Skip to content

Commit 805a202

Browse files
committed
feat: implement advanced search functionality with regex support and UI enhancements 🔍
1 parent 3592048 commit 805a202

File tree

6 files changed

+335
-37
lines changed

6 files changed

+335
-37
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ All notable changes to the "magento-log-viewer" extension will be documented in
88

99
## Latest Release
1010

11+
### [1.14.0] - 2025-07-02
12+
13+
- feat: Added Quick Search/Filter Box functionality for log entries
14+
- feat: Real-time filtering of log entries with case-sensitive and regex support
15+
- feat: Search UI integration with navigation buttons in tree view header
16+
- feat: Active search indicator with clear search capability
17+
- ui: Enhanced log viewer with search term highlighting and status display
18+
1119
### [1.13.0] - 2025-07-02
1220

1321
- perf: Enhanced file system caching with intelligent memory management

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ The Magento Log Viewer extension for Visual Studio Code provides a convenient wa
1111
- Tree view of log files from Magento's `var/log` directory
1212
- Section for Grouped log entries by severity level (ERROR, WARN, DEBUG, INFO)
1313
- Section for Report files with parsed and optimized titles
14+
- **Quick Search/Filter Box** - Real-time filtering of log entries with case-sensitive and regex support
15+
- **Advanced Search Options** - Search through log entries with pattern matching and text filtering
16+
- **Intelligent File Caching** - Enhanced performance with smart memory management and ~80% faster file reads
1417
- Option to group log entries by message content
1518
- Direct file opening with line highlighting
1619
- One-click log file clearing with confirmation popup
17-
- Status bar showing total log entries
18-
- Real-time log file monitoring
20+
- Status bar showing total log entries with active search indicator
21+
- Real-time log file monitoring with optimized cache invalidation
1922
- Badge in the tree view showing the total number of log entries
2023
- Improved report file titles by parsing content for better readability
2124
- Color-coded icons for different log levels (ERROR, WARN, DEBUG, INFO)
@@ -33,11 +36,16 @@ The Magento Log Viewer extension for Visual Studio Code provides a convenient wa
3336
4. Select your Magento root directory
3437
5. The extension will now load your log files
3538

36-
Note: Settings are workspace-specific, allowing different configurations for each Magento project.
39+
Note: Settings are workspace-specific, allowing different configurations for each Magento project. The extension features intelligent file caching for improved performance, especially with large log files.
3740

3841
## Usage
3942

4043
- **View Logs**: Open the Magento Log Viewer sidebar (M logo)
44+
- **Search Logs**: Click the search icon in the log view header to filter entries in real-time
45+
- **Text Search**: Enter any text to filter log entries containing that text
46+
- **Case-Sensitive**: Enable in settings (`magentoLogViewer.searchCaseSensitive`) for exact case matching
47+
- **Regex Support**: Enable in settings (`magentoLogViewer.searchUseRegex`) for pattern matching (e.g., `error.*critical`)
48+
- **Clear Search**: Click the clear icon (visible during active search) to remove filters
4149
- **Clear Logs**: Click the trash icon in the view header. A confirmation popup will appear to confirm the action.
4250
- **Refresh**: Click the refresh icon or wait for auto-update
4351
- **Navigate**: Click on log entries to jump to specific lines

