Skip to content

Commit 008c532

Browse files
Copilotjoshspicer
andcommitted
Implement CodeLens provider for TODO comments
Co-authored-by: joshspicer <23246594+joshspicer@users.noreply.github.com>
1 parent d48e4ff commit 008c532

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

src/issues/issueFeatureRegistrar.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,8 +575,12 @@ export class IssueFeatureRegistrar extends Disposable {
575575
this._register(
576576
vscode.languages.registerHoverProvider('*', new UserHoverProvider(this.manager, this.telemetry)),
577577
);
578+
const todoProvider = new IssueTodoProvider(this.context, this.copilotRemoteAgentManager);
578579
this._register(
579-
vscode.languages.registerCodeActionsProvider('*', new IssueTodoProvider(this.context, this.copilotRemoteAgentManager), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }),
580+
vscode.languages.registerCodeActionsProvider('*', todoProvider, { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }),
581+
);
582+
this._register(
583+
vscode.languages.registerCodeLensProvider('*', todoProvider),
580584
);
581585
});
582586
}

src/issues/issueTodoProvider.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { escapeRegExp } from '../common/utils';
1010
import { CopilotRemoteAgentManager } from '../github/copilotRemoteAgent';
1111
import { ISSUE_OR_URL_EXPRESSION } from '../github/utils';
1212

13-
export class IssueTodoProvider implements vscode.CodeActionProvider {
13+
export class IssueTodoProvider implements vscode.CodeActionProvider, vscode.CodeLensProvider {
1414
private expression: RegExp | undefined;
1515

1616
constructor(
@@ -87,4 +87,50 @@ export class IssueTodoProvider implements vscode.CodeActionProvider {
8787
} while (range.end.line >= lineNumber);
8888
return codeActions;
8989
}
90+
91+
provideCodeLenses(
92+
document: vscode.TextDocument,
93+
_token: vscode.CancellationToken,
94+
): vscode.CodeLens[] {
95+
if (this.expression === undefined) {
96+
return [];
97+
}
98+
99+
const codeLenses: vscode.CodeLens[] = [];
100+
for (let lineNumber = 0; lineNumber < document.lineCount; lineNumber++) {
101+
const line = document.lineAt(lineNumber).text;
102+
const truncatedLine = line.substring(0, MAX_LINE_LENGTH);
103+
const matches = truncatedLine.match(ISSUE_OR_URL_EXPRESSION);
104+
if (!matches) {
105+
const match = truncatedLine.match(this.expression);
106+
const search = match?.index ?? -1;
107+
if (search >= 0 && match) {
108+
const range = new vscode.Range(lineNumber, search, lineNumber, search + match[0].length);
109+
const indexOfWhiteSpace = truncatedLine.substring(search).search(/\s/);
110+
const insertIndex =
111+
search +
112+
(indexOfWhiteSpace > 0 ? indexOfWhiteSpace : truncatedLine.match(this.expression)![0].length);
113+
114+
// Create GitHub Issue CodeLens
115+
const createIssueCodeLens = new vscode.CodeLens(range, {
116+
title: vscode.l10n.t('Create GitHub Issue'),
117+
command: 'issue.createIssueFromSelection',
118+
arguments: [{ document, lineNumber, line, insertIndex, range }],
119+
});
120+
codeLenses.push(createIssueCodeLens);
121+
122+
// Delegate to coding agent CodeLens (if copilot manager is available)
123+
if (this.copilotRemoteAgentManager) {
124+
const startAgentCodeLens = new vscode.CodeLens(range, {
125+
title: vscode.l10n.t('Delegate to coding agent'),
126+
command: 'issue.startCodingAgentFromTodo',
127+
arguments: [{ document, lineNumber, line, insertIndex, range }],
128+
});
129+
codeLenses.push(startAgentCodeLens);
130+
}
131+
}
132+
}
133+
}
134+
return codeLenses;
135+
}
90136
}

src/test/issues/issueTodoProvider.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,40 @@ describe('IssueTodoProvider', function () {
4242
assert.strictEqual(createIssueAction?.command?.command, 'issue.createIssueFromSelection');
4343
assert.strictEqual(startAgentAction?.command?.command, 'issue.startCodingAgentFromTodo');
4444
});
45+
46+
it('should provide code lenses for TODO comments', async function () {
47+
const mockContext = {
48+
subscriptions: []
49+
} as any as vscode.ExtensionContext;
50+
51+
const mockCopilotManager = {} as any; // Mock CopilotRemoteAgentManager
52+
53+
const provider = new IssueTodoProvider(mockContext, mockCopilotManager);
54+
55+
// Create a mock document with TODO comment
56+
const document = {
57+
lineAt: (line: number) => ({
58+
text: line === 1 ? ' // TODO: Fix this' : 'function test() {}'
59+
}),
60+
lineCount: 4
61+
} as vscode.TextDocument;
62+
63+
const codeLenses = provider.provideCodeLenses(document, new vscode.CancellationTokenSource().token);
64+
65+
assert.strictEqual(codeLenses.length, 2);
66+
67+
// Verify the code lenses
68+
const createIssueLens = codeLenses.find(cl => cl.command?.title === 'Create GitHub Issue');
69+
const startAgentLens = codeLenses.find(cl => cl.command?.title === 'Delegate to coding agent');
70+
71+
assert.ok(createIssueLens, 'Should have Create GitHub Issue CodeLens');
72+
assert.ok(startAgentLens, 'Should have Delegate to coding agent CodeLens');
73+
74+
assert.strictEqual(createIssueLens?.command?.command, 'issue.createIssueFromSelection');
75+
assert.strictEqual(startAgentLens?.command?.command, 'issue.startCodingAgentFromTodo');
76+
77+
// Verify the range points to the TODO text
78+
assert.strictEqual(createIssueLens?.range.start.line, 1);
79+
assert.strictEqual(startAgentLens?.range.start.line, 1);
80+
});
4581
});

0 commit comments

Comments
 (0)