From ee8f89f6145ff7202e30b80280adc50b156f46e5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 18 Sep 2023 16:50:05 -0700 Subject: [PATCH] Allow cmd+c to copy chat row content (#193410) * Allow cmd+c to copy chat row content Fix microsoft/vscode-copilot#1433 * Add onDidBlur listener --- .../chat/browser/actions/chatCopyActions.ts | 16 ++++++++++++++-- .../workbench/contrib/chat/browser/chatWidget.ts | 7 ++++++- .../contrib/chat/common/chatContextKeys.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index a2e7aa52b62..8899be6939b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CONTEXT_IN_CHAT_LIST } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export function registerChatCopyActions() { @@ -57,14 +60,23 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyC, + when: CONTEXT_IN_CHAT_LIST } }); } run(accessor: ServicesAccessor, ...args: any[]) { - const item = args[0]; + let item = args[0]; if (!isRequestVM(item) && !isResponseVM(item)) { - return; + const widgetService = accessor.get(IChatWidgetService); + item = widgetService.lastFocusedWidget?.getFocus(); + if (!isRequestVM(item) && !isResponseVM(item)) { + return; + } } const clipboardService = accessor.get(IClipboardService); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 91c15635f2e..7948c9dd8fc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -25,7 +25,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_LIST, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; @@ -119,6 +119,8 @@ export class ChatWidget extends Disposable implements IChatWidget { private lastSlashCommands: ISlashCommand[] | undefined; private slashCommandsPromise: Promise | undefined; + private readonly chatListFocused: IContextKey; + constructor( readonly viewContext: IChatWidgetViewContext, private readonly styles: IChatWidgetStyles, @@ -132,6 +134,7 @@ export class ChatWidget extends Disposable implements IChatWidget { ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); + this.chatListFocused = CONTEXT_IN_CHAT_LIST.bindTo(contextKeyService); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); this._register((chatWidgetService as ChatWidgetService).register(this)); @@ -351,7 +354,9 @@ export class ChatWidget extends Disposable implements IChatWidget { })); this._register(this.tree.onDidFocus(() => { this._onDidFocus.fire(); + this.chatListFocused.set(this.tree.isDOMFocused()); })); + this._register(this.tree.onDidBlur(() => this.chatListFocused.set(false))); } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 631f0af7d00..95dd8d6e825 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -17,5 +17,6 @@ export const CONTEXT_REQUEST = new RawContextKey('chatRequest', false, export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); +export const CONTEXT_IN_CHAT_LIST = new RawContextKey('chatListFocused', false, { type: 'boolean', description: localize('chatListFocused', "True when a row of the chat list is focused, but not when focus is on a different element inside the chat row.") }); export const CONTEXT_PROVIDER_EXISTS = new RawContextKey('hasChatProvider', false, { type: 'boolean', description: localize('hasChatProvider', "True when some chat provider has been registered.") });