Skip to content

Commit 52b2e4d

Browse files
Copilotalexr00
andcommitted
Add debouncing and validation improvements to diagnostic provider
Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 17b6329 commit 52b2e4d

File tree

1 file changed

+160
-125
lines changed

1 file changed

+160
-125
lines changed
Lines changed: 160 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,160 @@
1-
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) Microsoft Corporation. All rights reserved.
3-
* Licensed under the MIT License. See License.txt in the project root for license information.
4-
*--------------------------------------------------------------------------------------------*/
5-
6-
import * as vscode from 'vscode';
7-
import { StateManager } from './stateManager';
8-
import { getIssue } from './util';
9-
import { CREATE_ISSUE_TRIGGERS, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys';
10-
import { escapeRegExp } from '../common/utils';
11-
import { RepositoriesManager } from '../github/repositoriesManager';
12-
import { ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput } from '../github/utils';
13-
14-
export class IssueTodoDiagnosticProvider {
15-
private diagnosticCollection: vscode.DiagnosticCollection;
16-
private expression: RegExp | undefined;
17-
18-
constructor(
19-
context: vscode.ExtensionContext,
20-
private manager: RepositoriesManager,
21-
private stateManager: StateManager,
22-
) {
23-
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('github-issues-todo');
24-
context.subscriptions.push(this.diagnosticCollection);
25-
26-
context.subscriptions.push(
27-
vscode.workspace.onDidChangeConfiguration(e => {
28-
if (e.affectsConfiguration(ISSUES_SETTINGS_NAMESPACE)) {
29-
this.updateTriggers();
30-
}
31-
}),
32-
);
33-
34-
context.subscriptions.push(
35-
vscode.workspace.onDidOpenTextDocument(document => {
36-
this.validateDocument(document);
37-
}),
38-
);
39-
40-
context.subscriptions.push(
41-
vscode.workspace.onDidChangeTextDocument(e => {
42-
this.validateDocument(e.document);
43-
}),
44-
);
45-
46-
context.subscriptions.push(
47-
vscode.workspace.onDidCloseTextDocument(document => {
48-
this.diagnosticCollection.delete(document.uri);
49-
}),
50-
);
51-
52-
this.updateTriggers();
53-
54-
// Validate all currently open documents
55-
vscode.workspace.textDocuments.forEach(document => {
56-
this.validateDocument(document);
57-
});
58-
}
59-
60-
private updateTriggers() {
61-
const triggers = vscode.workspace.getConfiguration(ISSUES_SETTINGS_NAMESPACE).get(CREATE_ISSUE_TRIGGERS, []);
62-
this.expression = triggers.length > 0 ? new RegExp(triggers.map(trigger => escapeRegExp(trigger)).join('|')) : undefined;
63-
}
64-
65-
private async validateDocument(document: vscode.TextDocument): Promise<void> {
66-
if (!this.expression) {
67-
return;
68-
}
69-
70-
const folderManager = this.manager.getManagerForFile(document.uri);
71-
if (!folderManager) {
72-
return;
73-
}
74-
75-
const diagnostics: vscode.Diagnostic[] = [];
76-
77-
for (let lineNumber = 0; lineNumber < document.lineCount; lineNumber++) {
78-
const line = document.lineAt(lineNumber);
79-
const lineText = line.text;
80-
81-
// Check if line contains a TODO trigger
82-
if (!this.expression.test(lineText)) {
83-
continue;
84-
}
85-
86-
// Look for issue references on this line
87-
const match = lineText.match(ISSUE_OR_URL_EXPRESSION);
88-
if (!match) {
89-
continue;
90-
}
91-
92-
const parsed = parseIssueExpressionOutput(match);
93-
if (!parsed) {
94-
continue;
95-
}
96-
97-
// Get the issue
98-
try {
99-
const issue = await getIssue(this.stateManager, folderManager, match[0], parsed);
100-
if (issue && issue.isClosed) {
101-
// Find the position of the issue reference
102-
const issueIndex = lineText.indexOf(match[0]);
103-
if (issueIndex !== -1) {
104-
const range = new vscode.Range(
105-
new vscode.Position(lineNumber, issueIndex),
106-
new vscode.Position(lineNumber, issueIndex + match[0].length)
107-
);
108-
109-
const diagnostic = new vscode.Diagnostic(
110-
range,
111-
vscode.l10n.t('Issue #{0} is closed. Consider removing this TODO comment.', issue.number),
112-
vscode.DiagnosticSeverity.Warning
113-
);
114-
diagnostic.source = 'GitHub Issues';
115-
diagnostics.push(diagnostic);
116-
}
117-
}
118-
} catch (error) {
119-
// Silently ignore errors fetching issues
120-
}
121-
}
122-
123-
this.diagnosticCollection.set(document.uri, diagnostics);
124-
}
125-
}
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { StateManager } from './stateManager';
8+
import { getIssue } from './util';
9+
import { CREATE_ISSUE_TRIGGERS, ISSUES_SETTINGS_NAMESPACE } from '../common/settingKeys';
10+
import { escapeRegExp } from '../common/utils';
11+
import { RepositoriesManager } from '../github/repositoriesManager';
12+
import { ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput } from '../github/utils';
13+
14+
export class IssueTodoDiagnosticProvider {
15+
private diagnosticCollection: vscode.DiagnosticCollection;
16+
private expression: RegExp | undefined;
17+
private validationTimeouts: Map<string, NodeJS.Timeout> = new Map();
18+
19+
constructor(
20+
context: vscode.ExtensionContext,
21+
private manager: RepositoriesManager,
22+
private stateManager: StateManager,
23+
) {
24+
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('github-issues-todo');
25+
context.subscriptions.push(this.diagnosticCollection);
26+
27+
context.subscriptions.push(
28+
vscode.workspace.onDidChangeConfiguration(e => {
29+
if (e.affectsConfiguration(ISSUES_SETTINGS_NAMESPACE)) {
30+
this.updateTriggers();
31+
// Re-validate all open documents when triggers change
32+
vscode.workspace.textDocuments.forEach(document => {
33+
this.scheduleValidation(document);
34+
});
35+
}
36+
}),
37+
);
38+
39+
context.subscriptions.push(
40+
vscode.workspace.onDidOpenTextDocument(document => {
41+
this.scheduleValidation(document);
42+
}),
43+
);
44+
45+
context.subscriptions.push(
46+
vscode.workspace.onDidChangeTextDocument(e => {
47+
this.scheduleValidation(e.document);
48+
}),
49+
);
50+
51+
context.subscriptions.push(
52+
vscode.workspace.onDidCloseTextDocument(document => {
53+
this.cancelValidation(document);
54+
this.diagnosticCollection.delete(document.uri);
55+
}),
56+
);
57+
58+
this.updateTriggers();
59+
60+
// Validate all currently open documents
61+
vscode.workspace.textDocuments.forEach(document => {
62+
this.scheduleValidation(document);
63+
});
64+
}
65+
66+
private updateTriggers() {
67+
const triggers = vscode.workspace.getConfiguration(ISSUES_SETTINGS_NAMESPACE).get(CREATE_ISSUE_TRIGGERS, []);
68+
this.expression = triggers.length > 0 ? new RegExp(triggers.map(trigger => escapeRegExp(trigger)).join('|')) : undefined;
69+
}
70+
71+
private scheduleValidation(document: vscode.TextDocument): void {
72+
// Skip validation for non-file schemes and certain document types
73+
if (document.uri.scheme !== 'file' && document.uri.scheme !== 'untitled') {
74+
return;
75+
}
76+
77+
const key = document.uri.toString();
78+
79+
// Cancel any existing timeout for this document
80+
this.cancelValidation(document);
81+
82+
// Schedule validation with a small delay to avoid excessive validation
83+
const timeout = setTimeout(() => {
84+
this.validationTimeouts.delete(key);
85+
this.validateDocument(document);
86+
}, 500);
87+
88+
this.validationTimeouts.set(key, timeout);
89+
}
90+
91+
private cancelValidation(document: vscode.TextDocument): void {
92+
const key = document.uri.toString();
93+
const timeout = this.validationTimeouts.get(key);
94+
if (timeout) {
95+
clearTimeout(timeout);
96+
this.validationTimeouts.delete(key);
97+
}
98+
}
99+
100+
private async validateDocument(document: vscode.TextDocument): Promise<void> {
101+
if (!this.expression) {
102+
return;
103+
}
104+
105+
const folderManager = this.manager.getManagerForFile(document.uri);
106+
if (!folderManager) {
107+
return;
108+
}
109+
110+
const diagnostics: vscode.Diagnostic[] = [];
111+
112+
for (let lineNumber = 0; lineNumber < document.lineCount; lineNumber++) {
113+
const line = document.lineAt(lineNumber);
114+
const lineText = line.text;
115+
116+
// Check if line contains a TODO trigger
117+
if (!this.expression.test(lineText)) {
118+
continue;
119+
}
120+
121+
// Look for issue references on this line
122+
const match = lineText.match(ISSUE_OR_URL_EXPRESSION);
123+
if (!match) {
124+
continue;
125+
}
126+
127+
const parsed = parseIssueExpressionOutput(match);
128+
if (!parsed) {
129+
continue;
130+
}
131+
132+
// Get the issue
133+
try {
134+
const issue = await getIssue(this.stateManager, folderManager, match[0], parsed);
135+
if (issue && issue.isClosed) {
136+
// Find the position of the issue reference
137+
const issueIndex = lineText.indexOf(match[0]);
138+
if (issueIndex !== -1) {
139+
const range = new vscode.Range(
140+
new vscode.Position(lineNumber, issueIndex),
141+
new vscode.Position(lineNumber, issueIndex + match[0].length)
142+
);
143+
144+
const diagnostic = new vscode.Diagnostic(
145+
range,
146+
vscode.l10n.t('Issue #{0} is closed. Consider removing this TODO comment.', issue.number),
147+
vscode.DiagnosticSeverity.Warning
148+
);
149+
diagnostic.source = 'GitHub Issues';
150+
diagnostics.push(diagnostic);
151+
}
152+
}
153+
} catch (error) {
154+
// Silently ignore errors fetching issues
155+
}
156+
}
157+
158+
this.diagnosticCollection.set(document.uri, diagnostics);
159+
}
160+
}

0 commit comments

Comments
 (0)