diff --git a/package-lock.json b/package-lock.json index 08ce22f..694d8ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "source-doc", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "source-doc", - "version": "0.2.1", + "version": "0.3.0", "license": "MIT", "devDependencies": { "@types/glob": "^8.1.0", diff --git a/src/codeLensProvider.ts b/src/codeLensProvider.ts index 9a64e84..54c59f1 100644 --- a/src/codeLensProvider.ts +++ b/src/codeLensProvider.ts @@ -91,7 +91,9 @@ export class SourceDocCodeLensProvider implements vscode.CodeLensProvider { } const lenses: vscode.CodeLens[] = []; - const firstRange = new vscode.Range(0, 0, 0, document.lineAt(0).text.length); + const firstRange = document.lineCount > 0 + ? new vscode.Range(0, 0, 0, document.lineAt(0).text.length) + : new vscode.Range(0, 0, 0, 0); // File-level lens at line 0 — always shown (except 'none') lenses.push( @@ -187,7 +189,7 @@ export class SourceDocCodeLensProvider implements vscode.CodeLensProvider { // For code languages: match function/class/method/def/fn declarations. const CODE_RE = /^[ \t]*((?:export\s+)?(?:default\s+)?(?:async\s+)?function[\s*]+\w+|(?:export\s+)?(?:abstract\s+|sealed\s+)?class\s+\w+|(?:export\s+)?(?:abstract\s+)?interface\s+\w+|(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?(?:\([^)]*\)|\w+)\s*=>|(?:public|private|protected|internal)(?:\s+(?:static|override|virtual|abstract|async|readonly))*\s+\S+\s+\w+\s*[({]|def\s+\w+\s*\(|fn\s+\w+\s*[(<]|func\s+\w+\s*[(<])/; - const RE = isXml ? XAML_RE : CODE_RE;; + const RE = isXml ? XAML_RE : CODE_RE; // In block mode, pass the full element/function text as context. // For XAML, capture from the opening tag to the matching close or end-of-line. diff --git a/src/explanationProvider.ts b/src/explanationProvider.ts index c35bf85..8b8e142 100644 --- a/src/explanationProvider.ts +++ b/src/explanationProvider.ts @@ -76,12 +76,29 @@ export class ExplanationProvider implements vscode.Disposable { } } catch (err) { if (err instanceof vscode.LanguageModelError) { - throw new Error(`Copilot error (${err.code}): ${err.message}`); + switch (err.code) { + case 'NoPermissions': + throw new Error(`Copilot error (${err.code}): access denied. Please check your GitHub Copilot subscription.`); + case 'Blocked': + throw new Error(`Copilot error (${err.code}): request was blocked. Please check your GitHub Copilot settings.`); + case 'NotFound': + throw new Error(`Copilot error (${err.code}): model not found. Please check your GitHub Copilot extension.`); + case 'RequestFailed': + throw new Error(`Copilot error (${err.code}): request failed. Please try again.`); + default: + throw new Error(`Copilot error (${err.code}): ${err.message}. Please check your GitHub Copilot extension.`); + } } throw err; } result = result.trim(); + if (token.isCancellationRequested) { + throw new vscode.CancellationError(); + } + if (!result) { + throw new Error('Copilot returned an empty explanation. Please try again.'); + } this.addToCache(key, result); return result; } diff --git a/src/extension.ts b/src/extension.ts index 4a69fbd..0b42d67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -30,7 +30,13 @@ export function activate(context: vscode.ExtensionContext): void { ); // ── Register CodeLens provider for each configured language ────────────── - registerCodeLensProviders(context, codeLensProvider); + // Track ALL registrations in languageRegistrations (not context.subscriptions) + // so the full set can be disposed when sourceDoc.languages changes. + let languageRegistrations: vscode.Disposable[] = []; + registerCodeLensProviders(codeLensProvider, languageRegistrations); + + // Dispose the language registrations when the extension is deactivated. + context.subscriptions.push({ dispose: () => languageRegistrations.forEach(d => d.dispose()) }); // Refresh lenses when the user switches to a different editor tab context.subscriptions.push( @@ -43,13 +49,12 @@ export function activate(context: vscode.ExtensionContext): void { ); // Re-register providers when sourceDoc.languages changes - let languageRegistrations: vscode.Disposable[] = []; context.subscriptions.push( vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration('sourceDoc.languages')) { languageRegistrations.forEach(d => d.dispose()); languageRegistrations = []; - registerCodeLensProviders(context, codeLensProvider, languageRegistrations); + registerCodeLensProviders(codeLensProvider, languageRegistrations); } }), ); @@ -153,6 +158,11 @@ export function activate(context: vscode.ExtensionContext): void { } } + if (lines.length === 0) { + vscode.window.showInformationMessage('Source Doc: nothing to explain in this file.'); + return; + } + await vscode.window.withProgress( { location: vscode.ProgressLocation.Window, @@ -200,9 +210,8 @@ export function deactivate(): void { // ── Helpers ─────────────────────────────────────────────────────────────────── function registerCodeLensProviders( - context: vscode.ExtensionContext, provider: SourceDocCodeLensProvider, - into: vscode.Disposable[] = context.subscriptions as unknown as vscode.Disposable[], + into: vscode.Disposable[], ): void { const config = vscode.workspace.getConfiguration('sourceDoc'); const languages = config.get('languages') ?? [