Skip to content

Commit ff5f1c4

Browse files
committed
feat(config): Auswahl des Magento‑Root, relative Pfade & Standard auf Workspace‑Root ✨
- Neuer Command "Magento Log Viewer: Select Root Folder" (package.json + helpers) 🔎 - Speichere magentoRoot als relativen Pfad und implementiere getEffectiveMagentoRoot() mit Workspace‑Root als Fallback 🗂️ - Erzwinge TreeView‑Refresh nach Provider‑Initialisierung, damit "Loading log files..." verschwindet 🔁 - CHANGELOG aktualisiert und Unit‑Tests für getEffectiveMagentoRoot hinzugefügt 🧪
1 parent 6b2849d commit ff5f1c4

File tree

7 files changed

+332
-9
lines changed

7 files changed

+332
-9
lines changed

.github/copilot-instructions.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Copilot Instructions for Magento Log Viewer Extension
2+
3+
## Architecture Overview
4+
5+
This is a VS Code extension that provides intelligent viewing and management of Magento log files. The extension follows a modular architecture with clear separation of concerns:
6+
7+
- **`src/extension.ts`** - Entry point with asynchronous initialization pattern to avoid blocking VS Code startup
8+
- **`src/logViewer.ts`** - Core TreeDataProvider implementations (`LogViewerProvider`, `ReportViewerProvider`)
9+
- **`src/helpers.ts`** - Utility functions, caching system, file operations, and command handlers
10+
- **`src/updateNotifier.ts`** - Version update notifications and changelog integration
11+
12+
## Key Architectural Patterns
13+
14+
### Workspace-Scoped Configuration
15+
The extension uses workspace-scoped settings via `vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri)`. All configuration is stored per-workspace, allowing different Magento projects to have independent settings.
16+
17+
### Asynchronous Initialization
18+
```typescript
19+
// Pattern: Delay heavy operations to let VS Code indexing settle
20+
await new Promise(resolve => setTimeout(resolve, 500));
21+
```
22+
23+
### Intelligent File Caching System
24+
The extension implements a sophisticated caching mechanism in `helpers.ts`:
25+
- **Line count caching** - Avoids re-reading files for badge counts
26+
- **File content caching** - Smart memory management with size limits
27+
- **Cache invalidation** - File system watchers automatically invalidate stale cache entries
28+
- Use `getCacheStatistics()`, `clearFileContentCache()`, `invalidateFileCache()` for cache management
29+
30+
### Context-Driven UI
31+
Commands are conditionally shown using VS Code's `when` clauses:
32+
- `magentoLogViewer.hasMagentoRoot` - Controls visibility of most features
33+
- `magentoLogViewer.hasActiveSearch` - Shows/hides search clear button
34+
- `magentoLogViewer.hasLogFiles` - Controls log file operations
35+
36+
## Critical Development Workflows
37+
38+
### Build & Development
39+
```bash
40+
npm run compile # Webpack build for development
41+
npm run watch # Watch mode during development
42+
npm run package # Production build for publishing
43+
npm run test # Run extension tests
44+
```
45+
46+
### Testing Strategy
47+
Tests are located in `src/test/` with focused test files:
48+
- `extension.test.ts` - Basic extension lifecycle
49+
- `caching.test.ts` - Cache system validation
50+
- `search.test.ts` - Search functionality
51+
- Run tests with `npm run test` or F5 debug launch
52+
53+
### Extension Configuration Flow
54+
1. Extension activates on `onStartupFinished`
55+
2. Checks `isMagentoProject` setting ("Please select" → prompt user)
56+
3. Validates `magentoRoot` path → auto-opens folder picker if invalid
57+
4. Creates providers and activates file system watchers on `var/log` and `var/report`
58+
59+
## Project-Specific Conventions
60+
61+
### File System Patterns
62+
- **Log files**: `{magentoRoot}/var/log/*.log`
63+
- **Report files**: `{magentoRoot}/var/report/` (recursive directory scanning)
64+
- **Icons**: Color-coded by log level (ERROR=red, WARN=orange, DEBUG=yellow, INFO=blue)
65+
66+
### TreeItem Structure
67+
```typescript
68+
// Pattern: LogItem extends vscode.TreeItem with command integration
69+
new LogItem(label, collapsibleState, {
70+
command: 'magento-log-viewer.openFile',
71+
arguments: [filePath, lineNumber]
72+
})
73+
```
74+
75+
### Search Implementation
76+
- Real-time filtering via `quickPick.onDidChangeValue`
77+
- Supports regex patterns when `searchUseRegex` is enabled
78+
- Case sensitivity controlled by `searchCaseSensitive` setting
79+
- Search state managed through context variables for UI updates
80+
81+
### Error Handling Pattern
82+
```typescript
83+
// Pattern: User-friendly error messages with actionable buttons
84+
vscode.window.showErrorMessage('Error message', 'Action Button').then(selection => {
85+
if (selection === 'Action Button') {
86+
// Provide immediate solution
87+
}
88+
});
89+
```
90+
91+
### Memory Management
92+
- Dispose pattern: All providers implement `dispose()` with cleanup of disposables array
93+
- Cache optimization: `optimizeCacheSize()` runs automatically to prevent memory leaks
94+
- File watchers: Properly disposed in `deactivate()` function
95+
96+
## Integration Points
97+
98+
### VS Code APIs Used
99+
- **TreeDataProvider**: Custom tree views for log/report files
100+
- **FileSystemWatcher**: Real-time file change detection
101+
- **QuickPick**: Search interface and file selection
102+
- **Workspace Configuration**: Per-workspace settings
103+
- **Status Bar**: Badge counts and search indicators
104+
105+
### External Dependencies
106+
- **Webpack**: Bundles TypeScript to single `dist/extension.js`
107+
- **Node.js fs**: Synchronous and async file operations
108+
- **VS Code Test Framework**: `@vscode/test-cli` for extension testing
109+
110+
## Performance Considerations
111+
112+
- **Lazy loading**: Heavy operations delayed until user interaction
113+
- **Throttled badge updates**: Maximum one badge update per second
114+
- **Smart caching**: File content cached with automatic memory limits
115+
- **Async file operations**: Uses `fs.promises` for non-blocking I/O where possible
116+
117+
When modifying this extension, prioritize user experience with immediate feedback, maintain the caching system integrity, and ensure proper disposal of resources to prevent memory leaks.

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ All notable changes to the "magento-log-viewer" extension will be documented in
55

