From c3e4504edd9e01e6d39bfebebe6a84c15ef896a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:15:56 +0000 Subject: [PATCH 1/4] Initial plan From b857fb3067afe3e6ad25acf68b775659dcfe9a97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:22:34 +0000 Subject: [PATCH 2/4] Update renameTool and usagesTool: skip registration when no providers, add language names to userDescription Co-authored-by: jrieken <1794099+jrieken@users.noreply.github.com> --- .../contrib/chat/browser/tools/renameTool.ts | 24 +++++++++++++------ .../contrib/chat/browser/tools/usagesTool.ts | 23 +++++++++++++----- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts b/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts index fef06150ff0a7..41b158c96aac9 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts @@ -15,6 +15,7 @@ import { TextEdit } from '../../../../../editor/common/languages.js'; import { IBulkEditService, ResourceTextEdit } from '../../../../../editor/browser/services/bulkEditService.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { rename } from '../../../../../editor/contrib/rename/browser/rename.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -53,6 +54,7 @@ export class RenameTool extends Disposable implements IToolImpl { constructor( @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ILanguageService private readonly _languageService: ILanguageService, @ITextModelService private readonly _textModelService: ITextModelService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IChatService private readonly _chatService: IChatService, @@ -67,26 +69,31 @@ export class RenameTool extends Disposable implements IToolImpl { )((() => this._onDidUpdateToolData.fire()))); } - getToolData(): IToolData { + getToolData(): IToolData | undefined { const languageIds = this._languageFeaturesService.renameProvider.registeredLanguageIds; + if (!languageIds.has('*') && languageIds.size === 0) { + return undefined; + } + let modelDescription = BaseModelDescription; + let userDescription: string; if (languageIds.has('*')) { modelDescription += '\n\nSupported for all languages.'; - } else if (languageIds.size > 0) { + userDescription = localize('tool.rename.userDescription', 'Rename a symbol across the workspace'); + } else { const sorted = [...languageIds].sort(); modelDescription += `\n\nCurrently supported for: ${sorted.join(', ')}.`; - } else { - modelDescription += '\n\nNo languages currently have rename providers registered.'; + const niceNames = sorted.map(id => this._languageService.getLanguageName(id) ?? id); + userDescription = localize('tool.rename.userDescriptionWithLanguages', 'Rename a symbol across the workspace ({0})', niceNames.join(', ')); } - return { id: RenameToolId, toolReferenceName: 'rename', canBeReferencedInPrompt: false, icon: ThemeIcon.fromId(Codicon.rename.id), displayName: localize('tool.rename.displayName', 'Rename Symbol'), - userDescription: localize('tool.rename.userDescription', 'Rename a symbol across the workspace'), + userDescription, modelDescription, source: ToolDataSource.Internal, when: ContextKeyExpr.has('config.chat.tools.renameTool.enabled'), @@ -251,9 +258,12 @@ export class RenameToolContribution extends Disposable implements IWorkbenchCont let registration: IDisposable | undefined; const registerRenameTool = () => { registration?.dispose(); + registration = undefined; toolsService.flushToolUpdates(); const toolData = renameTool.getToolData(); - registration = toolsService.registerTool(toolData, renameTool); + if (toolData) { + registration = toolsService.registerTool(toolData, renameTool); + } }; registerRenameTool(); this._store.add(renameTool.onDidUpdateToolData(registerRenameTool)); diff --git a/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts b/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts index 075977bc799a1..99420104957e4 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts @@ -18,6 +18,7 @@ import { Location, LocationLink } from '../../../../../editor/common/languages.j import { IModelService } from '../../../../../editor/common/services/model.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getReferencesAtPosition } from '../../../../../editor/contrib/gotoSymbol/browser/goToSymbol.js'; import { localize } from '../../../../../nls.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -50,6 +51,7 @@ export class UsagesTool extends Disposable implements IToolImpl { constructor( @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService, @ISearchService private readonly _searchService: ISearchService, @ITextModelService private readonly _textModelService: ITextModelService, @@ -64,17 +66,23 @@ export class UsagesTool extends Disposable implements IToolImpl { )((() => this._onDidUpdateToolData.fire()))); } - getToolData(): IToolData { + getToolData(): IToolData | undefined { const languageIds = this._languageFeaturesService.referenceProvider.registeredLanguageIds; + if (!languageIds.has('*') && languageIds.size === 0) { + return undefined; + } + let modelDescription = BaseModelDescription; + let userDescription: string; if (languageIds.has('*')) { modelDescription += '\n\nSupported for all languages.'; - } else if (languageIds.size > 0) { + userDescription = localize('tool.usages.userDescription', 'Find references, definitions, and implementations of a symbol'); + } else { const sorted = [...languageIds].sort(); modelDescription += `\n\nCurrently supported for: ${sorted.join(', ')}.`; - } else { - modelDescription += '\n\nNo languages currently have reference providers registered.'; + const niceNames = sorted.map(id => this._languageService.getLanguageName(id) ?? id); + userDescription = localize('tool.usages.userDescriptionWithLanguages', 'Find references, definitions, and implementations of a symbol ({0})', niceNames.join(', ')); } return { @@ -83,7 +91,7 @@ export class UsagesTool extends Disposable implements IToolImpl { canBeReferencedInPrompt: false, icon: ThemeIcon.fromId(Codicon.references.id), displayName: localize('tool.usages.displayName', 'List Code Usages'), - userDescription: localize('tool.usages.userDescription', 'Find references, definitions, and implementations of a symbol'), + userDescription, modelDescription, source: ToolDataSource.Internal, when: ContextKeyExpr.has('config.chat.tools.usagesTool.enabled'), @@ -320,9 +328,12 @@ export class UsagesToolContribution extends Disposable implements IWorkbenchCont let registration: IDisposable | undefined; const registerUsagesTool = () => { registration?.dispose(); + registration = undefined; toolsService.flushToolUpdates(); const toolData = usagesTool.getToolData(); - registration = toolsService.registerTool(toolData, usagesTool); + if (toolData) { + registration = toolsService.registerTool(toolData, usagesTool); + } }; registerUsagesTool(); this._store.add(usagesTool.onDidUpdateToolData(registerUsagesTool)); From 1026d57b9311f7e71012611c5da792f90d2f38ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:36:33 +0000 Subject: [PATCH 3/4] Simplify empty-set check: size === 0 is sufficient Co-authored-by: jrieken <1794099+jrieken@users.noreply.github.com> --- src/vs/workbench/contrib/chat/browser/tools/renameTool.ts | 2 +- src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts b/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts index 41b158c96aac9..0ddb27fa1ffcf 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/renameTool.ts @@ -72,7 +72,7 @@ export class RenameTool extends Disposable implements IToolImpl { getToolData(): IToolData | undefined { const languageIds = this._languageFeaturesService.renameProvider.registeredLanguageIds; - if (!languageIds.has('*') && languageIds.size === 0) { + if (languageIds.size === 0) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts b/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts index 99420104957e4..daa5a2bd8fec4 100644 --- a/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts +++ b/src/vs/workbench/contrib/chat/browser/tools/usagesTool.ts @@ -69,7 +69,7 @@ export class UsagesTool extends Disposable implements IToolImpl { getToolData(): IToolData | undefined { const languageIds = this._languageFeaturesService.referenceProvider.registeredLanguageIds; - if (!languageIds.has('*') && languageIds.size === 0) { + if (languageIds.size === 0) { return undefined; } From 35f486e711d8d0ada43659b9a9aad0f5dc12a08e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:44:48 +0000 Subject: [PATCH 4/4] Fix compile errors in renameTool.test.ts and usagesTool.test.ts Co-authored-by: jrieken <1794099+jrieken@users.noreply.github.com> --- .../chat/test/browser/tools/renameTool.test.ts | 16 ++++++++++------ .../chat/test/browser/tools/usagesTool.test.ts | 17 ++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/browser/tools/renameTool.test.ts b/src/vs/workbench/contrib/chat/test/browser/tools/renameTool.test.ts index 42226127e20d6..fabf66ac855a7 100644 --- a/src/vs/workbench/contrib/chat/test/browser/tools/renameTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/tools/renameTool.test.ts @@ -12,10 +12,11 @@ import { RenameProvider, WorkspaceEdit, Rejection } from '../../../../../../edit import { IMarkdownString } from '../../../../../../base/common/htmlContent.js'; import { LanguageFeaturesService } from '../../../../../../editor/common/services/languageFeaturesService.js'; import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; +import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../../../platform/workspace/common/workspace.js'; import { IBulkEditService, IBulkEditResult } from '../../../../../../editor/browser/services/bulkEditService.js'; -import { RenameTool, RenameToolId } from '../../../browser/tools/renameTool.js'; +import { RenameTool } from '../../../browser/tools/renameTool.js'; import { IChatService } from '../../../common/chatService/chatService.js'; import { IToolInvocation, IToolResult, IToolResultTextPart, ToolProgress } from '../../../common/tools/languageModelToolsService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; @@ -100,9 +101,14 @@ suite('RenameTool', () => { const noopCountTokens = async () => 0; const noopProgress: ToolProgress = { report() { } }; + function createMockLanguageService(): ILanguageService { + return { getLanguageName: (id: string) => id } as unknown as ILanguageService; + } + function createTool(textModelService: ITextModelService, options?: { bulkEditService?: IBulkEditService }): RenameTool { return new RenameTool( langFeatures, + createMockLanguageService(), textModelService, createMockWorkspaceService(), createMockChatService(), @@ -124,9 +130,7 @@ suite('RenameTool', () => { test('reports no providers when none registered', () => { const tool = disposables.add(createTool(createMockTextModelService(null!))); - const data = tool.getToolData(); - assert.strictEqual(data.id, RenameToolId); - assert.ok(data.modelDescription.includes('No languages currently have rename providers')); + assert.strictEqual(tool.getToolData(), undefined); }); test('lists registered language ids', () => { @@ -136,7 +140,7 @@ suite('RenameTool', () => { provideRenameEdits: () => ({ edits: [] }), })); const data = tool.getToolData(); - assert.ok(data.modelDescription.includes('typescript')); + assert.ok(data?.modelDescription.includes('typescript')); }); test('reports all languages for wildcard', () => { @@ -145,7 +149,7 @@ suite('RenameTool', () => { provideRenameEdits: () => ({ edits: [] }), })); const data = tool.getToolData(); - assert.ok(data.modelDescription.includes('all languages')); + assert.ok(data?.modelDescription.includes('all languages')); }); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/tools/usagesTool.test.ts b/src/vs/workbench/contrib/chat/test/browser/tools/usagesTool.test.ts index e0e20ec03f629..28c14f3b1ad24 100644 --- a/src/vs/workbench/contrib/chat/test/browser/tools/usagesTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/tools/usagesTool.test.ts @@ -13,10 +13,11 @@ import { ITextModel } from '../../../../../../editor/common/model.js'; import { LanguageFeaturesService } from '../../../../../../editor/common/services/languageFeaturesService.js'; import { IModelService } from '../../../../../../editor/common/services/model.js'; import { ITextModelService } from '../../../../../../editor/common/services/resolverService.js'; +import { ILanguageService } from '../../../../../../editor/common/languages/language.js'; import { createTextModel } from '../../../../../../editor/test/common/testTextModel.js'; import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../../../platform/workspace/common/workspace.js'; import { FileMatch, ISearchComplete, ISearchService, ITextQuery, OneLineRange, TextSearchMatch } from '../../../../../services/search/common/search.js'; -import { UsagesTool, UsagesToolId } from '../../../browser/tools/usagesTool.js'; +import { UsagesTool } from '../../../browser/tools/usagesTool.js'; import { IToolInvocation, IToolResult, IToolResultTextPart, ToolProgress } from '../../../common/tools/languageModelToolsService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; @@ -91,8 +92,12 @@ suite('UsagesTool', () => { const noopCountTokens = async () => 0; const noopProgress: ToolProgress = { report() { } }; + function createMockLanguageService(): ILanguageService { + return { getLanguageName: (id: string) => id } as unknown as ILanguageService; + } + function createTool(textModelService: ITextModelService, workspaceService: IWorkspaceContextService, options?: { modelService?: IModelService; searchService?: ISearchService }): UsagesTool { - return new UsagesTool(langFeatures, options?.modelService ?? createMockModelService(), options?.searchService ?? createMockSearchService(), textModelService, workspaceService); + return new UsagesTool(langFeatures, createMockLanguageService(), options?.modelService ?? createMockModelService(), options?.searchService ?? createMockSearchService(), textModelService, workspaceService); } setup(() => { @@ -109,9 +114,7 @@ suite('UsagesTool', () => { test('reports no providers when none registered', () => { const tool = disposables.add(createTool(createMockTextModelService(null!), createMockWorkspaceService())); - const data = tool.getToolData(); - assert.strictEqual(data.id, UsagesToolId); - assert.ok(data.modelDescription.includes('No languages currently have reference providers')); + assert.strictEqual(tool.getToolData(), undefined); }); test('lists registered language ids', () => { @@ -119,14 +122,14 @@ suite('UsagesTool', () => { const tool = disposables.add(createTool(createMockTextModelService(model), createMockWorkspaceService())); disposables.add(langFeatures.referenceProvider.register('typescript', { provideReferences: () => [] })); const data = tool.getToolData(); - assert.ok(data.modelDescription.includes('typescript')); + assert.ok(data?.modelDescription.includes('typescript')); }); test('reports all languages for wildcard', () => { const tool = disposables.add(createTool(createMockTextModelService(null!), createMockWorkspaceService())); disposables.add(langFeatures.referenceProvider.register('*', { provideReferences: () => [] })); const data = tool.getToolData(); - assert.ok(data.modelDescription.includes('all languages')); + assert.ok(data?.modelDescription.includes('all languages')); }); });