package.json

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "magento-log-viewer",
33
"displayName": "Magento Log Viewer",
44
"description": "A Visual Studio Code extension to view and manage Magento log files.",
5-
"version": "1.13.0",
5+
"version": "1.14.0",
66
"publisher": "MathiasElle",
77
"icon": "resources/logo.png",
88
"repository": {
@@ -76,6 +76,16 @@
7676
"command": "magento-log-viewer.openFileAtLine",
7777
"title": "Open Log File at Line",
7878
"icon": "$(go-to-file)"
79+
},
80+
{
81+
"command": "magento-log-viewer.searchLogs",
82+
"title": "Search in Logs",
83+
"icon": "$(search)"
84+
},
85+
{
86+
"command": "magento-log-viewer.clearSearch",
87+
"title": "Clear Search",
88+
"icon": "$(clear-all)"
7989
}
8090
],
8191
"configuration": {
@@ -104,6 +114,18 @@
104114
"default": true,
105115
"description": "Group log entries by message content",
106116
"scope": "resource"
117+
},
118+
"magentoLogViewer.searchCaseSensitive": {
119+
"type": "boolean",
120+
"default": false,
121+
"description": "Enable case-sensitive search in log entries",
122+
"scope": "resource"
123+
},
124+
"magentoLogViewer.searchUseRegex": {
125+
"type": "boolean",
126+
"default": false,
127+
"description": "Enable regular expression search in log entries",
128+
"scope": "resource"
107129
}
108130
}
109131
},
@@ -186,15 +208,25 @@
186208
},
187209
"menus": {
188210
"view/title": [
211+
{
212+
"command": "magento-log-viewer.searchLogs",
213+
"when": "view == logFiles && magentoLogViewer.hasMagentoRoot",
214+
"group": "navigation@1"
215+
},
216+
{
217+
"command": "magento-log-viewer.clearSearch",
218+
"when": "view == logFiles && magentoLogViewer.hasActiveSearch",
219+
"group": "navigation@2"
220+
},
189221
{
190222
"command": "magento-log-viewer.clearAllLogFiles",
191223
"when": "view == logFiles && magentoLogViewer.hasLogFiles",
192-
"group": "navigation"
224+
"group": "navigation@3"
193225
},
194226
{
195227
"command": "magento-log-viewer.refreshLogFiles",
196228
"when": "view == logFiles && magentoLogViewer.hasMagentoRoot",
197-
"group": "navigation"
229+
"group": "navigation@4"
198230
},
199231
{
200232
"command": "magento-log-viewer.refreshReportFiles",

src/helpers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ export function activateExtension(context: vscode.ExtensionContext, magentoRoot:
9696
// Registers commands for the extension.
9797
export function registerCommands(context: vscode.ExtensionContext, logViewerProvider: LogViewerProvider, reportViewerProvider: ReportViewerProvider, magentoRoot: string): void {
9898
vscode.commands.registerCommand('magento-log-viewer.refreshLogFiles', () => logViewerProvider.refresh());
99-
vscode.commands.registerCommand('magento-log-viewer.refreshReportFiles', () => reportViewerProvider.refresh()); // Improved command registration for openFile
99+
vscode.commands.registerCommand('magento-log-viewer.refreshReportFiles', () => reportViewerProvider.refresh());
100+
101+
// Search commands
102+
vscode.commands.registerCommand('magento-log-viewer.searchLogs', () => logViewerProvider.searchInLogs());
103+
vscode.commands.registerCommand('magento-log-viewer.clearSearch', () => logViewerProvider.clearSearch()); // Improved command registration for openFile
100104
vscode.commands.registerCommand('magento-log-viewer.openFile', (filePath: string | unknown, lineNumber?: number) => {
101105
// If filePath is not a string, show a selection box with available log files
102106
if (typeof filePath !== 'string') {

src/logViewer.ts

Lines changed: 106 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
1010
private groupByMessage: boolean;
1111
private disposables: vscode.Disposable[] = [];
1212
private isInitialized: boolean = false;
13+
public searchTerm: string = '';
14+
public searchCaseSensitive: boolean = false;
15+
public searchUseRegex: boolean = false;
1316

1417
constructor(private workspaceRoot: string) {
1518
if (!LogViewerProvider.statusBarItem) {
@@ -21,6 +24,8 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
2124
const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri || null;
2225
const config = vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri);
2326
this.groupByMessage = config.get<boolean>('groupByMessage', true);
27+
this.searchCaseSensitive = config.get<boolean>('searchCaseSensitive', false);
28+
this.searchUseRegex = config.get<boolean>('searchUseRegex', false);
2429
this.updateRefreshButtonVisibility();
2530

2631
// Delay initial heavy operations to avoid competing with indexing
@@ -41,6 +46,56 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
4146

4247
private updateRefreshButtonVisibility(): void {
4348
vscode.commands.executeCommand('setContext', 'magentoLogViewer.hasMagentoRoot', !!this.workspaceRoot);
49+
vscode.commands.executeCommand('setContext', 'magentoLogViewer.hasActiveSearch', !!this.searchTerm);
50+
}
51+
52+
// Search functionality
53+
public async searchInLogs(): Promise<void> {
54+
const searchOptions = await vscode.window.showInputBox({
55+
prompt: 'Search in log entries...',
56+
placeHolder: 'Enter search term (supports regex if enabled in settings)',
57+
value: this.searchTerm
58+
});
59+
60+
if (searchOptions !== undefined) {
61+
this.searchTerm = searchOptions;
62+
this.updateRefreshButtonVisibility();
63+
this.refresh();
64+
65+
if (this.searchTerm) {
66+
vscode.window.showInformationMessage(`Searching for: "${this.searchTerm}"`);
67+
}
68+
}
69+
}
70+
71+
public clearSearch(): void {
72+
this.searchTerm = '';
73+
this.updateRefreshButtonVisibility();
74+
this.refresh();
75+
vscode.window.showInformationMessage('Search cleared');
76+
}
77+
78+
public matchesSearchTerm(text: string): boolean {
79+
if (!this.searchTerm) {
80+
return true; // No search term, show all
81+
}
82+
83+
try {
84+
if (this.searchUseRegex) {
85+
const flags = this.searchCaseSensitive ? 'g' : 'gi';
86+
const regex = new RegExp(this.searchTerm, flags);
87+
return regex.test(text);
88+
} else {
89+
const searchText = this.searchCaseSensitive ? text : text.toLowerCase();
90+
const searchTerm = this.searchCaseSensitive ? this.searchTerm : this.searchTerm.toLowerCase();
91+
return searchText.includes(searchTerm);
92+
}
93+
} catch (error) {
94+
// Invalid regex, fall back to simple string search
95+
const searchText = this.searchCaseSensitive ? text : text.toLowerCase();
96+
const searchTerm = this.searchCaseSensitive ? this.searchTerm : this.searchTerm.toLowerCase();
97+
return searchText.includes(searchTerm);
98+
}
4499
}
45100

46101
refresh(): void {
@@ -134,7 +189,7 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
134189
return items;
135190
}
136191

137-
private getLogFileLines(filePath: string): LogItem[] {
192+
public getLogFileLines(filePath: string): LogItem[] {
138193
const fileContent = getCachedFileContent(filePath);
139194
if (!fileContent) {
140195
return [];
@@ -144,17 +199,21 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
144199
return groupedLines;
145200
}
146201

147-
private groupLogEntries(lines: string[], filePath: string): LogItem[] {
202+
public groupLogEntries(lines: string[], filePath: string): LogItem[] {
148203
const groupedByType = new Map<string, { message: string, line: string, lineNumber: number }[]>();
149204

150205
lines.forEach((line, index) => {
151206
const match = line.match(/\.(\w+):/);
152207
if (match) {
153208
const level = match[1].toUpperCase();
154209
const message = line.replace(/^\[.*?\]\s*\.\w+:\s*/, '');
155-
const entries = groupedByType.get(level) || [];
156-
entries.push({ message, line, lineNumber: index });
157-
groupedByType.set(level, entries);
210+
211+
// Apply search filter
212+
if (this.matchesSearchTerm(line) || this.matchesSearchTerm(message)) {
213+
const entries = groupedByType.get(level) || [];
214+
entries.push({ message, line, lineNumber: index });
215+
groupedByType.set(level, entries);
216+
}
158217
}
159218
});
160219

@@ -163,9 +222,12 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
163222
const groupedByMessage = new Map<string, { line: string, lineNumber: number }[]>();
164223

165224
entries.forEach(entry => {
166-
const messageGroup = groupedByMessage.get(entry.message) || [];
167-
messageGroup.push({ line: entry.line, lineNumber: entry.lineNumber });
168-
groupedByMessage.set(entry.message, messageGroup);
225+
// Apply additional search filtering on message level
226+
if (this.matchesSearchTerm(entry.message) || this.matchesSearchTerm(entry.line)) {
227+
const messageGroup = groupedByMessage.get(entry.message) || [];
228+
messageGroup.push({ line: entry.line, lineNumber: entry.lineNumber });
229+
groupedByMessage.set(entry.message, messageGroup);
230+
}
169231
});
170232

171233
const messageGroups = Array.from(groupedByMessage.entries()).map(([message, messageEntries]) => {
@@ -189,30 +251,42 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
189251
);
190252
}).sort((a, b) => a.label.localeCompare(b.label)); // Sort message groups alphabetically
191253

192-
const logFile = new LogItem(`${level} (${entries.length}, grouped)`, vscode.TreeItemCollapsibleState.Collapsed, undefined, messageGroups);
193-
logFile.iconPath = getIconForLogLevel(level);
194-
return logFile;
254+
// Only add log level if it has matching entries after filtering
255+
if (messageGroups.length > 0) {
256+
const logFile = new LogItem(`${level} (${entries.length}, grouped)`, vscode.TreeItemCollapsibleState.Collapsed, undefined, messageGroups);
257+
logFile.iconPath = getIconForLogLevel(level);
258+
return logFile;
259+
}
260+
return null;
195261
} else {
196-
const logFile = new LogItem(`${level} (${entries.length})`, vscode.TreeItemCollapsibleState.Collapsed, undefined,
197-
entries.map(entry => {
198-
const lineNumber = (entry.lineNumber + 1).toString().padStart(2, '0');
199-
// Format the timestamp in the log entry
200-
const formattedLine = formatTimestamp(entry.line);
201-
return new LogItem(
202-
`Line ${lineNumber}: ${formattedLine}`,
203-
vscode.TreeItemCollapsibleState.None,
204-
{
205-
command: 'magento-log-viewer.openFileAtLine',
206-
title: 'Open Log File at Line',
207-
arguments: [filePath, entry.lineNumber]
208-
}
209-
);
210-
}).sort((a, b) => a.label.localeCompare(b.label)) // Sort entries alphabetically
262+
// Filter entries in non-grouped mode too
263+
const filteredEntries = entries.filter(entry =>
264+
this.matchesSearchTerm(entry.message) || this.matchesSearchTerm(entry.line)
211265
);
212-
logFile.iconPath = getIconForLogLevel(level);
213-
return logFile;
266+
267+
if (filteredEntries.length > 0) {
268+
const logFile = new LogItem(`${level} (${filteredEntries.length})`, vscode.TreeItemCollapsibleState.Collapsed, undefined,
269+
filteredEntries.map(entry => {
270+
const lineNumber = (entry.lineNumber + 1).toString().padStart(2, '0');
271+
// Format the timestamp in the log entry
272+
const formattedLine = formatTimestamp(entry.line);
273+
return new LogItem(
274+
`Line ${lineNumber}: ${formattedLine}`,
275+
vscode.TreeItemCollapsibleState.None,
276+
{
277+
command: 'magento-log-viewer.openFileAtLine',
278+
title: 'Open Log File at Line',
279+
arguments: [filePath, entry.lineNumber]
280+
}
281+
);
282+
}).sort((a, b) => a.label.localeCompare(b.label)) // Sort entries alphabetically
283+
);
284+
logFile.iconPath = getIconForLogLevel(level);
285+
return logFile;
286+
}
287+
return null;
214288
}
215-
}).sort((a, b) => a.label.localeCompare(b.label)); // Sort log files alphabetically
289+
}).filter((item): item is LogItem => item !== null).sort((a, b) => a.label.localeCompare(b.label)); // Sort log files alphabetically
216290
}
217291

218292
getLogFilesWithoutUpdatingBadge(dir: string): LogItem[] {
@@ -264,7 +338,9 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
264338
const logPath = path.join(this.workspaceRoot, 'var', 'log');
265339
const logFiles = this.getLogFilesWithoutUpdatingBadge(logPath);
266340
const totalEntries = logFiles.reduce((count, file) => count + parseInt(file.description?.match(/\d+/)?.[0] || '0', 10), 0);
267-
LogViewerProvider.statusBarItem.text = `Magento Log-Entries: ${totalEntries}`;
341+
342+
const searchInfo = this.searchTerm ? ` | Search: "${this.searchTerm}"` : '';
343+
LogViewerProvider.statusBarItem.text = `Magento Log-Entries: ${totalEntries}${searchInfo}`;
268344
}
269345

270346
dispose() {

0 commit comments

Comments
 (0)