diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 7743e7ed2ad..c2cf71bdc4a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -13,6 +13,7 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { INLINE_CHAT_ID } from '../../../inlineChat/common/inlineChat.js'; +import { TerminalContribCommandId } from '../../../terminal/terminalContribExports.js'; import { ChatContextKeyExprs, ChatContextKeys } from '../../common/chatContextKeys.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js'; import { IChatWidgetService } from '../chat.js'; @@ -81,8 +82,8 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'age content.push(localize('workbench.action.chat.previousUserPrompt', 'To navigate to the previous user prompt in the conversation, invoke the Previous User Prompt command{0}.', '')); content.push(localize('workbench.action.chat.announceConfirmation', 'To focus pending chat confirmation dialogs, invoke the Focus Chat Confirmation Status command{0}.', '')); content.push(localize('chat.showHiddenTerminals', 'If there are any hidden chat terminals, you can view them by invoking the View Hidden Chat Terminals command{0}.', '')); - content.push(localize('chat.focusMostRecentTerminal', 'To focus the last chat terminal that ran a tool, invoke the Focus Most Recent Chat Terminal command{0}.', '')); - content.push(localize('chat.focusMostRecentTerminalOutput', 'To focus the output from the last chat terminal tool, invoke the Focus Most Recent Chat Terminal Output command{0}.', '')); + content.push(localize('chat.focusMostRecentTerminal', 'To focus the last chat terminal that ran a tool, invoke the Focus Most Recent Chat Terminal command{0}.', ``)); + content.push(localize('chat.focusMostRecentTerminalOutput', 'To focus the output from the last chat terminal tool, invoke the Focus Most Recent Chat Terminal Output command{0}.', ``)); if (type === 'panelChat') { content.push(localize('workbench.action.chat.newChat', 'To create a new chat session, invoke the New Chat command{0}.', '')); } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index e17e6487abe..ffd78ae9466 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -31,7 +31,7 @@ import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/m import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { IPreferencesService } from '../../../../../services/preferences/common/preferences.js'; import { ITerminalChatService } from '../../../../terminal/browser/terminal.js'; -import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; +import { TerminalContribCommandId, TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; import { ChatContextKeys } from '../../../common/chatContextKeys.js'; import { IChatToolInvocation, ToolConfirmKind, type IChatTerminalToolInvocationData, type ILegacyChatTerminalToolInvocationData } from '../../../common/chatService.js'; @@ -43,7 +43,6 @@ import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatCo import { EditorPool } from '../chatContentCodePools.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart } from '../chatMarkdownContentPart.js'; -import { disableSessionAutoApprovalCommandId, openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; export const enum TerminalToolConfirmationStorageKeys { @@ -280,13 +279,13 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS await this.configurationService.updateValue(TerminalContribSettingId.AutoApprove, newValue, ConfigurationTarget.USER); function formatRuleLinks(newRules: ITerminalNewAutoApproveRule[]): string { return newRules.map(e => { - const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, ConfigurationTarget.USER); + const settingsUri = createCommandUri(TerminalContribCommandId.OpenTerminalSettingsLink, ConfigurationTarget.USER); return `[\`${e.key}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; }).join(', '); } const mdTrustSettings = { isTrusted: { - enabledCommands: [openTerminalSettingsLinkCommandId] + enabledCommands: [TerminalContribCommandId.OpenTerminalSettingsLink] } }; if (newRules.length === 1) { @@ -308,10 +307,10 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS case 'sessionApproval': { const sessionId = this.context.element.sessionId; this.terminalChatService.setChatSessionAutoApproval(sessionId, true); - const disableUri = createCommandUri(disableSessionAutoApprovalCommandId, sessionId); + const disableUri = createCommandUri(TerminalContribCommandId.DisableSessionAutoApproval, sessionId); const mdTrustSettings = { isTrusted: { - enabledCommands: [disableSessionAutoApprovalCommandId] + enabledCommands: [TerminalContribCommandId.DisableSessionAutoApproval] } }; terminalData.autoApproveInfo = new MarkdownString(`${localize('sessionApproval', 'All commands will be auto approved for this session')} ([${localize('sessionApproval.disable', 'Disable')}](${disableUri.toString()}))`, mdTrustSettings); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index ac4e12cc8d6..78968ca21e8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -5,11 +5,8 @@ import { h } from '../../../../../../base/browser/dom.js'; import { ActionBar } from '../../../../../../base/browser/ui/actionbar/actionbar.js'; -import { Codicon } from '../../../../../../base/common/codicons.js'; -import { KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js'; import { isMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IPreferencesService, type IOpenSettingsOptions } from '../../../../../services/preferences/common/preferences.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; import { IChatToolInvocation, IChatToolInvocationSerialized, type IChatMarkdownContent, type IChatTerminalToolInvocationData, type ILegacyChatTerminalToolInvocationData } from '../../../common/chatService.js'; import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js'; @@ -20,14 +17,9 @@ import { ChatMarkdownContentPart, type IChatMarkdownContentPartOptions } from '. import { ChatProgressSubPart } from '../chatProgressContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import '../media/chatTerminalToolProgressPart.css'; -import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; -import { ConfigurationTarget } from '../../../../../../platform/configuration/common/configuration.js'; import type { ICodeBlockRenderOptions } from '../../codeBlockPart.js'; -import { ChatConfiguration, CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES } from '../../../common/constants.js'; -import { CommandsRegistry } from '../../../../../../platform/commands/common/commands.js'; -import { MenuId, MenuRegistry } from '../../../../../../platform/actions/common/actions.js'; +import { CHAT_TERMINAL_OUTPUT_MAX_PREVIEW_LINES } from '../../../common/constants.js'; import { IChatTerminalToolProgressPart, ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../../terminal/browser/terminal.js'; -import { Action, IAction } from '../../../../../../base/common/actions.js'; import { Disposable, MutableDisposable, toDisposable, type IDisposable } from '../../../../../../base/common/lifecycle.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; @@ -36,7 +28,6 @@ import * as dom from '../../../../../../base/browser/dom.js'; import { DomScrollableElement } from '../../../../../../base/browser/ui/scrollbar/scrollableElement.js'; import { ScrollbarVisibility } from '../../../../../../base/common/scrollable.js'; import { localize } from '../../../../../../nls.js'; -import { TerminalLocation } from '../../../../../../platform/terminal/common/terminal.js'; import { ITerminalCommand, TerminalCapability, type ICommandDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IHoverService } from '../../../../../../platform/hover/browser/hover.js'; @@ -50,8 +41,13 @@ import { IContextKey, IContextKeyService } from '../../../../../../platform/cont import { AccessibilityVerbositySettingId } from '../../../../accessibility/browser/accessibilityConfiguration.js'; import { ChatContextKeys } from '../../../common/chatContextKeys.js'; import { EditorPool } from '../chatContentCodePools.js'; -import { KeybindingWeight, KeybindingsRegistry } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js'; +import { TerminalLocation } from '../../../../../../platform/terminal/common/terminal.js'; +import { Action, IAction } from '../../../../../../base/common/actions.js'; +import { Codicon } from '../../../../../../base/common/codicons.js'; +import { TerminalContribCommandId } from '../../../../terminal/terminalContribExports.js'; +import { ITelemetryService } from '../../../../../../platform/telemetry/common/telemetry.js'; + const MAX_TERMINAL_OUTPUT_PREVIEW_HEIGHT = 200; @@ -969,108 +965,16 @@ class ChatTerminalToolOutputSection extends Disposable { } } -export const focusMostRecentChatTerminalCommandId = 'workbench.action.chat.focusMostRecentChatTerminal'; -export const focusMostRecentChatTerminalOutputCommandId = 'workbench.action.chat.focusMostRecentChatTerminalOutput'; - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: focusMostRecentChatTerminalCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ChatContextKeys.inChatSession, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyT, - handler: async (accessor: ServicesAccessor) => { - const terminalChatService = accessor.get(ITerminalChatService); - const part = terminalChatService.getMostRecentProgressPart(); - if (!part) { - return; - } - await part.focusTerminal(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: focusMostRecentChatTerminalOutputCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ChatContextKeys.inChatSession, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyO, - handler: async (accessor: ServicesAccessor) => { - const terminalChatService = accessor.get(ITerminalChatService); - const part = terminalChatService.getMostRecentProgressPart(); - if (!part) { - return; - } - await part.toggleOutputFromKeyboard(); - } -}); - -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: focusMostRecentChatTerminalCommandId, - title: localize('chat.focusMostRecentTerminal', 'Chat: Focus Most Recent Terminal'), - }, - when: ChatContextKeys.inChatSession -}); - -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: focusMostRecentChatTerminalOutputCommandId, - title: localize('chat.focusMostRecentTerminalOutput', 'Chat: Focus Most Recent Terminal Output'), - }, - when: ChatContextKeys.inChatSession -}); - -export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink'; -export const disableSessionAutoApprovalCommandId = '_chat.disableSessionAutoApproval'; - -CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (accessor, scopeRaw: string) => { - const preferencesService = accessor.get(IPreferencesService); - - if (scopeRaw === 'global') { - preferencesService.openSettings({ - query: `@id:${ChatConfiguration.GlobalAutoApprove}` - }); - } else { - const scope = parseInt(scopeRaw); - const target = !isNaN(scope) ? scope as ConfigurationTarget : undefined; - const options: IOpenSettingsOptions = { - jsonEditor: true, - revealSetting: { - key: TerminalContribSettingId.AutoApprove - } - }; - switch (target) { - case ConfigurationTarget.APPLICATION: preferencesService.openApplicationSettings(options); break; - case ConfigurationTarget.USER: - case ConfigurationTarget.USER_LOCAL: preferencesService.openUserSettings(options); break; - case ConfigurationTarget.USER_REMOTE: preferencesService.openRemoteSettings(options); break; - case ConfigurationTarget.WORKSPACE: - case ConfigurationTarget.WORKSPACE_FOLDER: preferencesService.openWorkspaceSettings(options); break; - default: { - // Fallback if something goes wrong - preferencesService.openSettings({ - target: ConfigurationTarget.USER, - query: `@id:${TerminalContribSettingId.AutoApprove}`, - }); - break; - } - } - } -}); - -CommandsRegistry.registerCommand(disableSessionAutoApprovalCommandId, async (accessor, chatSessionId: string) => { - const terminalChatService = accessor.get(ITerminalChatService); - terminalChatService.setChatSessionAutoApproval(chatSessionId, false); -}); - - -class ToggleChatTerminalOutputAction extends Action implements IAction { +export class ToggleChatTerminalOutputAction extends Action implements IAction { private _expanded = false; constructor( private readonly _toggle: () => Promise, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { super( - 'chat.showTerminalOutput', + TerminalContribCommandId.ToggleChatTerminalOutput, localize('showTerminalOutput', 'Show Output'), ThemeIcon.asClassName(Codicon.chevronRight), true, @@ -1079,6 +983,18 @@ class ToggleChatTerminalOutputAction extends Action implements IAction { } public override async run(): Promise { + type ToggleChatTerminalOutputTelemetryEvent = { + previousExpanded: boolean; + }; + + type ToggleChatTerminalOutputTelemetryClassification = { + owner: 'meganrogge'; + comment: 'Track usage of the toggle chat terminal output action.'; + previousExpanded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the terminal output was expanded before the toggle.' }; + }; + this._telemetryService.publicLog2('terminal/chatToggleOutput', { + previousExpanded: this._expanded + }); await this._toggle(); } @@ -1103,7 +1019,7 @@ class ToggleChatTerminalOutputAction extends Action implements IAction { } private _updateTooltip(): void { - const keybinding = this._keybindingService.lookupKeybinding(focusMostRecentChatTerminalOutputCommandId); + const keybinding = this._keybindingService.lookupKeybinding(TerminalContribCommandId.FocusMostRecentChatTerminalOutput); const label = keybinding?.getLabel(); this.tooltip = label ? `${this.label} (${label})` : this.label; } @@ -1120,9 +1036,10 @@ export class FocusChatInstanceAction extends Action implements IAction { @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { super( - 'chat.focusTerminalInstance', + TerminalContribCommandId.FocusChatInstanceAction, isTerminalHidden ? localize('showTerminal', 'Show and Focus Terminal') : localize('focusTerminal', 'Focus Terminal'), ThemeIcon.asClassName(Codicon.openInProduct), true, @@ -1133,6 +1050,32 @@ export class FocusChatInstanceAction extends Action implements IAction { public override async run() { this.label = localize('focusTerminal', 'Focus Terminal'); this._updateTooltip(); + + let target: FocusChatInstanceTelemetryEvent['target'] = 'none'; + let location: FocusChatInstanceTelemetryEvent['location'] = 'panel'; + if (this._instance) { + target = 'instance'; + location = this._instance.target === TerminalLocation.Editor ? 'editor' : 'panel'; + } else if (this._commandUri) { + target = 'commandUri'; + } + + type FocusChatInstanceTelemetryEvent = { + target: 'instance' | 'commandUri' | 'none'; + location: 'panel' | 'editor'; + }; + + type FocusChatInstanceTelemetryClassification = { + owner: 'meganrogge'; + comment: 'Track usage of the focus chat terminal action.'; + target: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether focusing targeted an existing instance or opened a command URI.' }; + location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Location of the terminal instance when focusing.' }; + }; + this._telemetryService.publicLog2('terminal/chatFocusInstance', { + target, + location + }); + if (this._instance) { this._terminalService.setActiveInstance(this._instance); if (this._instance.target === TerminalLocation.Editor) { @@ -1174,7 +1117,7 @@ export class FocusChatInstanceAction extends Action implements IAction { } private _updateTooltip(): void { - const keybinding = this._keybindingService.lookupKeybinding(focusMostRecentChatTerminalCommandId); + const keybinding = this._keybindingService.lookupKeybinding(TerminalContribCommandId.FocusMostRecentChatTerminal); const label = keybinding?.getLabel(); this.tooltip = label ? `${this.label} (${label})` : this.label; } diff --git a/src/vs/workbench/contrib/terminal/terminalContribExports.ts b/src/vs/workbench/contrib/terminal/terminalContribExports.ts index 2bf09dc3431..a8fce413d4f 100644 --- a/src/vs/workbench/contrib/terminal/terminalContribExports.ts +++ b/src/vs/workbench/contrib/terminal/terminalContribExports.ts @@ -7,6 +7,7 @@ import type { IConfigurationNode } from '../../../platform/configuration/common/ import { TerminalAccessibilityCommandId, defaultTerminalAccessibilityCommandsToSkipShell } from '../terminalContrib/accessibility/common/terminal.accessibility.js'; import { terminalAccessibilityConfiguration } from '../terminalContrib/accessibility/common/terminalAccessibilityConfiguration.js'; import { terminalAutoRepliesConfiguration } from '../terminalContrib/autoReplies/common/terminalAutoRepliesConfiguration.js'; +import { TerminalChatCommandId } from '../terminalContrib/chat/browser/terminalChat.js'; import { terminalInitialHintConfiguration } from '../terminalContrib/chat/common/terminalInitialHintConfiguration.js'; import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.js'; import { terminalCommandGuideConfiguration } from '../terminalContrib/commandGuide/common/terminalCommandGuideConfiguration.js'; @@ -25,6 +26,12 @@ import { terminalZoomConfiguration } from '../terminalContrib/zoom/common/termin export const enum TerminalContribCommandId { A11yFocusAccessibleBuffer = TerminalAccessibilityCommandId.FocusAccessibleBuffer, DeveloperRestartPtyHost = TerminalDeveloperCommandId.RestartPtyHost, + OpenTerminalSettingsLink = TerminalChatCommandId.OpenTerminalSettingsLink, + DisableSessionAutoApproval = TerminalChatCommandId.DisableSessionAutoApproval, + FocusMostRecentChatTerminalOutput = TerminalChatCommandId.FocusMostRecentChatTerminalOutput, + FocusMostRecentChatTerminal = TerminalChatCommandId.FocusMostRecentChatTerminal, + ToggleChatTerminalOutput = TerminalChatCommandId.ToggleChatTerminalOutput, + FocusChatInstanceAction = TerminalChatCommandId.FocusChatInstanceAction, } // HACK: Export some settings from `terminalContrib/` that are depended upon elsewhere. These are diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 04f19f20ecd..44561ce3ae5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -19,6 +19,12 @@ export const enum TerminalChatCommandId { ViewInChat = 'workbench.action.terminal.chat.viewInChat', RerunRequest = 'workbench.action.terminal.chat.rerunRequest', ViewHiddenChatTerminals = 'workbench.action.terminal.chat.viewHiddenChatTerminals', + OpenTerminalSettingsLink = 'workbench.action.terminal.chat.openTerminalSettingsLink', + DisableSessionAutoApproval = 'workbench.action.terminal.chat.disableSessionAutoApproval', + FocusMostRecentChatTerminalOutput = 'workbench.action.terminal.chat.focusMostRecentChatTerminalOutput', + FocusMostRecentChatTerminal = 'workbench.action.terminal.chat.focusMostRecentChatTerminal', + ToggleChatTerminalOutput = 'workbench.action.terminal.chat.toggleChatTerminalOutput', + FocusChatInstanceAction = 'workbench.action.terminal.chat.focusChatInstance', } export const MENU_TERMINAL_CHAT_WIDGET_INPUT_SIDE_TOOLBAR = MenuId.for('terminalChatWidget'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index dbeb4afe478..275e89998a2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -5,15 +5,15 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; -import { localize2 } from '../../../../../nls.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ChatViewId, IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatContextKeys } from '../../../chat/common/chatContextKeys.js'; import { IChatService } from '../../../chat/common/chatService.js'; import { LocalChatSessionUri } from '../../../chat/common/chatUri.js'; -import { ChatAgentLocation } from '../../../chat/common/constants.js'; +import { ChatAgentLocation, ChatConfiguration } from '../../../chat/common/constants.js'; import { AbstractInline1ChatAction } from '../../../inlineChat/browser/inlineChatActions.js'; import { isDetachedTerminalInstance, ITerminalChatService, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from '../../../terminal/browser/terminal.js'; import { registerActiveXtermAction } from '../../../terminal/browser/terminalActions.js'; @@ -26,6 +26,10 @@ import { getIconId } from '../../../terminal/browser/terminalIcon.js'; import { TerminalChatController } from './terminalChatController.js'; import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { isString } from '../../../../../base/common/types.js'; +import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; +import { IPreferencesService, IOpenSettingsOptions } from '../../../../services/preferences/common/preferences.js'; +import { ConfigurationTarget } from '../../../../../platform/configuration/common/configuration.js'; +import { TerminalChatAgentToolsSettingId } from '../../chatAgentTools/common/terminalChatAgentToolsConfiguration.js'; registerActiveXtermAction({ id: TerminalChatCommandId.Start, @@ -425,3 +429,94 @@ registerAction2(class ShowChatTerminalsAction extends Action2 { qp.show(); } }); + + + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TerminalChatCommandId.FocusMostRecentChatTerminal, + weight: KeybindingWeight.WorkbenchContrib, + when: ChatContextKeys.inChatSession, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyT, + handler: async (accessor: ServicesAccessor) => { + const terminalChatService = accessor.get(ITerminalChatService); + const part = terminalChatService.getMostRecentProgressPart(); + if (!part) { + return; + } + await part.focusTerminal(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TerminalChatCommandId.FocusMostRecentChatTerminalOutput, + weight: KeybindingWeight.WorkbenchContrib, + when: ChatContextKeys.inChatSession, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyO, + handler: async (accessor: ServicesAccessor) => { + const terminalChatService = accessor.get(ITerminalChatService); + const part = terminalChatService.getMostRecentProgressPart(); + if (!part) { + return; + } + await part.toggleOutputFromKeyboard(); + } +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: TerminalChatCommandId.FocusMostRecentChatTerminal, + title: localize('chat.focusMostRecentTerminal', 'Chat: Focus Most Recent Terminal'), + }, + when: ChatContextKeys.inChatSession +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: TerminalChatCommandId.FocusMostRecentChatTerminalOutput, + title: localize('chat.focusMostRecentTerminalOutput', 'Chat: Focus Most Recent Terminal Output'), + }, + when: ChatContextKeys.inChatSession +}); + + +CommandsRegistry.registerCommand(TerminalChatCommandId.OpenTerminalSettingsLink, async (accessor, scopeRaw: string) => { + const preferencesService = accessor.get(IPreferencesService); + + if (scopeRaw === 'global') { + preferencesService.openSettings({ + query: `@id:${ChatConfiguration.GlobalAutoApprove}` + }); + } else { + const scope = parseInt(scopeRaw); + const target = !isNaN(scope) ? scope as ConfigurationTarget : undefined; + const options: IOpenSettingsOptions = { + jsonEditor: true, + revealSetting: { + key: TerminalChatAgentToolsSettingId.AutoApprove, + } + }; + switch (target) { + case ConfigurationTarget.APPLICATION: preferencesService.openApplicationSettings(options); break; + case ConfigurationTarget.USER: + case ConfigurationTarget.USER_LOCAL: preferencesService.openUserSettings(options); break; + case ConfigurationTarget.USER_REMOTE: preferencesService.openRemoteSettings(options); break; + case ConfigurationTarget.WORKSPACE: + case ConfigurationTarget.WORKSPACE_FOLDER: preferencesService.openWorkspaceSettings(options); break; + default: { + // Fallback if something goes wrong + preferencesService.openSettings({ + target: ConfigurationTarget.USER, + query: `@id:${TerminalChatAgentToolsSettingId.AutoApprove}`, + }); + break; + } + } + + } +}); + +CommandsRegistry.registerCommand(TerminalChatCommandId.DisableSessionAutoApproval, async (accessor, chatSessionId: string) => { + const terminalChatService = accessor.get(ITerminalChatService); + terminalChatService.setChatSessionAutoApproval(chatSessionId, false); +}); + diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts index 066391005f7..cb8181c67df 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts @@ -13,7 +13,6 @@ import { IInstantiationService } from '../../../../../../../platform/instantiati import { ITerminalChatService } from '../../../../../terminal/browser/terminal.js'; import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; -import { openTerminalSettingsLinkCommandId } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.js'; import { ChatConfiguration } from '../../../../../chat/common/constants.js'; import type { ToolConfirmationAction } from '../../../../../chat/common/languageModelToolsService.js'; import { TerminalChatAgentToolsSettingId } from '../../../common/terminalChatAgentToolsConfiguration.js'; @@ -22,6 +21,7 @@ import { dedupeRules, generateAutoApproveActions, isPowerShell } from '../../run import type { RunInTerminalToolTelemetry } from '../../runInTerminalToolTelemetry.js'; import { type TreeSitterCommandParser } from '../../treeSitterCommandParser.js'; import type { ICommandLineAnalyzer, ICommandLineAnalyzerOptions, ICommandLineAnalyzerResult } from './commandLineAnalyzer.js'; +import { TerminalChatCommandId } from '../../../../chat/browser/terminalChat.js'; const promptInjectionWarningCommandsLower = [ 'curl', @@ -53,10 +53,10 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma async analyze(options: ICommandLineAnalyzerOptions): Promise { if (options.chatSessionId && this._terminalChatService.hasChatSessionAutoApproval(options.chatSessionId)) { this._log('Session has auto approval enabled, auto approving command'); - const disableUri = createCommandUri('_chat.disableSessionAutoApproval', options.chatSessionId); + const disableUri = createCommandUri(TerminalChatCommandId.DisableSessionAutoApproval, options.chatSessionId); const mdTrustSettings = { isTrusted: { - enabledCommands: ['_chat.disableSessionAutoApproval'] + enabledCommands: [TerminalChatCommandId.DisableSessionAutoApproval] } }; return { @@ -191,21 +191,21 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma ): IMarkdownString | undefined { const formatRuleLinks = (result: SingleOrMany<{ result: ICommandApprovalResult; rule?: IAutoApproveRule; reason: string }>): string => { return asArray(result).map(e => { - const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, e.rule!.sourceTarget); + const settingsUri = createCommandUri(TerminalChatCommandId.OpenTerminalSettingsLink, e.rule!.sourceTarget); return `[\`${e.rule!.sourceText}\`](${settingsUri.toString()} "${localize('ruleTooltip', 'View rule in settings')}")`; }).join(', '); }; const mdTrustSettings = { isTrusted: { - enabledCommands: [openTerminalSettingsLinkCommandId] + enabledCommands: [TerminalChatCommandId.OpenTerminalSettingsLink] } }; const config = this._configurationService.inspect>(ChatConfiguration.GlobalAutoApprove); const isGlobalAutoApproved = config?.value ?? config.defaultValue; if (isGlobalAutoApproved) { - const settingsUri = createCommandUri(openTerminalSettingsLinkCommandId, 'global'); + const settingsUri = createCommandUri(TerminalChatCommandId.OpenTerminalSettingsLink, 'global'); return new MarkdownString(`${localize('autoApprove.global', 'Auto approved by setting {0}', `[\`${ChatConfiguration.GlobalAutoApprove}\`](${settingsUri.toString()} "${localize('ruleTooltip.global', 'View settings')}")`)}`, mdTrustSettings); }