From 951289f9411e73b7feefbb8aeb874b5c6b3f97ea Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Oct 2025 08:57:22 +0200 Subject: [PATCH] =?UTF-8?q?Show=20=E2=9C=A8=20for=20rename=20suggestions?= =?UTF-8?q?=20OOTB=20(fix=20#270489)=20(#270490)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/rename/browser/renameWidget.ts | 2 +- .../contrib/chat/browser/chatSetup.ts | 141 ++++++++++++------ 2 files changed, 99 insertions(+), 44 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index 340d9dc2e83..f2bc9d613f2 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -930,7 +930,7 @@ class InputWithButton implements IDisposable { this._buttonNode.className = 'rename-suggestions-button'; this._buttonNode.setAttribute('tabindex', '0'); - this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate new name suggestions"); + this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate New Name Suggestions"); this._buttonCancelHoverText = nls.localize('cancelRenameSuggestionsButton', "Cancel"); this._buttonHoverContent = this._buttonGenHoverText; this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 503be92d784..6af48705f3c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -76,6 +76,10 @@ import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js'; import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { chatViewsWelcomeRegistry } from './viewsWelcome/chatViewsWelcome.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { NewSymbolName, NewSymbolNameTriggerKind } from '../../../../editor/common/languages.js'; +import { ITextModel } from '../../../../editor/common/model.js'; +import { IRange } from '../../../../editor/common/core/range.js'; const defaultChat = { extensionId: product.defaultChatAgent?.extensionId ?? '', @@ -145,7 +149,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { }); } - static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): { disposable: IDisposable } { + static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): IDisposable { return instantiationService.invokeFunction(accessor => { const chatAgentService = accessor.get(IChatAgentService); @@ -174,9 +178,9 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { canBeReferencedInPrompt: true, toolReferenceName: 'new', when: ContextKeyExpr.true(), - }).disposable); + })); - return { disposable: disposables }; + return disposables; }); } @@ -454,7 +458,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { let result: IChatSetupResult | undefined = undefined; try { result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ - disableChatViewReveal: true, // we are already in a chat context + disableChatViewReveal: true, // we are already in a chat context forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively }); } catch (error) { @@ -583,7 +587,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { class SetupTool extends Disposable implements IToolImpl { - static registerTool(instantiationService: IInstantiationService, toolData: IToolData): { tool: SetupTool; disposable: IDisposable } { + static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable { return instantiationService.invokeFunction(accessor => { const toolService = accessor.get(ILanguageModelToolsService); @@ -592,7 +596,7 @@ class SetupTool extends Disposable implements IToolImpl { const tool = instantiationService.createInstance(SetupTool); disposables.add(toolService.registerTool(toolData, tool)); - return { tool, disposable: disposables }; + return disposables; }); } @@ -614,6 +618,41 @@ class SetupTool extends Disposable implements IToolImpl { } } +class DefaultNewSymbolNamesProvider extends Disposable { + + static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy): IDisposable { + return instantiationService.invokeFunction(accessor => { + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + + const disposables = new DisposableStore(); + + const provider = disposables.add(instantiationService.createInstance(DefaultNewSymbolNamesProvider, context, controller)); + disposables.add(languageFeaturesService.newSymbolNamesProvider.register('*', provider)); + + return disposables; + }); + } + + constructor( + private readonly context: ChatEntitlementContext, + private readonly controller: Lazy, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService, + ) { + super(); + } + + async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise { + await this.instantiationService.invokeFunction(accessor => { + return ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({ + forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined + }); + }); + + return []; + } +} + enum ChatSetupStrategy { Canceled = 0, DefaultSetup = 1, @@ -886,53 +925,69 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr return; // TODO@bpasero eventually remove this when we figured out extension activation issues } - const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload - const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); - const updateRegistration = () => { - if (!context.state.hidden && !context.state.disabled) { - // Default Agents (always, even if installed to allow for speedy requests right on startup) - if (!defaultAgentDisposables.value) { - const disposables = defaultAgentDisposables.value = new DisposableStore(); + // Agent + Tools + { + const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload + const vscodeAgentDisposables = markAsSingleton(new MutableDisposable()); + if (!context.state.hidden && !context.state.disabled) { - // Panel Agents - const panelAgentDisposables = disposables.add(new DisposableStore()); - for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) { - const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller); - panelAgentDisposables.add(disposable); - panelAgentDisposables.add(agent.onUnresolvableError(() => { - const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); - if (panelAgentHasGuidance) { - // An unresolvable error from our agent registrations means that - // Copilot is unhealthy for some reason. We clear our panel - // registration to give Copilot a chance to show a custom message - // to the user from the views and stop pretending as if there was - // a functional agent. - this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.'); - panelAgentDisposables.dispose(); - } - })); + // Default Agents (always, even if installed to allow for speedy requests right on startup) + if (!defaultAgentDisposables.value) { + const disposables = defaultAgentDisposables.value = new DisposableStore(); + + // Panel Agents + const panelAgentDisposables = disposables.add(new DisposableStore()); + for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) { + const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller); + panelAgentDisposables.add(disposable); + panelAgentDisposables.add(agent.onUnresolvableError(() => { + const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when)); + if (panelAgentHasGuidance) { + // An unresolvable error from our agent registrations means that + // Copilot is unhealthy for some reason. We clear our panel + // registration to give Copilot a chance to show a custom message + // to the user from the views and stop pretending as if there was + // a functional agent. + this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.'); + panelAgentDisposables.dispose(); + } + })); + } + + // Inline Agents + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); + disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable); } - // Inline Agents - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable); - disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable); + // Built-In Agent + Tool (unless installed, signed-in and enabled) + if ((!context.state.installed || context.state.entitlement === ChatEntitlement.Unknown || context.state.entitlement === ChatEntitlement.Unresolved) && !vscodeAgentDisposables.value) { + const disposables = vscodeAgentDisposables.value = new DisposableStore(); + disposables.add(SetupAgent.registerBuiltInAgents(this.instantiationService, context, controller)); + } + } else { + defaultAgentDisposables.clear(); + vscodeAgentDisposables.clear(); } - // Built-In Agent + Tool (unless installed, signed-in and enabled) - if ((!context.state.installed || context.state.entitlement === ChatEntitlement.Unknown || context.state.entitlement === ChatEntitlement.Unresolved) && !vscodeAgentDisposables.value) { - const disposables = vscodeAgentDisposables.value = new DisposableStore(); - disposables.add(SetupAgent.registerBuiltInAgents(this.instantiationService, context, controller).disposable); + if (context.state.installed && !context.state.disabled) { + vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list } - } else { - defaultAgentDisposables.clear(); - vscodeAgentDisposables.clear(); } - if (context.state.installed && !context.state.disabled) { - vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list + // Rename Provider + { + const renameProviderDisposables = markAsSingleton(new MutableDisposable()); + + if (!context.state.installed && !context.state.hidden && !context.state.disabled) { + if (!renameProviderDisposables.value) { + renameProviderDisposables.value = DefaultNewSymbolNamesProvider.registerProvider(this.instantiationService, context, controller); + } + } else { + renameProviderDisposables.clear(); + } } };