From c437ea427de036eb73db03417b51ade54d622d10 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 27 Mar 2025 14:06:23 -0700 Subject: [PATCH 1/2] Show placeholder welcome view for the unified Edits view --- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../browser/chatParticipant.contribution.ts | 185 ++++++++++++++---- 2 files changed, 151 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 581292c263460..18790669a58db 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -465,7 +465,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - mode = validateChatMode(mode) ?? ChatMode.Ask; + mode = validateChatMode(mode) ?? (this.location === ChatAgentLocation.Panel ? ChatMode.Ask : ChatMode.Edit); if (mode === ChatMode.Agent && !this.agentService.hasToolsAgent) { mode = ChatMode.Edit; } diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 604883bf16bb7..87ef57767b2aa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -11,26 +11,35 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableMap, DisposableStore } from '../../../../base/common/lifecycle.js'; import * as strings from '../../../../base/common/strings.js'; +import { URI } from '../../../../base/common/uri.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { localize, localize2 } from '../../../../nls.js'; -import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ViewPane } from '../../../browser/parts/views/viewPane.js'; import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; -import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js'; import { Extensions, IExtensionFeaturesRegistry, IExtensionFeatureTableRenderer, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js'; import { isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import * as extensionsRegistry from '../../../services/extensions/common/extensionsRegistry.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showExtensionsWithIdsCommandId } from '../../extensions/browser/extensionsActions.js'; import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js'; +import { IChatService } from '../common/chatService.js'; import { ChatAgentLocation, ChatConfiguration } from '../common/constants.js'; -import { ChatViewId } from './chat.js'; +import { ChatViewId, showChatView } from './chat.js'; import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js'; // --- Chat Container & View Registration @@ -87,38 +96,6 @@ const editsViewContainer: ViewContainer = Registry.as(V order: 101, }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); -const editsViewDescriptor: IViewDescriptor[] = [{ - id: 'workbench.panel.chat.view.edits', - containerIcon: editsViewContainer.icon, - containerTitle: editsViewContainer.title.value, - singleViewPaneContainerTitle: editsViewContainer.title.value, - name: editsViewContainer.title, - canToggleVisibility: false, - canMoveView: true, - openCommandActionDescriptor: { - id: CHAT_EDITING_SIDEBAR_PANEL_ID, - title: editsViewContainer.title, - mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), - keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, - linux: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI - } - }, - order: 2 - }, - ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), - when: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${ChatConfiguration.UnifiedChatView}`).negate(), - ContextKeyExpr.or( - ChatContextKeys.Setup.hidden.negate(), - ChatContextKeys.Setup.installed, - ChatContextKeys.editingParticipantRegistered - ) - ) -}]; -Registry.as(ViewExtensions.ViewsRegistry).registerViews(editsViewDescriptor, editsViewContainer); - const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatParticipants', jsonSchema: { @@ -454,3 +431,141 @@ Registry.as(Extensions.ExtensionFeaturesRegistry).re }, renderer: new SyncDescriptor(ChatParticipantDataRenderer), }); + +// TODO@roblourens remove after a few months + +export class MovedChatEditsViewPane extends ViewPane { + override shouldShowWelcome(): boolean { + return true; + } +} + +const editsViewId = 'workbench.panel.chat.view.edits'; +const baseEditsViewDescriptor: IViewDescriptor = { + id: editsViewId, + containerIcon: editsViewContainer.icon, + containerTitle: editsViewContainer.title.value, + singleViewPaneContainerTitle: editsViewContainer.title.value, + name: editsViewContainer.title, + canToggleVisibility: false, + canMoveView: true, + openCommandActionDescriptor: { + id: CHAT_EDITING_SIDEBAR_PANEL_ID, + title: editsViewContainer.title, + mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), + keybindings: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, + linux: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI + } + }, + order: 2 + }, + ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), + when: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${ChatConfiguration.UnifiedChatView}`).negate(), + ContextKeyExpr.or( + ChatContextKeys.Setup.hidden.negate(), + ChatContextKeys.Setup.installed, + ChatContextKeys.editingParticipantRegistered + ) + ) +}; + +const ShowMovedChatEditsView = new RawContextKey('showMovedChatEditsView', true, { type: 'boolean', description: localize('hideMovedChatEditsView', "True when the moved chat edits view should be hidden.") }); + +class EditsViewContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatEditsView'; + + private static readonly HideMovedEditsViewKey = 'chatEditsView.hideMovedEditsView'; + + private readonly showWelcomeViewCtx = ShowMovedChatEditsView.bindTo(this.contextKeyService); + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IChatService private readonly chatService: IChatService, + ) { + super(); + + const unifiedViewEnabled = this.configurationService.getValue(ChatConfiguration.UnifiedChatView); + + const movedEditsViewDescriptor = { + ...baseEditsViewDescriptor, + ctorDescriptor: new SyncDescriptor(MovedChatEditsViewPane), + when: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${ChatConfiguration.UnifiedChatView}`), + ShowMovedChatEditsView, + ContextKeyExpr.or( + ChatContextKeys.Setup.hidden.negate(), + ChatContextKeys.Setup.installed, + ChatContextKeys.editingParticipantRegistered + ) + ) + }; + + const editsViewToRegister = unifiedViewEnabled ? + movedEditsViewDescriptor : baseEditsViewDescriptor; + + if (unifiedViewEnabled) { + this.init(); + this.updateContextKey(); + this.registerWelcomeView(); + this.registerCommands(); + } + Registry.as(ViewExtensions.ViewsRegistry).registerViews([editsViewToRegister], editsViewContainer); + } + + private registerWelcomeView(): void { + const welcomeViewMainMessage = localize('editsMovedMainMessage', "Copilot Edits has been moved to the [main Chat view](command:workbench.action.chat.open). You can switch between modes by using the dropdown in the Chat input box."); + const okButton = `[${localize('ok', "Got it")}](command:_movedEditsView.ok)`; + const welcomeViewFooterMessage = localize('editsMovedFooterMessage', "[Learn more](command:_movedEditsView.learnMore) about the Chat view."); + + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this._register(viewsRegistry.registerViewWelcomeContent(editsViewId, { + content: [welcomeViewMainMessage, okButton, welcomeViewFooterMessage].join('\n\n'), + renderSecondaryButtons: true, + when: ShowMovedChatEditsView + })); + } + + private markViewToHide(): void { + this.storageService.store(EditsViewContribution.HideMovedEditsViewKey, true, StorageScope.APPLICATION, StorageTarget.USER); + this.updateContextKey(); + } + + private init() { + const hasChats = this.chatService.hasSessions(); + if (!hasChats) { + // No chats from previous sessions, might be a new user, so hide the view. + // Could also be a previous user who happened to first open a workspace with no chats. + this.markViewToHide(); + } + } + + private updateContextKey(): void { + const hidden = this.storageService.getBoolean(EditsViewContribution.HideMovedEditsViewKey, StorageScope.APPLICATION, false); + const hasChats = this.chatService.hasSessions(); + this.showWelcomeViewCtx.set(!hidden && hasChats); + } + + private registerCommands(): void { + this._register(CommandsRegistry.registerCommand({ + id: '_movedEditsView.ok', + handler: async (accessor: ServicesAccessor) => { + showChatView(accessor.get(IViewsService)); + this.markViewToHide(); + } + })); + this._register(CommandsRegistry.registerCommand({ + id: '_movedEditsView.learnMore', + handler: async (accessor: ServicesAccessor) => { + const openerService = accessor.get(IOpenerService); + openerService.open(URI.parse('https://aka.ms/vscode-chat-modes')); + } + })); + } +} + +registerWorkbenchContribution2(EditsViewContribution.ID, EditsViewContribution, WorkbenchPhase.BlockRestore); From f5068b2045de6ad84c4c874a4bb6a8aecf5aa27d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 27 Mar 2025 16:35:15 -0700 Subject: [PATCH 2/2] Fix build --- .../contrib/chat/browser/chatParticipant.contribution.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 87ef57767b2aa..e794b9ee88ff3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -16,7 +16,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js import { localize, localize2 } from '../../../../nls.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ExtensionIdentifier, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -479,7 +479,7 @@ class EditsViewContribution extends Disposable implements IWorkbenchContribution private static readonly HideMovedEditsViewKey = 'chatEditsView.hideMovedEditsView'; - private readonly showWelcomeViewCtx = ShowMovedChatEditsView.bindTo(this.contextKeyService); + private readonly showWelcomeViewCtx: IContextKey; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -489,6 +489,8 @@ class EditsViewContribution extends Disposable implements IWorkbenchContribution ) { super(); + this.showWelcomeViewCtx = ShowMovedChatEditsView.bindTo(this.contextKeyService); + const unifiedViewEnabled = this.configurationService.getValue(ChatConfiguration.UnifiedChatView); const movedEditsViewDescriptor = {