Skip to content

Commit 50d5d8f

Browse files
author
duncan
committed
Feature: Support a Code Lens to show the error above the line (usernamehw#74 )
1 parent 972ea1f commit 50d5d8f

File tree

5 files changed

+214
-2
lines changed

5 files changed

+214
-2
lines changed

package.json

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,71 @@
939939
"type": "boolean",
940940
"default": false,
941941
"description": "Controls whether to run on untitled (unsaved) files."
942-
}
942+
},
943+
"errorLens.codeLensEnabled": {
944+
"type": "boolean",
945+
"default": true,
946+
"description": "Controls whether to show the Error Lens as a Code Lens above the code."
947+
},
948+
"errorLens.codeLensPrefix": {
949+
"type": "object",
950+
"properties": {
951+
"error": {
952+
"type": "string",
953+
"minLength": 0,
954+
"maxLength": 50,
955+
"default": ""
956+
},
957+
"warning": {
958+
"type": "string",
959+
"minLength": 0,
960+
"maxLength": 50,
961+
"default": "⚠️"
962+
},
963+
"info": {
964+
"type": "string",
965+
"minLength": 0,
966+
"maxLength": 50,
967+
"default": ""
968+
},
969+
"hint": {
970+
"type": "string",
971+
"minLength": 0,
972+
"maxLength": 50,
973+
"default": ""
974+
}
975+
},
976+
"default": {
977+
"error": "",
978+
"warning": "⚠️",
979+
"info": "",
980+
"hint": ""
981+
},
982+
"additionalProperties": false,
983+
"markdownDescription": "Pick an emoji/prefix for `#errorLens.showCodeLens#` e.g. 🔥"
984+
},
985+
"errorLens.codeLensMessageTemplate": {
986+
"type": "string",
987+
"default": "$message",
988+
"markdownDescription": "Template used for all messages appearing in Code Lens. Whitespace between items is important.\n\nList of variables:\n\n- `$message` - diagnostic message text\n\n- `$count` - Number of diagnostics on the line\n\n- `$severity` - Severity prefix taken from `#errorLens.severityText#`\n\n- `$source` - Source of diagnostic e.g. \"eslint\"\n\n- `$code` - Code of the diagnostic"
989+
},
990+
"errorLens.codeLensOnClick": {
991+
"type": "string",
992+
"enum": [
993+
"none",
994+
"showProblemWindow",
995+
"showQuickFix",
996+
"searchForProblem"
997+
],
998+
"enumDescriptions": [
999+
"Does nothing.",
1000+
"shows problem window.",
1001+
"Shows quick fix menu.",
1002+
"Search for the problem."
1003+
],
1004+
"default": "showQuickFix",
1005+
"markdownDescription": "Controls what happens when clicking on the code lens."
1006+
}
9431007
}
9441008
},
9451009
"colors": [

src/ErrorCodeLensProvider.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as vscode from 'vscode';
2+
import { $config } from 'src/extension';
3+
import { extUtils } from './utils/extUtils';
4+
5+
/**
6+
* ErrorCodeLensProvider
7+
*/
8+
export class ErrorCodeLensProvider implements vscode.CodeLensProvider {
9+
public onDidChangeCodeLenses: vscode.Event<void>;
10+
private codeLenses: vscode.CodeLens[] = [];
11+
private readonly _onDidChangeCodeLenses: vscode.EventEmitter<void>;
12+
private disposables: vscode.Disposable[];
13+
14+
constructor(_: vscode.ExtensionContext) {
15+
this._onDidChangeCodeLenses = new vscode.EventEmitter<void>();
16+
this.onDidChangeCodeLenses = this._onDidChangeCodeLenses.event;
17+
18+
this.disposables = [
19+
vscode.languages.registerCodeLensProvider('*', this),
20+
vscode.commands.registerCommand('errorLens.codeLensAction', diagnostic => {
21+
switch ($config.codeLensOnClick) {
22+
case 'showProblemWindow':
23+
vscode.commands.executeCommand('workbench.action.problems.focus', diagnostic);
24+
break;
25+
case 'showQuickFix':
26+
ErrorCodeLensProvider.setCaretInEditor(diagnostic as vscode.Diagnostic);
27+
vscode.commands.executeCommand('editor.action.quickFix', diagnostic);
28+
break;
29+
case 'searchForProblem':
30+
ErrorCodeLensProvider.setCaretInEditor(diagnostic as vscode.Diagnostic);
31+
vscode.commands.executeCommand('errorLens.searchForProblem', diagnostic);
32+
break;
33+
case 'none':
34+
default:
35+
break;
36+
}
37+
}),
38+
];
39+
}
40+
41+
static formatMessage(diagnostic: vscode.Diagnostic): string {
42+
return `${ErrorCodeLensProvider.getPrefix(diagnostic)}${extUtils.prepareMessage({
43+
template: $config.codeLensMessageTemplate,
44+
diagnostic,
45+
lineProblemCount: 1,
46+
removeLinebreaks: false,
47+
replaceLinebreaksSymbol: '',
48+
})}`;
49+
}
50+
51+
static setCaretInEditor(diagnostic: vscode.Diagnostic): void {
52+
const editor = vscode.window.activeTextEditor;
53+
if (editor) {
54+
editor.selection = new vscode.Selection(diagnostic.range.start, diagnostic.range.end);
55+
editor.revealRange(diagnostic.range);
56+
}
57+
}
58+
59+
static getPrefix(diagnostic: vscode.Diagnostic): string {
60+
switch (diagnostic.severity) {
61+
case vscode.DiagnosticSeverity.Error:
62+
return $config.codeLensPrefix.error;
63+
case vscode.DiagnosticSeverity.Warning:
64+
return $config.codeLensPrefix.warning;
65+
case vscode.DiagnosticSeverity.Information:
66+
return $config.codeLensPrefix.info;
67+
case vscode.DiagnosticSeverity.Hint:
68+
return $config.codeLensPrefix.hint;
69+
default:
70+
return '';
71+
}
72+
}
73+
74+
public provideCodeLenses(document: vscode.TextDocument, _: vscode.CancellationToken): Thenable<vscode.CodeLens[]> | vscode.CodeLens[] {
75+
this.codeLenses = [];
76+
if ($config.codeLensEnabled) {
77+
const diagnostics = vscode.languages.getDiagnostics(document.uri);
78+
for (const diagnostic of diagnostics) {
79+
if (!extUtils.shouldExcludeDiagnostic(diagnostic)) {
80+
this.codeLenses.push(new vscode.CodeLens(
81+
diagnostic.range,
82+
{
83+
title: ErrorCodeLensProvider.formatMessage(diagnostic),
84+
command: 'errorLens.codeLensAction',
85+
arguments: [diagnostic],
86+
},
87+
));
88+
}
89+
}
90+
}
91+
return this.codeLenses;
92+
}
93+
94+
public resolveCodeLens(codeLens: vscode.CodeLens, _: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens> {
95+
// eslint-disable-next-line no-param-reassign
96+
codeLens.command = {
97+
title: 'Codelens provided by ErrorLens extension',
98+
tooltip: 'Tooltip provided by ErrorLens extension',
99+
command: 'errorLens.codelensAction',
100+
arguments: ['Diagnostic'],
101+
};
102+
return codeLens;
103+
}
104+
105+
public dispose(): void {
106+
this.codeLenses = [];
107+
this._onDidChangeCodeLenses.dispose();
108+
for (const disposable of this.disposables) {
109+
disposable.dispose();
110+
}
111+
this.disposables = [];
112+
}
113+
114+
public updateCodeLenses(): void {
115+
this._onDidChangeCodeLenses.fire();
116+
}
117+
}
118+

src/decorations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ export function doUpdateDecorations(editor: TextEditor, groupedDiagnostics: Grou
520520
}
521521

522522
$state.statusBarMessage.updateText(editor, groupedDiagnostics);
523+
524+
$state.errorCodeLensProvider.updateCodeLenses();
523525
}
524526

525527
export function updateDecorationsForAllVisibleEditors(): void {

src/extension.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { StatusBarMessage } from 'src/statusBar/statusBarMessage';
66
import { Constants, type ExtensionConfig } from 'src/types';
77
import { extUtils } from 'src/utils/extUtils';
88
import { workspace, type ExtensionContext } from 'vscode';
9+
import { ErrorCodeLensProvider } from './ErrorCodeLensProvider';
910

1011
/**
1112
* All user settings.
@@ -32,6 +33,10 @@ export abstract class $state {
3233
* Status bar object. Handles all status bar stuff (for icons)
3334
*/
3435
static statusBarIcons: StatusBarIcons;
36+
/**
37+
* Code Lens Provider. Handles all Code Lens stuff
38+
*/
39+
static errorCodeLensProvider: ErrorCodeLensProvider;
3540
/**
3641
* Array of RegExp matchers and their updated messages.
3742
* message may include groups references like $0 (entire expression), $1 (first group), etc.
@@ -142,6 +147,7 @@ export function updateEverything(context: ExtensionContext): void {
142147
alignment: $config.statusBarIconsAlignment,
143148
targetProblems: $config.statusBarIconsTargetProblems,
144149
});
150+
$state.errorCodeLensProvider = new ErrorCodeLensProvider(context);
145151

146152
$state.configErrorEnabled = $config.enabledDiagnosticLevels.includes('error');
147153
$state.configWarningEnabled = $config.enabledDiagnosticLevels.includes('warning');
@@ -219,6 +225,7 @@ export function disposeEverything(): void {
219225
disposeAllEventListeners();
220226
$state.statusBarMessage?.dispose();
221227
$state.statusBarIcons?.dispose();
228+
$state.errorCodeLensProvider?.dispose();
222229
disposeAllDecorations();
223230
}
224231

src/types.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ interface ExtensionConfigType {
184184
delayMode: 'debounce' | 'old';
185185
/**
186186
* Highlight only portion of the problems.
187-
* For instance, only active line or the closest to the cursor diasnostic.
187+
* For instance, only active line or the closest to the cursor diagnostic.
188188
*/
189189
followCursor: 'activeLine' | 'allLines' | 'allLinesExceptActive' | 'closestProblem' | 'closestProblemMultiline' | 'closestProblemMultilineBySeverity' | 'closestProblemMultilineInViewport';
190190
/**
@@ -336,6 +336,27 @@ interface ExtensionConfigType {
336336
* Controls whether to run on untitled (unsaved) files.
337337
*/
338338
ignoreUntitled: boolean;
339+
/**
340+
* "Controls whether to show the Error Lens as a Code Lens above the code."
341+
*/
342+
codeLensEnabled: boolean;
343+
/**
344+
* Pick prefix for `#errorLens.showCodeLens#` e.g. 🔥
345+
*/
346+
codeLensPrefix: {
347+
error: string;
348+
warning: string;
349+
info: string;
350+
hint: string;
351+
};
352+
/**
353+
* See `#errorLens.messageTemplate#`.
354+
*/
355+
codeLensMessageTemplate: string;
356+
/**
357+
* Controls what do on clicking the code lens
358+
*/
359+
codeLensOnClick: 'none' | 'searchForProblem' | 'showProblemWindow' | 'showQuickFix';
339360
}
340361

341362
export type ExtensionConfig = Readonly<ExtensionConfigType>;

0 commit comments

Comments
 (0)