66
## Next release
77

8-
- ux: Automatic folder picker dialog when Magento root path is not set or invalid
9-
- ux: Improved error handling with direct path selection option
8+
- config: Default Magento root is now the workspace root if unset; paths are stored relative to workspace
9+
- ux: Added "Select Root Folder" command for easy Magento root configuration and automatic folder picker when path is missing or invalid
10+
- fix: Log and report files now load automatically without manual refresh; improved error handling with direct path selection
1011

1112
---
1213

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
"command": "magento-log-viewer.showCacheStatistics",
9292
"title": "Show Cache Statistics",
9393
"icon": "$(info)"
94+
},
95+
{
96+
"command": "magento-log-viewer.selectMagentoRootFromSettings",
97+
"title": "Magento Log Viewer: Select Root Folder",
98+
"category": "Magento Log Viewer"
9499
}
95100
],
96101
"configuration": {
@@ -111,7 +116,7 @@
111116
"magentoLogViewer.magentoRoot": {
112117
"type": "string",
113118
"default": "",
114-
"description": "Path to the Magento root folder",
119+
"markdownDescription": "Path to the Magento root folder. Use Command Palette: `Magento Log Viewer: Select Root Folder` to browse and select a folder.",
115120
"scope": "resource"
116121
},
117122
"magentoLogViewer.groupByMessage": {

src/extension.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { promptMagentoProjectSelection, showErrorMessage, activateExtension, isValidPath, deleteReportFile, clearFileContentCache, selectMagentoRootFolder, selectMagentoRootFolderDirect } from './helpers';
2+
import { promptMagentoProjectSelection, showErrorMessage, activateExtension, isValidPath, deleteReportFile, clearFileContentCache, selectMagentoRootFolder, selectMagentoRootFolderDirect, getEffectiveMagentoRoot, selectMagentoRootFromSettings } from './helpers';
33
import { LogItem, ReportViewerProvider } from './logViewer';
44
import { showUpdateNotification } from './updateNotifier';
55

@@ -10,6 +10,13 @@ export function activate(context: vscode.ExtensionContext): void {
1010
// Show Update-Popup first (lightweight operation)
1111
showUpdateNotification(context);
1212

13+
// Register the settings button command
14+
const selectMagentoRootCommand = vscode.commands.registerCommand('magento-log-viewer.selectMagentoRootFromSettings', () => {
15+
selectMagentoRootFromSettings();
16+
});
17+
disposables.push(selectMagentoRootCommand);
18+
context.subscriptions.push(selectMagentoRootCommand);
19+
1320
// Initialize extension in a more intelligent way
1421
const initializeExtension = async () => {
1522
try {
@@ -29,7 +36,7 @@ export function activate(context: vscode.ExtensionContext): void {
2936
if (isMagentoProject === 'Please select') {
3037
promptMagentoProjectSelection(config, context);
3138
} else if (isMagentoProject === 'Yes') {
32-
const magentoRoot = config.get<string>('magentoRoot', '');
39+
const magentoRoot = getEffectiveMagentoRoot(workspaceUri);
3340
if (!magentoRoot || !isValidPath(magentoRoot)) {
3441
// Show error message and automatically open folder picker
3542
vscode.window.showErrorMessage(

src/helpers.ts

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ export function selectMagentoRootFolder(config: vscode.WorkspaceConfiguration, c
2525
const defaultUri = workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0].uri : undefined;
2626
vscode.window.showOpenDialog({ defaultUri, canSelectFolders: true, canSelectMany: false, openLabel: 'Select Magento Root Folder' }).then(folderUri => {
2727
if (folderUri?.[0]) {
28-
const newConfig = vscode.workspace.getConfiguration('magentoLogViewer', folderUri[0]);
29-
updateConfig(newConfig, 'magentoRoot', folderUri[0].fsPath).then(() => {
28+
const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri || null;
29+
const newConfig = vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri);
30+
const relativePath = vscode.workspace.asRelativePath(folderUri[0].fsPath);
31+
updateConfig(newConfig, 'magentoRoot', relativePath).then(() => {
3032
showInformationMessage('Magento root folder successfully saved!');
3133
updateConfig(newConfig, 'isMagentoProject', 'Yes');
3234
activateExtension(context, folderUri[0].fsPath, new ReportViewerProvider(folderUri[0].fsPath));
@@ -49,8 +51,10 @@ export function selectMagentoRootFolderDirect(config: vscode.WorkspaceConfigurat
4951
title: 'Select Magento Root Folder'
5052
}).then(folderUri => {
5153
if (folderUri?.[0]) {
52-
const newConfig = vscode.workspace.getConfiguration('magentoLogViewer', folderUri[0]);
53-
updateConfig(newConfig, 'magentoRoot', folderUri[0].fsPath).then(() => {
54+
const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri || null;
55+
const newConfig = vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri);
56+
const relativePath = vscode.workspace.asRelativePath(folderUri[0].fsPath);
57+
updateConfig(newConfig, 'magentoRoot', relativePath).then(() => {
5458
showInformationMessage('Magento root folder successfully saved!');
5559
updateConfig(newConfig, 'isMagentoProject', 'Yes');
5660
activateExtension(context, folderUri[0].fsPath, new ReportViewerProvider(folderUri[0].fsPath));
@@ -59,6 +63,66 @@ export function selectMagentoRootFolderDirect(config: vscode.WorkspaceConfigurat
5963
});
6064
}
6165

66+
// Opens folder selection dialog from Command Palette and updates the magentoRoot configuration.
67+
export async function selectMagentoRootFromSettings(): Promise<void> {
68+
const workspaceFolders = vscode.workspace.workspaceFolders;
69+
const workspaceUri = workspaceFolders?.[0]?.uri || null;
70+
71+
// Get current configuration for display
72+
const config = vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri);
73+
const currentPath = config.get<string>('magentoRoot', '');
74+
const currentPathDisplay = currentPath || 'Not set (using workspace root)';
75+
76+
// Show info about current setting
77+
const proceed = await vscode.window.showInformationMessage(
78+
`Current Magento root: ${currentPathDisplay}\n\nDo you want to select a new Magento root folder?`,
79+
'Select New Folder',
80+
'Cancel'
81+
);
82+
83+
if (proceed === 'Select New Folder') {
84+
const defaultUri = workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0].uri : undefined;
85+
86+
const folderUri = await vscode.window.showOpenDialog({
87+
defaultUri,
88+
canSelectFolders: true,
89+
canSelectMany: false,
90+
openLabel: 'Select Magento Root Folder',
91+
title: 'Select Magento Root Folder'
92+
});
93+
94+
if (folderUri?.[0]) {
95+
try {
96+
// Store the relative path instead of absolute path
97+
const relativePath = vscode.workspace.asRelativePath(folderUri[0].fsPath);
98+
await updateConfig(config, 'magentoRoot', relativePath);
99+
100+
// Also set the project as Magento project if not already set
101+
const isMagentoProject = config.get<string>('isMagentoProject', 'Please select');
102+
if (isMagentoProject !== 'Yes') {
103+
await updateConfig(config, 'isMagentoProject', 'Yes');
104+
}
105+
106+
// Show success message with the new path
107+
showInformationMessage(`✅ Magento root updated to: ${relativePath}`);
108+
109+
// Suggest reloading the window for changes to take effect
110+
const reload = await vscode.window.showInformationMessage(
111+
'Reload the window for all changes to take effect.',
112+
'Reload Window',
113+
'Later'
114+
);
115+
116+
if (reload === 'Reload Window') {
117+
vscode.commands.executeCommand('workbench.action.reloadWindow');
118+
}
119+
} catch (error) {
120+
showErrorMessage(`Failed to update Magento root path: ${error instanceof Error ? error.message : String(error)}`);
121+
}
122+
}
123+
}
124+
}
125+
62126
// Updates the specified configuration key with the given value.
63127
export function updateConfig(config: vscode.WorkspaceConfiguration, key: string, value: unknown): Thenable<void> {
64128
return config.update(key, value, vscode.ConfigurationTarget.Workspace);
@@ -368,6 +432,42 @@ export function pathExists(p: string): boolean {
368432
return true;
369433
}
370434

435+
/**
436+
* Gets the effective Magento root path. If the configured path is empty,
437+
* returns the workspace root as the default.
438+
* @param workspaceUri The workspace URI to get configuration from
439+
* @returns string - The effective Magento root path
440+
*/
441+
export function getEffectiveMagentoRoot(workspaceUri?: vscode.Uri | null): string {
442+
const config = vscode.workspace.getConfiguration('magentoLogViewer', workspaceUri);
443+
const configuredRoot = config.get<string>('magentoRoot', '');
444+
const workspaceFolders = vscode.workspace.workspaceFolders;
445+
446+
// If configured root is not empty, resolve it
447+
if (configuredRoot && configuredRoot.trim()) {
448+
// If it's already an absolute path, use it as is
449+
if (path.isAbsolute(configuredRoot)) {
450+
return configuredRoot;
451+
}
452+
453+
// If it's a relative path, resolve it against the workspace root
454+
if (workspaceFolders && workspaceFolders.length > 0) {
455+
return path.resolve(workspaceFolders[0].uri.fsPath, configuredRoot);
456+
}
457+
458+
// Fallback: return the configured path as is
459+
return configuredRoot;
460+
}
461+
462+
// Otherwise, use workspace root as default
463+
if (workspaceFolders && workspaceFolders.length > 0) {
464+
return workspaceFolders[0].uri.fsPath;
465+
}
466+
467+
// Fallback: return empty string if no workspace
468+
return '';
469+
}
470+
371471
// Cache for file line counts
372472
const lineCountCache = new Map<string, { count: number, timestamp: number }>();
373473

src/logViewer.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
3939
await new Promise(resolve => setTimeout(resolve, 300));
4040
this.updateBadge();
4141
this.isInitialized = true;
42+
43+
// Trigger refresh to show the actual log files instead of "Loading log files..."
44+
this._onDidChangeTreeData.fire();
4245
} catch (error) {
4346
console.error('Error during LogViewerProvider initialization:', error);
4447
this.isInitialized = true; // Set to true anyway to prevent blocking
48+
49+
// Still trigger refresh even on error to show the UI
50+
this._onDidChangeTreeData.fire();
4551
}
4652
}
4753

@@ -532,9 +538,15 @@ export class ReportViewerProvider implements vscode.TreeDataProvider<LogItem>, v
532538
// Wait a bit for VS Code indexing to settle
533539
await new Promise(resolve => setTimeout(resolve, 300));
534540
this.isInitialized = true;
541+
542+
// Trigger refresh to show the actual report files instead of "Loading report files..."
543+
this._onDidChangeTreeData.fire();
535544
} catch (error) {
536545
console.error('Error during ReportViewerProvider initialization:', error);
537546
this.isInitialized = true; // Set to true anyway to prevent blocking
547+
548+
// Still trigger refresh even on error to show the UI
549+
this._onDidChangeTreeData.fire();
538550
}
539551
}
540552

0 commit comments

Comments
 (0)