From bac1926029c174998664be4a0ae977e2e787481d Mon Sep 17 00:00:00 2001 From: Russell Davis <551404+russelldavis@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:30:41 -0700 Subject: [PATCH 001/753] Fix decreaseIndentPattern for javascript and typescript Fixes #201424 It wasn't matching closing parens, which resulted in these issues: * Pressing enter with just a closing paren to the right of the caret wouldn't result in a dedent on the next line * With the caret at the start of the line below a line containing only a closing paren, pressing tab would result in an extra level of indentation --- extensions/javascript/javascript-language-configuration.json | 2 +- extensions/typescript-basics/language-configuration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json index 4029985233a..12f6e5cac1f 100644 --- a/extensions/javascript/javascript-language-configuration.json +++ b/extensions/javascript/javascript-language-configuration.json @@ -111,7 +111,7 @@ }, "indentationRules": { "decreaseIndentPattern": { - "pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]].*$" + "pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]\\)].*$" }, "increaseIndentPattern": { "pattern": "^((?!//).)*(\\{([^}\"'`/]*|(\\t|[ ])*//.*)|\\([^)\"'`/]*|\\[[^\\]\"'`/]*)$" diff --git a/extensions/typescript-basics/language-configuration.json b/extensions/typescript-basics/language-configuration.json index 03f06fa04d5..45657928dbc 100644 --- a/extensions/typescript-basics/language-configuration.json +++ b/extensions/typescript-basics/language-configuration.json @@ -129,7 +129,7 @@ }, "indentationRules": { "decreaseIndentPattern": { - "pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]].*$" + "pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]\\)].*$" }, "increaseIndentPattern": { "pattern": "^((?!//).)*(\\{([^}\"'`/]*|(\\t|[ ])*//.*)|\\([^)\"'`/]*|\\[[^\\]\"'`/]*)$" From dad5b331eaee2d4e7f87061b412097c21e98ef81 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:07:17 -0600 Subject: [PATCH 002/753] add basics --- src/vs/platform/terminal/common/terminal.ts | 4 + .../terminal/browser/media/terminal.css | 11 ++ .../contrib/terminal/common/terminal.ts | 4 + .../terminal/common/terminalContextKey.ts | 17 ++ .../contrib/terminal/terminal.all.ts | 1 + .../browser/terminal.chat.contribution.ts | 160 ++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 76 +++++++++ 7 files changed, 273 insertions(+) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a189..56ebf9ddae5 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -121,6 +121,10 @@ export const enum TerminalSettingId { StickyScrollEnabled = 'terminal.integrated.stickyScroll.enabled', StickyScrollMaxLineCount = 'terminal.integrated.stickyScroll.maxLineCount', MouseWheelZoom = 'terminal.integrated.mouseWheelZoom', + FocusChat = 'workbench.action.terminal.focusChat', + HideChat = 'workbench.action.terminal.hideChat', + SubmitChat = 'workbench.action.terminal.submitChat', + CancelChat = 'workbench.action.terminal.cancelChat', // Debug settings that are hidden from user diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 488815cf258..a6ea80d22d6 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,3 +565,14 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } + +.monaco-workbench .terminal-chat-widget { + z-index: 33 !important; + position: absolute; + bottom: 30px; + left: 10px; +} + +.monaco-workbench .terminal-chat-widget.hide { + visibility: hidden; +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index fc925b646bb..0579628b521 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,6 +500,10 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', + FocusChat = 'workbench.action.terminal.focusChat', + HideChat = 'workbench.action.terminal.hideChat', + SubmitChat = 'workbench.action.terminal.submitChat', + CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 72335480aee..1de53d61930 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -39,6 +39,10 @@ export const enum TerminalContextKeyStrings { ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', + ChatFocus = 'terminalChatFocus', + ChatVisible = 'terminalChatVisible', + ChatSessionInProgress = 'terminalChatSessionInProgress', + ChatInputHasText = 'terminalChatInputHasText', } export namespace TerminalContextKeys { @@ -158,4 +162,17 @@ export namespace TerminalContextKeys { ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') ) ); + + + /** Whether the chat widget is focused */ + export const chatFocused = new RawContextKey(TerminalContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + + /** Whether the chat widget is visible */ + export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + + /** Whether a chat session is in progress */ + export const chatSessionInProgress = new RawContextKey(TerminalContextKeyStrings.ChatSessionInProgress, false, localize('chatSessionInProgressContextKey', "Whether a chat session is in progress.")); + + /** Whether the chat input has text */ + export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); } diff --git a/src/vs/workbench/contrib/terminal/terminal.all.ts b/src/vs/workbench/contrib/terminal/terminal.all.ts index b49aa829f7b..e9cdc253212 100644 --- a/src/vs/workbench/contrib/terminal/terminal.all.ts +++ b/src/vs/workbench/contrib/terminal/terminal.all.ts @@ -17,6 +17,7 @@ import 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.acce import 'vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution'; import 'vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution'; import 'vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution'; +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution'; import 'vs/workbench/contrib/terminalContrib/highlight/browser/terminal.highlight.contribution'; import 'vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution'; import 'vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts new file mode 100644 index 00000000000..bb9cbd24540 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDimension } from 'vs/base/browser/dom'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize2 } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { Codicon } from 'vs/base/common/codicons'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; + +export class TerminalChatContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.Chat'; + + /** + * Currently focused Chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatContribution; + + static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { + return instance.getContribution(TerminalChatContribution.ID); + } + + private _chatWidget: Lazy | undefined; + private _lastLayoutDimensions: IDimension | undefined; + + get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } + + constructor( + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + processManager: ITerminalProcessManager | ITerminalProcessInfo, + widgetManager: TerminalWidgetManager, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(); + } + + layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + this._lastLayoutDimensions = dimension; + this._chatWidget?.rawValue?.layout(dimension.width); + } + + xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + this._chatWidget = new Lazy(() => { + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + + // Track focus and set state so we can force the scroll bar to be visible + chatWidget.onDidFocus(() => { + TerminalChatContribution.activeChatWidget = this; + this._instance.forceScrollbarVisibility(); + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + // chatWidget.onDidBlur(() => { + // TerminalChatContribution.activeChatWidget = undefined; + // this._instance.resetScrollbarVisibility(); + // }); + + if (!this._instance.domElement) { + throw new Error('FindWidget expected terminal DOM to be initialized'); + } + + // this._instance.domElement?.appendChild(chatWidget.getDomNode()); + if (this._lastLayoutDimensions) { + chatWidget.layout(this._lastLayoutDimensions.width); + } + + return chatWidget; + }); + } + + override dispose() { + if (TerminalChatContribution.activeChatWidget === this) { + TerminalChatContribution.activeChatWidget = undefined; + } + super.dispose(); + this._chatWidget?.rawValue?.dispose(); + } +} +registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, true); + +registerActiveXtermAction({ + id: TerminalCommandId.FocusChat, + title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + when: ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.focusInAny), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.reveal(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.HideChat, + title: localize2('workbench.action.terminal.hideChat', 'Terminal: Hide Chat'), + keybinding: { + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.hide(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.SubmitChat, + title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText), + icon: Codicon.send, + menu: { + id: MenuId.ChatExecute, + when: TerminalContextKeys.chatSessionInProgress.negate(), + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.acceptInput(); + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.CancelChat, + title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + precondition: TerminalContextKeys.chatSessionInProgress, + icon: Codicon.debugStop, + menu: { + id: MenuId.ChatExecute, + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.cancel(); + } +}); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts new file mode 100644 index 00000000000..5b44b0a0cf7 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; + +export class TerminalChatWidget extends Disposable { + private _widget: ChatWidget | undefined; + private _scopedInstantiationService: IInstantiationService; + private readonly _onDidFocus = this._register(new Emitter()); + readonly onDidFocus = this._onDidFocus.event; + private _widgetContainer: HTMLElement | undefined; + private _chatWidgetFocused: IContextKey; + private _chatWidgetVisible: IContextKey; + constructor( + private readonly _container: HTMLElement, + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + super(); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + } + reveal(): void { + this._widgetContainer = document.createElement('div'); + this._widgetContainer.classList.add('terminal-chat-widget'); + this._widget = this._register(this._scopedInstantiationService.createInstance( + ChatWidget, + { viewId: 'terminal' }, + { supportsFileReferences: false, renderStyle: 'compact' }, + { + listForeground: editorForeground, + listBackground: editorBackground, + inputEditorBackground: inputBackground, + resultEditorBackground: editorBackground + })); + this._widget.render(this._widgetContainer); + this._container.appendChild(this._widgetContainer); + this._register(this._widget.onDidFocus(() => { + this._onDidFocus.fire(); + this._chatWidgetFocused.set(true); + })); + this._widget.setVisible(true); + this._chatWidgetVisible.set(true); + this._widget.setInput('@terminal'); + this._widget.setInputPlaceholder('Request a terminal command'); + this._widget.focusInput(); + } + hide(): void { + if (this._widgetContainer) { + this._container.removeChild(this._widgetContainer); + } + this._chatWidgetVisible.set(false); + } + cancel(): void { + this._widget?.clear(); + } + acceptInput(): void { + this._widget?.acceptInput(); + } + layout(width: number): void { + this._widget?.layout(40, width); + } +} From 4f219d7a161c595d66dce6e376aa77f6703a5158 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:11:24 -0600 Subject: [PATCH 003/753] fix some issues --- .../browser/terminal.chat.contribution.ts | 36 +++---------------- .../chat/browser/terminalChatWidget.ts | 5 +-- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index bb9cbd24540..0f803200129 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -11,7 +11,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; @@ -25,12 +25,6 @@ import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/br export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; - /** - * Currently focused Chat widget. This is used to track action context since - * 'active terminals' are only tracked for non-detached terminal instanecs. - */ - static activeChatWidget?: TerminalChatContribution; - static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } @@ -59,19 +53,6 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - // Track focus and set state so we can force the scroll bar to be visible - chatWidget.onDidFocus(() => { - TerminalChatContribution.activeChatWidget = this; - this._instance.forceScrollbarVisibility(); - if (!isDetachedTerminalInstance(this._instance)) { - this._terminalService.setActiveInstance(this._instance); - } - }); - // chatWidget.onDidBlur(() => { - // TerminalChatContribution.activeChatWidget = undefined; - // this._instance.resetScrollbarVisibility(); - // }); - if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } @@ -86,9 +67,6 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon } override dispose() { - if (TerminalChatContribution.activeChatWidget === this) { - TerminalChatContribution.activeChatWidget = undefined; - } super.dispose(); this._chatWidget?.rawValue?.dispose(); } @@ -106,8 +84,7 @@ registerActiveXtermAction({ f1: true, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.reveal(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); } }); @@ -123,8 +100,7 @@ registerActiveXtermAction({ f1: true, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.hide(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); } }); @@ -139,8 +115,7 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.acceptInput(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.acceptInput(); } }); @@ -154,7 +129,6 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.cancel(); + TerminalChatContribution.get(activeInstance)?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 5b44b0a0cf7..b6dbcfa67ee 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,8 +15,6 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin export class TerminalChatWidget extends Disposable { private _widget: ChatWidget | undefined; private _scopedInstantiationService: IInstantiationService; - private readonly _onDidFocus = this._register(new Emitter()); - readonly onDidFocus = this._onDidFocus.event; private _widgetContainer: HTMLElement | undefined; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; @@ -49,7 +46,6 @@ export class TerminalChatWidget extends Disposable { this._widget.render(this._widgetContainer); this._container.appendChild(this._widgetContainer); this._register(this._widget.onDidFocus(() => { - this._onDidFocus.fire(); this._chatWidgetFocused.set(true); })); this._widget.setVisible(true); @@ -63,6 +59,7 @@ export class TerminalChatWidget extends Disposable { this._container.removeChild(this._widgetContainer); } this._chatWidgetVisible.set(false); + this._instance.focus(); } cancel(): void { this._widget?.clear(); From ab4a6959f7298fbd463daf364309350e16c40812 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:44:29 -0600 Subject: [PATCH 004/753] add hold for speech --- .../contrib/inlineChat/electron-sandbox/inlineChatActions.ts | 5 +++-- .../chat/browser/terminal.chat.contribution.ts | 4 ++-- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 +--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index d30d230a2b7..ec8c7cd1c47 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -20,16 +20,17 @@ import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/c import { localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class HoldToSpeak extends AbstractInlineChatAction { constructor() { super({ id: 'inlineChat.holdForSpeech', - precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), + precondition: ContextKeyExpr.and(HasSpeechProvider, ContextKeyExpr.or(CTX_INLINE_CHAT_VISIBLE, TerminalContextKeys.chatVisible)), title: localize2('holdForSpeech', "Hold for Speech"), keybinding: { - when: EditorContextKeys.textInputFocus, + when: ContextKeyExpr.or(EditorContextKeys.textInputFocus, TerminalContextKeys.chatFocused), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyI, }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0f803200129..46bef94963c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -39,7 +39,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon processManager: ITerminalProcessManager | ITerminalProcessInfo, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ITerminalService private readonly _terminalService: ITerminalService + @ITerminalService terminalService: ITerminalService ) { super(); } @@ -78,7 +78,7 @@ registerActiveXtermAction({ title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib }, f1: true, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b6dbcfa67ee..cb2db4bb1be 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -45,9 +45,7 @@ export class TerminalChatWidget extends Disposable { })); this._widget.render(this._widgetContainer); this._container.appendChild(this._widgetContainer); - this._register(this._widget.onDidFocus(() => { - this._chatWidgetFocused.set(true); - })); + this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); this._widget.setVisible(true); this._chatWidgetVisible.set(true); this._widget.setInput('@terminal'); From dc4ebd856d49a1ae73b93214f635d755a1f0ad58 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 11:04:23 -0600 Subject: [PATCH 005/753] fix some issues --- .../terminal/browser/media/terminal.css | 3 +-- .../chat/browser/terminalChatWidget.ts | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index a6ea80d22d6..8fc9e6be0eb 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -569,8 +569,7 @@ .monaco-workbench .terminal-chat-widget { z-index: 33 !important; position: absolute; - bottom: 30px; - left: 10px; + top: 10px; } .monaco-workbench .terminal-chat-widget.hide { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index cb2db4bb1be..4f0c787fc6b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -13,9 +13,9 @@ import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contr import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget: ChatWidget | undefined; + private _widget: ChatWidget; private _scopedInstantiationService: IInstantiationService; - private _widgetContainer: HTMLElement | undefined; + private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; constructor( @@ -29,10 +29,9 @@ export class TerminalChatWidget extends Disposable { this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - } - reveal(): void { this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-chat-widget'); + this._container.appendChild(this._widgetContainer); this._widget = this._register(this._scopedInstantiationService.createInstance( ChatWidget, { viewId: 'terminal' }, @@ -44,19 +43,22 @@ export class TerminalChatWidget extends Disposable { resultEditorBackground: editorBackground })); this._widget.render(this._widgetContainer); - this._container.appendChild(this._widgetContainer); this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + } + reveal(): void { + this._widgetContainer.classList.remove('hide'); this._widget.setVisible(true); + this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); this._widget.setInput('@terminal'); this._widget.setInputPlaceholder('Request a terminal command'); this._widget.focusInput(); } hide(): void { - if (this._widgetContainer) { - this._container.removeChild(this._widgetContainer); - } + this._widgetContainer.classList.add('hide'); + this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); + this._widget.clear(); this._instance.focus(); } cancel(): void { @@ -66,6 +68,6 @@ export class TerminalChatWidget extends Disposable { this._widget?.acceptInput(); } layout(width: number): void { - this._widget?.layout(40, width); + this._widget?.layout(100, width < 300 ? 300 : width); } } From eedd668787628d52ff190f60a52a6f49522500b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:23:24 -0800 Subject: [PATCH 006/753] Add setting to disable term inline chat by default --- src/vs/platform/terminal/common/terminal.ts | 5 +-- .../terminal/common/terminalConfiguration.ts | 5 +++ .../browser/terminal.chat.contribution.ts | 33 ++++++++++++++++--- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 56ebf9ddae5..6d291481c7f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -121,10 +121,7 @@ export const enum TerminalSettingId { StickyScrollEnabled = 'terminal.integrated.stickyScroll.enabled', StickyScrollMaxLineCount = 'terminal.integrated.stickyScroll.maxLineCount', MouseWheelZoom = 'terminal.integrated.mouseWheelZoom', - FocusChat = 'workbench.action.terminal.focusChat', - HideChat = 'workbench.action.terminal.hideChat', - SubmitChat = 'workbench.action.terminal.submitChat', - CancelChat = 'workbench.action.terminal.cancelChat', + ExperimentalInlineChat = 'workbench.action.terminal.experimentalInlineChat', // Debug settings that are hidden from user diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ac0903b8ae3..397ff3bc97a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -653,6 +653,11 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, + [TerminalSettingId.ExperimentalInlineChat]: { + markdownDescription: localize('terminal.integrated.experimentalInlineChat', "Whether to enable the upcoming experimental inline terminal chat UI."), + type: 'boolean', + default: false + } } }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 46bef94963c..c0ee3d62f1b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -21,6 +21,8 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { Codicon } from 'vs/base/common/codicons'; import { MenuId } from 'vs/platform/actions/common/actions'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -39,17 +41,27 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon processManager: ITerminalProcessManager | ITerminalProcessInfo, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService terminalService: ITerminalService ) { super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } } layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } this._lastLayoutDimensions = dimension; this._chatWidget?.rawValue?.layout(dimension.width); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); @@ -82,7 +94,10 @@ registerActiveXtermAction({ weight: KeybindingWeight.WorkbenchContrib }, f1: true, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), run: (_xterm, _accessor, activeInstance) => { TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); } @@ -98,7 +113,10 @@ registerActiveXtermAction({ weight: KeybindingWeight.WorkbenchContrib }, f1: true, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), run: (_xterm, _accessor, activeInstance) => { TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); } @@ -107,7 +125,11 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.SubmitChat, title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatInputHasText + ), icon: Codicon.send, menu: { id: MenuId.ChatExecute, @@ -122,7 +144,10 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.CancelChat, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), - precondition: TerminalContextKeys.chatSessionInProgress, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatSessionInProgress, + ), icon: Codicon.debugStop, menu: { id: MenuId.ChatExecute, From 0d202eff3d87ae7cb3e943fb82fa9a98f6aeffcd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 11:46:48 -0600 Subject: [PATCH 007/753] widget per terminal --- .../browser/terminal.chat.contribution.ts | 33 ++++++++++++++----- .../chat/browser/terminalChatWidget.ts | 6 ++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c0ee3d62f1b..b9fab74a3a2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -11,7 +11,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; @@ -30,7 +30,11 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } - + /** + * Currently focused chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatContribution; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; @@ -42,7 +46,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private _configurationService: IConfigurationService, - @ITerminalService terminalService: ITerminalService + @ITerminalService private readonly _terminalService: ITerminalService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -64,7 +68,16 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon } this._chatWidget = new Lazy(() => { const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - + chatWidget.focusTracker.onDidFocus(() => { + TerminalChatContribution.activeChatWidget = this; + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + chatWidget.focusTracker.onDidBlur(() => { + TerminalChatContribution.activeChatWidget = undefined; + this._instance.resetScrollbarVisibility(); + }); if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } @@ -99,7 +112,8 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.reveal(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.reveal(); } }); @@ -118,7 +132,8 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.hide(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.hide(); } }); @@ -137,7 +152,8 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.acceptInput(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.acceptInput(); } }); @@ -154,6 +170,7 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { - TerminalChatContribution.get(activeInstance)?.chatWidget?.cancel(); + const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + contr?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4f0c787fc6b..de48c32fcf3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -18,6 +19,7 @@ export class TerminalChatWidget extends Disposable { private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; + private readonly _focusTracker: IFocusTracker; constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, @@ -44,6 +46,7 @@ export class TerminalChatWidget extends Disposable { })); this._widget.render(this._widgetContainer); this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + this._focusTracker = this._register(trackFocus(this._widgetContainer)); } reveal(): void { this._widgetContainer.classList.remove('hide'); @@ -70,4 +73,7 @@ export class TerminalChatWidget extends Disposable { layout(width: number): void { this._widget?.layout(100, width < 300 ? 300 : width); } + public get focusTracker(): IFocusTracker { + return this._focusTracker; + } } From 10e7518fbe0d4e9da230165470ed87558a70b86d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:57:23 -0800 Subject: [PATCH 008/753] WIP inline chat widget integration --- .../chat/browser/media/chat.css | 13 ++++ .../browser/terminal.chat.contribution.ts | 1 + .../chat/browser/terminalChatWidget.ts | 72 +++++++++++++------ 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css new file mode 100644 index 00000000000..e81dc6b4752 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.terminal-inline-chat { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 100; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c0ee3d62f1b..8881c8f664a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/chat'; import { IDimension } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4f0c787fc6b..76482c5c987 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,61 +4,91 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget: ChatWidget; + private _widget!: ChatWidget; private _scopedInstantiationService: IInstantiationService; private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; private _chatWidgetVisible: IContextKey; + + private readonly _inlineChatWidget: InlineChatWidget; + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); this._widgetContainer = document.createElement('div'); - this._widgetContainer.classList.add('terminal-chat-widget'); + this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); - this._widget = this._register(this._scopedInstantiationService.createInstance( - ChatWidget, - { viewId: 'terminal' }, - { supportsFileReferences: false, renderStyle: 'compact' }, + // this._widget = this._register(this._scopedInstantiationService.createInstance( + // ChatWidget, + // { viewId: 'terminal' }, + // { supportsFileReferences: false, renderStyle: 'compact' }, + // { + // listForeground: editorForeground, + // listBackground: editorBackground, + // inputEditorBackground: inputBackground, + // resultEditorBackground: editorBackground + // })); + // this._widget.render(this._widgetContainer); + // this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + + const fakeParentEditorElement = document.createElement('div'); + + // const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); + // this.setPlaceholderFontStyles(editorConstructionOptions.fontFamily!, editorConstructionOptions.fontSize!, editorConstructionOptions.lineHeight!); + + const fakeParentEditor = this._scopedInstantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + {}, + { isSimpleWidget: true } + ); + + this._inlineChatWidget = this._scopedInstantiationService.createInstance( + InlineChatWidget, + fakeParentEditor, { - listForeground: editorForeground, - listBackground: editorBackground, - inputEditorBackground: inputBackground, - resultEditorBackground: editorBackground - })); - this._widget.render(this._widgetContainer); - this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); + menuId: MENU_CELL_CHAT_INPUT, + widgetMenuId: MENU_CELL_CHAT_WIDGET, + statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + } + ); + + this._widgetContainer.appendChild(this._inlineChatWidget.domNode); } reveal(): void { this._widgetContainer.classList.remove('hide'); - this._widget.setVisible(true); + // this._widget.setVisible(true); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); - this._widget.setInput('@terminal'); - this._widget.setInputPlaceholder('Request a terminal command'); - this._widget.focusInput(); + // this._widget.setInput('@terminal'); + // this._widget.setInputPlaceholder('Request a terminal command'); + // this._widget.focusInput(); } hide(): void { this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); - this._widget.clear(); + // this._widget.clear(); this._instance.focus(); } cancel(): void { From 73e7e46e8056b36e74118d23304d65c8c959819a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:17:35 -0800 Subject: [PATCH 009/753] More styled inline chat --- .../contrib/inlineChat/browser/inlineChat.css | 150 +++++++++--------- .../{chat.css => terminalChatWidget.css} | 5 +- .../browser/terminal.chat.contribution.ts | 1 - .../chat/browser/terminalChatWidget.ts | 13 +- 4 files changed, 85 insertions(+), 84 deletions(-) rename src/vs/workbench/contrib/terminalContrib/chat/browser/media/{chat.css => terminalChatWidget.css} (86%) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index ee46f6df25e..862f0183354 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -11,7 +11,7 @@ background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat { +.inline-chat { color: inherit; padding: 6px; margin-top: 6px; @@ -23,11 +23,11 @@ /* body */ -.monaco-editor .inline-chat .body { +.inline-chat .body { display: flex; } -.monaco-editor .inline-chat .body .content { +.inline-chat .body .content { display: flex; box-sizing: border-box; outline: 1px solid var(--vscode-inlineChatInput-border); @@ -35,11 +35,11 @@ border-radius: 2px; } -.monaco-editor .inline-chat .body .content.synthetic-focus { +.inline-chat .body .content.synthetic-focus { outline: 1px solid var(--vscode-inlineChatInput-focusBorder); } -.monaco-editor .inline-chat .body .content .input { +.inline-chat .body .content .input { display: flex; align-items: center; justify-content: space-between; @@ -48,11 +48,11 @@ cursor: text; } -.monaco-editor .inline-chat .body .content .input .monaco-editor-background { +.inline-chat .body .content .input .monaco-editor-background { background-color: var(--vscode-inlineChatInput-background); } -.monaco-editor .inline-chat .body .content .input .editor-placeholder { +.inline-chat .body .content .input .editor-placeholder { position: absolute; z-index: 1; color: var(--vscode-inlineChatInput-placeholderForeground); @@ -61,14 +61,14 @@ text-overflow: ellipsis; } -.monaco-editor .inline-chat .body .content .input .editor-placeholder.hidden { +.inline-chat .body .content .input .editor-placeholder.hidden { display: none; } -.monaco-editor .inline-chat .body .content .input .editor-container { +.inline-chat .body .content .input .editor-container { vertical-align: middle; } -.monaco-editor .inline-chat .body .toolbar { +.inline-chat .body .toolbar { display: flex; flex-direction: column; align-self: stretch; @@ -78,47 +78,47 @@ background: var(--vscode-inlineChatInput-background); } -.monaco-editor .inline-chat .body .toolbar .actions-container { +.inline-chat .body .toolbar .actions-container { display: flex; flex-direction: row; gap: 4px; } -.monaco-editor .inline-chat .body > .widget-toolbar { +.inline-chat .body > .widget-toolbar { padding-left: 4px; } /* progress bit */ -.monaco-editor .inline-chat .progress { +.inline-chat .progress { position: relative; width: calc(100% - 18px); left: 19px; } /* UGLY - fighting against workbench styles */ -.monaco-workbench .part.editor > .content .monaco-editor .inline-chat .progress .monaco-progress-container { +.monaco-workbench .part.editor > .content .inline-chat .progress .monaco-progress-container { top: 0; } /* status */ -.monaco-editor .inline-chat .status { +.inline-chat .status { margin-top: 4px; display: flex; justify-content: space-between; align-items: center; } -.monaco-editor .inline-chat .status.actions { +.inline-chat .status.actions { margin-top: 4px; } -.monaco-editor .inline-chat .status .actions.hidden { +.inline-chat .status .actions.hidden { display: none; } -.monaco-editor .inline-chat .status .label { +.inline-chat .status .label { overflow: hidden; color: var(--vscode-descriptionForeground); font-size: 11px; @@ -126,60 +126,60 @@ display: inline-flex; } -.monaco-editor .inline-chat .status .label.hidden { +.inline-chat .status .label.hidden { display: none; } -.monaco-editor .inline-chat .status .label.info { +.inline-chat .status .label.info { margin-right: auto; padding-left: 2px; } -.monaco-editor .inline-chat .status .label.info > .codicon { +.inline-chat .status .label.info > .codicon { padding: 0 5px; font-size: 12px; line-height: 18px; } -.monaco-editor .inline-chat .status .label.status { +.inline-chat .status .label.status { padding-left: 10px; padding-right: 4px; margin-left: auto; } -.monaco-editor .inline-chat .status .label .slash-command-pill CODE { +.inline-chat .status .label .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.monaco-editor .inline-chat .detectedIntent { +.inline-chat .detectedIntent { color: var(--vscode-descriptionForeground); padding: 5px 0px 5px 5px; } -.monaco-editor .inline-chat .detectedIntent.hidden { +.inline-chat .detectedIntent.hidden { display: none; } -.monaco-editor .inline-chat .detectedIntent .slash-command-pill CODE { +.inline-chat .detectedIntent .slash-command-pill CODE { border-radius: 3px; padding: 0 1px; background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); } -.monaco-editor .inline-chat .detectedIntent .slash-command-pill a { +.inline-chat .detectedIntent .slash-command-pill a { color: var(--vscode-textLink-foreground); cursor: pointer; } -/* .monaco-editor .inline-chat .markdownMessage .message * { +/* .inline-chat .markdownMessage .message * { margin: unset; } -.monaco-editor .inline-chat .markdownMessage .message code { +.inline-chat .markdownMessage .message code { font-family: var(--monaco-monospace-font); font-size: 12px; color: var(--vscode-textPreformat-foreground); @@ -189,7 +189,7 @@ } */ -.monaco-editor .inline-chat .chatMessage .chatMessageContent .value { +.inline-chat .chatMessage .chatMessageContent .value { -webkit-line-clamp: initial; -webkit-box-orient: vertical; overflow: hidden; @@ -198,130 +198,130 @@ user-select: text; } -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { +.inline-chat .chatMessage .chatMessageContent[state="cropped"] .value { -webkit-line-clamp: var(--vscode-inline-chat-cropped, 3); } -.monaco-editor .inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { +.inline-chat .chatMessage .chatMessageContent[state="expanded"] .value { -webkit-line-clamp: var(--vscode-inline-chat-expanded, 10); } -.monaco-editor .inline-chat .followUps { +.inline-chat .followUps { padding: 5px 5px; } -.monaco-editor .inline-chat .followUps .interactive-session-followups .monaco-button { +.inline-chat .followUps .interactive-session-followups .monaco-button { display: block; color: var(--vscode-textLink-foreground); font-size: 12px; } -.monaco-editor .inline-chat .followUps.hidden { +.inline-chat .followUps.hidden { display: none; } -.monaco-editor .inline-chat .chatMessage { +.inline-chat .chatMessage { padding: 8px 3px; } -.monaco-editor .inline-chat .chatMessage .chatMessageContent { +.inline-chat .chatMessage .chatMessageContent { padding: 2px 2px; } -.monaco-editor .inline-chat .chatMessage.hidden { +.inline-chat .chatMessage.hidden { display: none; } -.monaco-editor .inline-chat .status .label A { +.inline-chat .status .label A { color: var(--vscode-textLink-foreground); cursor: pointer; } -.monaco-editor .inline-chat .status .label.error { +.inline-chat .status .label.error { color: var(--vscode-errorForeground); } -.monaco-editor .inline-chat .status .label.warn { +.inline-chat .status .label.warn { color: var(--vscode-editorWarning-foreground); } -.monaco-editor .inline-chat .status .actions { +.inline-chat .status .actions { display: flex; } -.monaco-editor .inline-chat .status .actions > .monaco-button, -.monaco-editor .inline-chat .status .actions > .monaco-button-dropdown { +.inline-chat .status .actions > .monaco-button, +.inline-chat .status .actions > .monaco-button-dropdown { margin-right: 6px; } -.monaco-editor .inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { +.inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button { display: flex; align-items: center; padding: 0 4px; } -.monaco-editor .inline-chat .status .actions > .monaco-button.codicon { +.inline-chat .status .actions > .monaco-button.codicon { display: flex; } -.monaco-editor .inline-chat .status .actions > .monaco-button.codicon::before { +.inline-chat .status .actions > .monaco-button.codicon::before { align-self: center; } -.monaco-editor .inline-chat .status .actions .monaco-text-button { +.inline-chat .status .actions .monaco-text-button { padding: 2px 4px; white-space: nowrap; } -.monaco-editor .inline-chat .status .monaco-toolbar .action-item { +.inline-chat .status .monaco-toolbar .action-item { padding: 0 2px; } /* TODO@jrieken not needed? */ -.monaco-editor .inline-chat .status .monaco-toolbar .action-label.checked { +.inline-chat .status .monaco-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); } -.monaco-editor .inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { +.inline-chat .status .monaco-toolbar .action-item.button-item .action-label:is(:hover, :focus) { background-color: var(--vscode-button-hoverBackground); } /* preview */ -.monaco-editor .inline-chat .preview { +.inline-chat .preview { display: none; } -.monaco-editor .inline-chat .previewDiff, -.monaco-editor .inline-chat .previewCreate { +.inline-chat .previewDiff, +.inline-chat .previewCreate { display: inherit; border: 1px solid var(--vscode-inlineChat-border); border-radius: 2px; margin: 6px 0px; } -.monaco-editor .inline-chat .previewCreateTitle { +.inline-chat .previewCreateTitle { padding-top: 6px; } -.monaco-editor .inline-chat .previewDiff.hidden, -.monaco-editor .inline-chat .previewCreate.hidden, -.monaco-editor .inline-chat .previewCreateTitle.hidden { +.inline-chat .previewDiff.hidden, +.inline-chat .previewCreate.hidden, +.inline-chat .previewCreateTitle.hidden { display: none; } -.monaco-editor .inline-chat-toolbar { +.inline-chat-toolbar { display: flex; } -.monaco-editor .inline-chat-toolbar > .monaco-button{ +.inline-chat-toolbar > .monaco-button{ margin-right: 6px; } -.monaco-editor .inline-chat-toolbar .action-label.checked { +.inline-chat-toolbar .action-label.checked { color: var(--vscode-inputOption-activeForeground); background-color: var(--vscode-inputOption-activeBackground); outline: 1px solid var(--vscode-inputOption-activeBorder); @@ -329,65 +329,65 @@ /* decoration styles */ -.monaco-editor .inline-chat-inserted-range { +.inline-chat-inserted-range { background-color: var(--vscode-inlineChatDiff-inserted); } -.monaco-editor .inline-chat-inserted-range-linehighlight { +.inline-chat-inserted-range-linehighlight { background-color: var(--vscode-diffEditor-insertedLineBackground); } -.monaco-editor .inline-chat-original-zone2 { +.inline-chat-original-zone2 { background-color: var(--vscode-diffEditor-removedLineBackground); opacity: 0.8; } -.monaco-editor .inline-chat-lines-inserted-range { +.inline-chat-lines-inserted-range { background-color: var(--vscode-diffEditor-insertedTextBackground); } -.monaco-editor .inline-chat-block-selection { +.inline-chat-block-selection { background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat-slash-command { +.inline-chat-slash-command { opacity: 0; } -.monaco-editor .inline-chat-slash-command-detail { +.inline-chat-slash-command-detail { opacity: 0.5; } /* diff zone */ -.monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, -.monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { +.inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, +.inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { background-color: var(--vscode-inlineChat-regionHighlight); } /* create zone */ -.monaco-editor .inline-chat-newfile-widget { +.inline-chat-newfile-widget { background-color: var(--vscode-inlineChat-regionHighlight); } -.monaco-editor .inline-chat-newfile-widget .title { +.inline-chat-newfile-widget .title { display: flex; align-items: center; justify-content: space-between; } -.monaco-editor .inline-chat-newfile-widget .title .detail { +.inline-chat-newfile-widget .title .detail { margin-left: 4px; } -.monaco-editor .inline-chat-newfile-widget .buttonbar-widget { +.inline-chat-newfile-widget .buttonbar-widget { display: flex; margin-left: auto; margin-right: 8px; } -.monaco-editor .inline-chat-newfile-widget .buttonbar-widget > .monaco-button { +.inline-chat-newfile-widget .buttonbar-widget > .monaco-button { display: inline-flex; white-space: nowrap; margin-left: 4px; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css similarity index 86% rename from src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css rename to src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index e81dc6b4752..5c8676bbe6e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -6,8 +6,9 @@ .terminal-inline-chat { position: absolute; left: 0; - top: 0; bottom: 0; - right: 0; z-index: 100; } +/* .terminal-inline-chat .inline-chat .body { + display: flex; +} */ diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c210f1c4494..b9fab74a3a2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/chat'; import { IDimension } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index d8c1d5180c7..7c67b161aba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import 'vs/css!./media/terminalChatWidget'; +import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class TerminalChatWidget extends Disposable { - private _widget!: ChatWidget; private _scopedInstantiationService: IInstantiationService; private _widgetContainer: HTMLElement; private _chatWidgetFocused: IContextKey; @@ -81,6 +80,8 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } reveal(): void { + this._inlineChatWidget.layout(new Dimension(400, 150)); + this._widgetContainer.classList.remove('hide'); // this._widget.setVisible(true); this._chatWidgetFocused.set(true); @@ -97,13 +98,13 @@ export class TerminalChatWidget extends Disposable { this._instance.focus(); } cancel(): void { - this._widget?.clear(); + // this._widget?.clear(); } acceptInput(): void { - this._widget?.acceptInput(); + // this._widget?.acceptInput(); } layout(width: number): void { - this._widget?.layout(100, width < 300 ? 300 : width); + // this._widget?.layout(100, width < 300 ? 300 : width); } public get focusTracker(): IFocusTracker { return this._focusTracker; From 22670a1031a0af3df9cc13f267eaa3dd16626bb2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:21:09 -0800 Subject: [PATCH 010/753] Add temp placeholders --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 7c67b161aba..041ec3b9573 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -14,6 +14,7 @@ import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inline import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { localize } from 'vs/nls'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -74,6 +75,8 @@ export class TerminalChatWidget extends Disposable { feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } ); + this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); From 792fc7a419483cf0b1bd6a496e4b68307574d479 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 12:24:13 -0600 Subject: [PATCH 011/753] fix focus issues --- src/vs/workbench/contrib/terminal/browser/media/terminal.css | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8fc9e6be0eb..8fcb3c71172 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -572,6 +572,6 @@ top: 10px; } -.monaco-workbench .terminal-chat-widget.hide { +.monaco-workbench .terminal-inline-chat.hide { visibility: hidden; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 041ec3b9573..89c2518dd8e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -92,6 +92,7 @@ export class TerminalChatWidget extends Disposable { // this._widget.setInput('@terminal'); // this._widget.setInputPlaceholder('Request a terminal command'); // this._widget.focusInput(); + this._inlineChatWidget.focus(); } hide(): void { this._widgetContainer.classList.add('hide'); From dfed5c7b09b6ff0dd6d91de17875c8278e66e3d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:31:03 -0800 Subject: [PATCH 012/753] Remove detached terminal support --- .../browser/terminal.chat.contribution.ts | 34 +++++++++++++------ .../chat/browser/terminalChatWidget.ts | 14 ++++---- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index b9fab74a3a2..e54914120bd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,31 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IDimension } from 'vs/base/browser/dom'; +import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize2 } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { Codicon } from 'vs/base/common/codicons'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TerminalChatContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; - static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null { + static get(instance: ITerminalInstance): TerminalChatContribution | null { return instance.getContribution(TerminalChatContribution.ID); } /** @@ -41,8 +41,8 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } constructor( - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - processManager: ITerminalProcessManager | ITerminalProcessInfo, + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private _configurationService: IConfigurationService, @@ -96,7 +96,7 @@ export class TerminalChatContribution extends Disposable implements ITerminalCon this._chatWidget?.rawValue?.dispose(); } } -registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, true); +registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, false); registerActiveXtermAction({ id: TerminalCommandId.FocusChat, @@ -112,6 +112,9 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.reveal(); } @@ -132,6 +135,9 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ), run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.hide(); } @@ -152,6 +158,9 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.acceptInput(); } @@ -170,6 +179,9 @@ registerActiveXtermAction({ group: 'navigation', }, run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); contr?.chatWidget?.cancel(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 89c2518dd8e..3a99cd5ebef 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; @@ -28,10 +28,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, - + private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService + ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -61,7 +61,9 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - {}, + { + + }, { isSimpleWidget: true } ); @@ -76,7 +78,7 @@ export class TerminalChatWidget extends Disposable { } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); From cfeb85db2be149ab3c0e5a6e466f02876acf5330 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:49:57 -0800 Subject: [PATCH 013/753] Anchor term chat to bottom --- .../terminalContrib/chat/browser/media/terminalChatWidget.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 5c8676bbe6e..633b6778dfe 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -8,6 +8,7 @@ left: 0; bottom: 0; z-index: 100; + height: auto !important; } /* .terminal-inline-chat .inline-chat .body { display: flex; From 0a8994cf3ac712a7ee9e5fa1ddbf82a70c8ce891 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 12:53:06 -0600 Subject: [PATCH 014/753] use terminal menu ID --- src/vs/platform/actions/common/actions.ts | 1 + .../actions/voiceChatActions.ts | 6 ++++++ .../chat/browser/terminalChatWidget.ts | 20 +++++++++---------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 376627398ac..ec2810e91fb 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -208,6 +208,7 @@ export class MenuId { static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); + static readonly TerminalChat = new MenuId('TerminalChat'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3b605a096d5..75f3ab3c832 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -475,6 +475,12 @@ export class StartVoiceChatAction extends Action2 { when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 + }, + { + id: MenuId.TerminalChat, + when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), + group: 'main', + order: -1 }] }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 3a99cd5ebef..de24d36312d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,10 +11,11 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -28,10 +29,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance, + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService - ) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -61,9 +62,7 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - { - - }, + {}, { isSimpleWidget: true } ); @@ -71,15 +70,14 @@ export class TerminalChatWidget extends Disposable { InlineChatWidget, fakeParentEditor, { - menuId: MENU_CELL_CHAT_INPUT, + menuId: MenuId.TerminalChat, widgetMenuId: MENU_CELL_CHAT_WIDGET, statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); - + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._widgetContainer)); From 9e07b3ae1e411647d86531822b31178e044a4df5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:54:57 -0800 Subject: [PATCH 015/753] Remove detached terminal support --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index de24d36312d..bef5d94f3d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,7 +11,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -29,7 +29,7 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, - private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService) { From 5e3bd936bcb55db758af7de1346549eff0d3f2f5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:59:31 -0800 Subject: [PATCH 016/753] Fix panel background overriding input's --- src/vs/workbench/browser/parts/panel/media/panelpart.css | 6 +++--- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 4ccb53b0c80..7b17465b2d9 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -35,9 +35,9 @@ border-right-width: 0; /* no border when main editor area is hiden */ } -.monaco-workbench .part.panel > .content .monaco-editor, -.monaco-workbench .part.panel > .content .monaco-editor .margin, -.monaco-workbench .part.panel > .content .monaco-editor .monaco-editor-background { +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg), +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg) .margin, +.monaco-workbench .part.panel > .content .monaco-editor:not(.ignore-panel-bg) .monaco-editor-background { background-color: var(--vscode-panel-background); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bef5d94f3d8..fc85c9a5a50 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -62,7 +62,9 @@ export class TerminalChatWidget extends Disposable { const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, - {}, + { + extraEditorClassName: 'ignore-panel-bg' + }, { isSimpleWidget: true } ); From 2611151ba8966ba0c52c27ea0885a6cadd39c99a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:10:10 -0800 Subject: [PATCH 017/753] Add wip make request button --- .../contrib/terminal/common/terminal.ts | 2 +- .../browser/terminal.chat.contribution.ts | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 0579628b521..9a11ee3252c 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -502,7 +502,7 @@ export const enum TerminalCommandId { FontZoomReset = 'workbench.action.terminal.fontZoomReset', FocusChat = 'workbench.action.terminal.focusChat', HideChat = 'workbench.action.terminal.hideChat', - SubmitChat = 'workbench.action.terminal.submitChat', + MakeChatRequest = 'workbench.action.terminal.submitChat', CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index e54914120bd..6c44b62619c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -100,7 +100,7 @@ registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContributi registerActiveXtermAction({ id: TerminalCommandId.FocusChat, - title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'), + title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -122,7 +122,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.HideChat, - title: localize2('workbench.action.terminal.hideChat', 'Terminal: Hide Chat'), + title: localize2('workbench.action.terminal.hideChat', 'Hide Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -144,18 +144,28 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.SubmitChat, - title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'), + id: TerminalCommandId.MakeChatRequest, + title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText ), icon: Codicon.send, + keybinding: { + when: TerminalContextKeys.chatSessionInProgress, + // TODO: + // when: CTX_INLINE_CHAT_FOCUSED, + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, menu: { - id: MenuId.ChatExecute, + id: MenuId.TerminalChat, + group: 'main', + order: 1, when: TerminalContextKeys.chatSessionInProgress.negate(), - group: 'navigation', + // TODO: + // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From a8cda8716fb80b41170aeb251b99a26b276e2bfc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:10:25 -0800 Subject: [PATCH 018/753] Fix command name --- src/vs/workbench/contrib/terminal/common/terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 9a11ee3252c..5ed9e674af2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -502,7 +502,7 @@ export const enum TerminalCommandId { FontZoomReset = 'workbench.action.terminal.fontZoomReset', FocusChat = 'workbench.action.terminal.focusChat', HideChat = 'workbench.action.terminal.hideChat', - MakeChatRequest = 'workbench.action.terminal.submitChat', + MakeChatRequest = 'workbench.action.terminal.makeChatRequest', CancelChat = 'workbench.action.terminal.cancelChat', // Developer commands From 4e63128a7e9820f62c236913bcc0ac67e1da833c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Mon, 12 Feb 2024 18:12:40 +0100 Subject: [PATCH 019/753] fix: inverted resize for direction.right --- src/vs/workbench/contrib/terminal/browser/terminalGroup.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index d993243770f..163b64068d2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -69,6 +69,7 @@ class SplitPaneContainer extends Disposable { // Resize the entire pane as a whole if ( (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || + (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || (part === Parts.SIDEBAR_PART && direction === Direction.Left) || (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) ) { From 18fde1e863ce5c56f50e90b36095b50281196c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Mon, 12 Feb 2024 18:13:11 +0100 Subject: [PATCH 020/753] fix: inverted resize direction for left-positionned terminal panel --- .../workbench/contrib/terminal/browser/terminalGroup.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 163b64068d2..7e944aa9753 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -577,11 +577,18 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + + // Left-positionned panels have inverted controls + // see https://github.com/microsoft/vscode/issues/140873 + const shouldInvertHorizontalResize = (isHorizontal && this._panelPosition === Position.LEFT); + + const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; + const font = this._terminalService.configHelper.getFont(getWindow(this._groupElement)); // TODO: Support letter spacing and line height const charSize = (isHorizontal ? font.charWidth : font.charHeight); if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); + this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); } } From b79d48c296a18057450205fe65029dabebb08ddc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:52:59 -0600 Subject: [PATCH 021/753] wip make request action --- .../chat/browser/terminal.chat.contribution.ts | 6 +++--- .../chat/browser/terminalChatWidget.ts | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 6c44b62619c..c2bbe8aa74a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -149,11 +149,11 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatInputHasText + // TerminalContextKeys.chatInputHasText ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatSessionInProgress, + when: TerminalContextKeys.chatSessionInProgress.negate(), // TODO: // when: CTX_INLINE_CHAT_FOCUSED, weight: KeybindingWeight.EditorCore + 7, @@ -163,7 +163,7 @@ registerActiveXtermAction({ id: MenuId.TerminalChat, group: 'main', order: 1, - when: TerminalContextKeys.chatSessionInProgress.negate(), + // when: TerminalContextKeys.chatSessionInProgress.negate(), // TODO: // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index fc85c9a5a50..1467a9a86ef 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -16,6 +16,9 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -27,12 +30,15 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; + private _chatModel: ChatModel | undefined; + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService) { + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatService private readonly _chatService: IChatService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -41,6 +47,7 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); + // this._widget = this._register(this._scopedInstantiationService.createInstance( // ChatWidget, // { viewId: 'terminal' }, @@ -108,6 +115,14 @@ export class TerminalChatWidget extends Disposable { } acceptInput(): void { // this._widget?.acceptInput(); + this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + + // if (!this._model) { + // throw new Error('Could not start chat session'); + // } + this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + this._inlineChatWidget.value = ''; + // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); From a0d97ae9449d8665a531d8dd1dd58c4fe24332fe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 15:49:32 -0600 Subject: [PATCH 022/753] contrib => controller --- .../terminal/common/terminalContextKey.ts | 6 +- .../browser/terminal.chat.contribution.ts | 101 ++------------- .../chat/browser/terminalChatController.ts | 119 ++++++++++++++++++ .../chat/browser/terminalChatWidget.ts | 5 +- 4 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 1de53d61930..78fd7df6712 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -41,7 +41,7 @@ export const enum TerminalContextKeyStrings { TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', ChatFocus = 'terminalChatFocus', ChatVisible = 'terminalChatVisible', - ChatSessionInProgress = 'terminalChatSessionInProgress', + ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', } @@ -170,8 +170,8 @@ export namespace TerminalContextKeys { /** Whether the chat widget is visible */ export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); - /** Whether a chat session is in progress */ - export const chatSessionInProgress = new RawContextKey(TerminalContextKeyStrings.ChatSessionInProgress, false, localize('chatSessionInProgressContextKey', "Whether a chat session is in progress.")); + /** Whether there is an active chat request */ + export const chatRequestActive = new RawContextKey(TerminalContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); /** Whether the chat input has text */ export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index c2bbe8aa74a..1d909e6fc30 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,100 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { IDimension } from 'vs/base/browser/dom'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Lazy } from 'vs/base/common/lazy'; -import { Disposable } from 'vs/base/common/lifecycle'; import { localize2 } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -export class TerminalChatContribution extends Disposable implements ITerminalContribution { - static readonly ID = 'terminal.Chat'; - - static get(instance: ITerminalInstance): TerminalChatContribution | null { - return instance.getContribution(TerminalChatContribution.ID); - } - /** - * Currently focused chat widget. This is used to track action context since - * 'active terminals' are only tracked for non-detached terminal instanecs. - */ - static activeChatWidget?: TerminalChatContribution; - private _chatWidget: Lazy | undefined; - private _lastLayoutDimensions: IDimension | undefined; - - get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - - constructor( - private readonly _instance: ITerminalInstance, - processManager: ITerminalProcessManager, - widgetManager: TerminalWidgetManager, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private _configurationService: IConfigurationService, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(); - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - } - - layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._lastLayoutDimensions = dimension; - this._chatWidget?.rawValue?.layout(dimension.width); - } - - xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - chatWidget.focusTracker.onDidFocus(() => { - TerminalChatContribution.activeChatWidget = this; - if (!isDetachedTerminalInstance(this._instance)) { - this._terminalService.setActiveInstance(this._instance); - } - }); - chatWidget.focusTracker.onDidBlur(() => { - TerminalChatContribution.activeChatWidget = undefined; - this._instance.resetScrollbarVisibility(); - }); - if (!this._instance.domElement) { - throw new Error('FindWidget expected terminal DOM to be initialized'); - } - - // this._instance.domElement?.appendChild(chatWidget.getDomNode()); - if (this._lastLayoutDimensions) { - chatWidget.layout(this._lastLayoutDimensions.width); - } - - return chatWidget; - }); - } - - override dispose() { - super.dispose(); - this._chatWidget?.rawValue?.dispose(); - } -} -registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, false); +registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ id: TerminalCommandId.FocusChat, @@ -115,7 +36,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); } }); @@ -138,7 +59,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.hide(); } }); @@ -153,7 +74,7 @@ registerActiveXtermAction({ ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatSessionInProgress.negate(), + when: TerminalContextKeys.chatRequestActive.negate(), // TODO: // when: CTX_INLINE_CHAT_FOCUSED, weight: KeybindingWeight.EditorCore + 7, @@ -171,8 +92,8 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); - contr?.chatWidget?.acceptInput(); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptInput(); } }); @@ -181,7 +102,7 @@ registerActiveXtermAction({ title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatSessionInProgress, + TerminalContextKeys.chatRequestActive, ), icon: Codicon.debugStop, menu: { @@ -192,7 +113,7 @@ registerActiveXtermAction({ if (isDetachedTerminalInstance(activeInstance)) { return; } - const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance); + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts new file mode 100644 index 00000000000..ae9ded44aec --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDimension } from 'vs/base/browser/dom'; +import { Lazy } from 'vs/base/common/lazy'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; + +export class TerminalChatController extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.Chat'; + + static get(instance: ITerminalInstance): TerminalChatController | null { + return instance.getContribution(TerminalChatController.ID); + } + /** + * Currently focused chat widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeChatWidget?: TerminalChatController; + private _chatWidget: Lazy | undefined; + private _lastLayoutDimensions: IDimension | undefined; + + get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } + + // private _sessionCtor: CancelablePromise | undefined; + private _activeSession?: Session; + // private readonly _ctxHasActiveRequest: IContextKey; + // private _isVisible: boolean = false; + // private _strategy: EditStrategy | undefined; + + // private _inlineChatListener: IDisposable | undefined; + // private _toolbar: MenuWorkbenchToolBar | undefined; + // private readonly _ctxLastResponseType: IContextKey; + // private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); + + constructor( + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager, + widgetManager: TerminalWidgetManager, + @IConfigurationService private _configurationService: IConfigurationService, + @ITerminalService private readonly _terminalService: ITerminalService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + // @IContextKeyService private readonly _contextKeyService: IContextKeyService, + // @IInstantiationService private readonly _instantiationService: IInstantiationService, + // @ICommandService private readonly _commandService: ICommandService, + // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService + ) { + super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + // this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + } + + layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + this._lastLayoutDimensions = dimension; + this._chatWidget?.rawValue?.layout(dimension.width); + } + + xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { + return; + } + this._chatWidget = new Lazy(() => { + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + chatWidget.focusTracker.onDidFocus(() => { + TerminalChatController.activeChatWidget = this; + if (!isDetachedTerminalInstance(this._instance)) { + this._terminalService.setActiveInstance(this._instance); + } + }); + chatWidget.focusTracker.onDidBlur(() => { + TerminalChatController.activeChatWidget = undefined; + this._instance.resetScrollbarVisibility(); + }); + if (!this._instance.domElement) { + throw new Error('FindWidget expected terminal DOM to be initialized'); + } + + // this._instance.domElement?.appendChild(chatWidget.getDomNode()); + if (this._lastLayoutDimensions) { + chatWidget.layout(this._lastLayoutDimensions.width); + } + + return chatWidget; + }); + } + + async acceptInput(): Promise { + // TODO: create session, deal with response + this._chatWidget?.rawValue?.acceptInput(); + } + + reveal(): void { + this._chatWidget?.rawValue?.reveal(); + } + + override dispose() { + super.dispose(); + this._chatWidget?.rawValue?.dispose(); + } +} + diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 1467a9a86ef..14a6ca567b4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -115,12 +115,13 @@ export class TerminalChatWidget extends Disposable { } acceptInput(): void { // this._widget?.acceptInput(); - this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); // if (!this._model) { // throw new Error('Could not start chat session'); // } - this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + this._inlineChatWidget.value = ''; // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } From fd0784486582c2cff44a8c4bb28fa01b38904473 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 16:27:38 -0600 Subject: [PATCH 023/753] wip make request action --- .../chat/browser/terminalChatController.ts | 20 +++++++++- .../chat/browser/terminalChatWidget.ts | 39 ++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index ae9ded44aec..5392e1787dd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,8 +14,8 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -51,7 +51,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IChatAgentService private readonly _chatAgentService: IChatAgentService // @IContextKeyService private readonly _contextKeyService: IContextKeyService, // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, @@ -104,6 +104,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr async acceptInput(): Promise { // TODO: create session, deal with response + // this._activeSession = new Session(EditMode.Live, , this._instance); + // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + // const requestProps: IChatAgentRequest = { + // sessionId: 'sessionId', + // requestId: 'fake', + // agentId: 'terminal', + // message: this._chatWidget?.rawValue?.getValue() || '', + // // variables: variableData.variables, + // // command: agentSlashCommandPart?.command.name, + // // variables2: asVariablesData2(parsedRequest, variableData) + // }; + // const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, undefined, token); + // const rawResult = agentResult; + // const agentOrCommandFollowups = this._chatAgentService.getFollowups('terminal', agentResult, followupsCancelToken); this._chatWidget?.rawValue?.acceptInput(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 14a6ca567b4..ed78f6292e7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -16,8 +16,9 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CancellationToken } from 'vs/base/common/cancellation'; export class TerminalChatWidget extends Disposable { @@ -30,7 +31,6 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - private _chatModel: ChatModel | undefined; constructor( private readonly _container: HTMLElement, @@ -38,7 +38,7 @@ export class TerminalChatWidget extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatService private readonly _chatService: IChatService) { + @IChatAgentService private readonly _chatAgentService: IChatAgentService) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -113,7 +113,7 @@ export class TerminalChatWidget extends Disposable { cancel(): void { // this._widget?.clear(); } - acceptInput(): void { + async acceptInput(): Promise { // this._widget?.acceptInput(); // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); @@ -121,9 +121,36 @@ export class TerminalChatWidget extends Disposable { // throw new Error('Could not start chat session'); // } // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // this._activeSession = new Session(EditMode.Live, , this._instance); + // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + const progressCallback = (progress: IChatProgress) => { + // if (token.isCancellationRequested) { + // return; + // } + console.log(progress); + // gotProgress = true; + + if (progress.kind === 'content' || progress.kind === 'markdownContent') { + // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); + } else { + // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); + } + // model.acceptResponseProgress(request, progress); + }; + const requestProps: IChatAgentRequest = { + sessionId: generateUuid(), + requestId: generateUuid(), + agentId: 'terminal', + message: this._inlineChatWidget.value || '', + variables: new Map() as any, + variables2: {} as any + }; + const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + console.log(agentResult); this._inlineChatWidget.value = ''; - // this.widget.setModel(this.model, { inputValue: this._currentQuery }); } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); From 2f785c99624573f441d445d7327fd48790f899e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Tue, 13 Feb 2024 09:14:21 +0100 Subject: [PATCH 024/753] fix: resize direction for terminals in the sidebar --- .../workbench/contrib/terminal/browser/terminalGroup.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 7e944aa9753..30a95315f33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -70,7 +70,6 @@ class SplitPaneContainer extends Disposable { if ( (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || - (part === Parts.SIDEBAR_PART && direction === Direction.Left) || (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) ) { amount *= -1; @@ -578,9 +577,13 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + const part = getPartByLocation(this._terminalLocation); + + const isTerminalLeft = this._panelPosition === Position.LEFT || part === Parts.SIDEBAR_PART; + // Left-positionned panels have inverted controls // see https://github.com/microsoft/vscode/issues/140873 - const shouldInvertHorizontalResize = (isHorizontal && this._panelPosition === Position.LEFT); + const shouldInvertHorizontalResize = (isHorizontal && isTerminalLeft); const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; @@ -588,7 +591,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { // TODO: Support letter spacing and line height const charSize = (isHorizontal ? font.charWidth : font.charHeight); if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); + this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, part); } } From 4d542ef5d144449f1c399dc5c06cbf296380b7fe Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Tue, 13 Feb 2024 13:55:49 +0000 Subject: [PATCH 025/753] Make channel log level settable from output view --- .../contrib/logs/common/logs.contribution.ts | 4 +- .../contrib/logs/common/logsActions.ts | 17 ++++++--- .../output/browser/output.contribution.ts | 37 ++++++++++++++++++- .../contrib/output/browser/outputServices.ts | 9 ++++- .../services/output/common/output.ts | 2 + 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 19257411641..2b95a4341ce 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -36,8 +36,8 @@ registerAction2(class extends Action2 { f1: true }); } - run(servicesAccessor: ServicesAccessor): Promise { - return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(); + run(servicesAccessor: ServicesAccessor, channelId?: string): Promise { + return servicesAccessor.get(IInstantiationService).createInstance(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.TITLE.value).run(channelId); } }); diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 519fab7f93a..f295d14a03c 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -12,7 +12,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IOutputChannelDescriptor, IOutputService } from 'vs/workbench/services/output/common/output'; import { extensionTelemetryLogChannelId, telemetryLogId } from 'vs/platform/telemetry/common/telemetryUtils'; import { IDefaultLogLevelsService } from 'vs/workbench/contrib/logs/common/defaultLogLevels'; import { Codicon } from 'vs/base/common/codicons'; @@ -36,8 +36,8 @@ export class SetLogLevelAction extends Action { super(id, label); } - override async run(): Promise { - const logLevelOrChannel = await this.selectLogLevelOrChannel(); + override async run(channelId?: string): Promise { + const logLevelOrChannel = await this.getLogLevelOrChannel(channelId); if (logLevelOrChannel !== null) { if (isLogLevel(logLevelOrChannel)) { this.loggerService.setLogLevel(logLevelOrChannel); @@ -47,16 +47,19 @@ export class SetLogLevelAction extends Action { } } - private async selectLogLevelOrChannel(): Promise { + private async getLogLevelOrChannel(channelId?: string): Promise { const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels(); const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = []; const logLevel = this.loggerService.getLogLevel(); for (const channel of this.outputService.getChannelDescriptors()) { - if (!channel.log || !channel.file || channel.id === telemetryLogId || channel.id === extensionTelemetryLogChannelId) { + if (!SetLogLevelAction.isLevelSettable(channel) || !channel.file) { continue; } const channelLogLevel = this.loggerService.getLogLevel(channel.file) ?? logLevel; const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.file, label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId }; + if (channelId && channel.id === channelId) { + return item; + } if (channel.extensionId) { extensionLogs.push(item); } else { @@ -96,6 +99,10 @@ export class SetLogLevelAction extends Action { }); } + static isLevelSettable(channel: IOutputChannelDescriptor): boolean { + return channel.log && channel.file !== undefined && channel.id !== telemetryLogId && channel.id !== extensionTelemetryLogChannelId; + } + private async setLogLevelForChannel(logChannel: LogChannelQuickPickItem): Promise { const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels(); const defaultLogLevel = defaultLogLevels.extensions.find(e => e[0] === logChannel.extensionId?.toLowerCase())?.[1] ?? defaultLogLevels.default; diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 794a8e60d57..6ef9f9764ea 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { OutputService } from 'vs/workbench/contrib/output/browser/outputServices'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -30,6 +30,8 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -99,6 +101,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { this.registerOpenActiveOutputFileInAuxWindowAction(); this.registerShowLogsAction(); this.registerOpenLogFileAction(); + this.registerConfigureActiveOutputLogLevelAction(); } private registerSwitchOutputAction(): void { @@ -334,6 +337,38 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { return null; } + private registerConfigureActiveOutputLogLevelAction(): void { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.action.configureActiveOutputLogLevel`, + title: nls.localize2('configureActiveOutputLogLevel', "Configure Log Level..."), + menu: [{ + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), + group: 'navigation', + order: 6, + isHiddenByDefault: true + }], + icon: Codicon.gear, + precondition: CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE + }); + } + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + that.configureActiveOutputLogLevel(commandService); + } + })); + } + + private async configureActiveOutputLogLevel(commandService: ICommandService): Promise { + const channel = this.outputService.getActiveChannel(); + if (channel) { + await commandService.executeCommand(SetLogLevelAction.ID, channel.id); + } + } + private registerShowLogsAction(): void { this._register(registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 26fed6ffc4e..e004aaa86bc 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT } from 'vs/workbench/services/output/common/output'; +import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry, ACTIVE_OUTPUT_CHANNEL_CONTEXT, CONTEXT_ACTIVE_FILE_OUTPUT, CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE } from 'vs/workbench/services/output/common/output'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/browser/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; @@ -21,6 +21,7 @@ import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView'; import { IOutputChannelModelService } from 'vs/workbench/contrib/output/common/outputChannelModelService'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -74,6 +75,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private readonly activeOutputChannelContext: IContextKey; private readonly activeFileOutputChannelContext: IContextKey; + private readonly activeOutputChannelLevelSettableContext: IContextKey; constructor( @IStorageService private readonly storageService: IStorageService, @@ -91,6 +93,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this._register(this.onActiveOutputChannel(channel => this.activeOutputChannelContext.set(channel))); this.activeFileOutputChannelContext = CONTEXT_ACTIVE_FILE_OUTPUT.bindTo(contextKeyService); + this.activeOutputChannelLevelSettableContext = CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE.bindTo(contextKeyService); // Register as text model content provider for output textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); @@ -196,7 +199,9 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private setActiveChannel(channel: OutputChannel | undefined): void { this.activeChannel = channel; - this.activeFileOutputChannelContext.set(!!channel?.outputChannelDescriptor?.file); + const descriptor = channel?.outputChannelDescriptor; + this.activeFileOutputChannelContext.set(!!descriptor?.file); + this.activeOutputChannelLevelSettableContext.set(descriptor !== undefined && SetLogLevelAction.isLevelSettable(descriptor)); if (this.activeChannel) { this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE, StorageTarget.MACHINE); diff --git a/src/vs/workbench/services/output/common/output.ts b/src/vs/workbench/services/output/common/output.ts index 012855aaaee..716bb3bcfd6 100644 --- a/src/vs/workbench/services/output/common/output.ts +++ b/src/vs/workbench/services/output/common/output.ts @@ -43,6 +43,8 @@ export const CONTEXT_IN_OUTPUT = new RawContextKey('inOutput', false); export const CONTEXT_ACTIVE_FILE_OUTPUT = new RawContextKey('activeLogOutput', false); +export const CONTEXT_ACTIVE_OUTPUT_LEVEL_SETTABLE = new RawContextKey('activeLogOutput.levelSettable', false); + export const CONTEXT_OUTPUT_SCROLL_LOCK = new RawContextKey(`outputView.scrollLock`, false); export const IOutputService = createDecorator('outputService'); From 760ee5b9976bc9ee647e5aa5ecfa26c42780fab4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 10:22:14 -0600 Subject: [PATCH 026/753] wip --- .../chat/browser/terminalChatController.ts | 65 ++++++++++++------- .../chat/browser/terminalChatWidget.ts | 64 ++++++------------ 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5392e1787dd..8308105d9f5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,8 +14,11 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -34,7 +37,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } // private _sessionCtor: CancelablePromise | undefined; - private _activeSession?: Session; + // private _activeSession?: Session; // private readonly _ctxHasActiveRequest: IContextKey; // private _isVisible: boolean = false; // private _strategy: EditStrategy | undefined; @@ -51,7 +54,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IConfigurationService private _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + @IChatAgentService private readonly _chatAgentService: IChatAgentService, // @IContextKeyService private readonly _contextKeyService: IContextKeyService, // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, @@ -93,7 +96,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr throw new Error('FindWidget expected terminal DOM to be initialized'); } - // this._instance.domElement?.appendChild(chatWidget.getDomNode()); if (this._lastLayoutDimensions) { chatWidget.layout(this._lastLayoutDimensions.width); } @@ -103,24 +105,41 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - // TODO: create session, deal with response - // this._activeSession = new Session(EditMode.Live, , this._instance); - // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - // const requestProps: IChatAgentRequest = { - // sessionId: 'sessionId', - // requestId: 'fake', - // agentId: 'terminal', - // message: this._chatWidget?.rawValue?.getValue() || '', - // // variables: variableData.variables, - // // command: agentSlashCommandPart?.command.name, - // // variables2: asVariablesData2(parsedRequest, variableData) - // }; - // const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, undefined, token); - // const rawResult = agentResult; - // const agentOrCommandFollowups = this._chatAgentService.getFollowups('terminal', agentResult, followupsCancelToken); - this._chatWidget?.rawValue?.acceptInput(); + let message = ''; + const progressCallback = (progress: IChatProgress) => { + // if (token.isCancellationRequested) { + // return; + // } + + + // gotProgress = true; + + if (progress.kind === 'content' || progress.kind === 'markdownContent') { + // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); + message += progress.content; + } else { + // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); + } + + // model.acceptResponseProgress(request, progress); + }; + const resolvedVariables: Record = {}; + + const requestProps: IChatAgentRequest = { + sessionId: generateUuid(), + requestId: generateUuid(), + agentId: 'terminal', + message: this._chatWidget?.rawValue?.input() || '', + variables: resolvedVariables, + variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } + }; + // TODO: use token + const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + console.log(agentResult); + console.log(message); + alert(message); + this._chatWidget?.rawValue?.setValue(); + // TODO: accessibility announcement, help dialog } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index ed78f6292e7..ee0a778cc0f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,11 +15,8 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS, MENU_CELL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -113,45 +110,26 @@ export class TerminalChatWidget extends Disposable { cancel(): void { // this._widget?.clear(); } - async acceptInput(): Promise { - // this._widget?.acceptInput(); - // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); - - // if (!this._model) { - // throw new Error('Could not start chat session'); - // } - // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); - // this._activeSession = new Session(EditMode.Live, , this._instance); - // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - const progressCallback = (progress: IChatProgress) => { - // if (token.isCancellationRequested) { - // return; - // } - console.log(progress); - // gotProgress = true; - - if (progress.kind === 'content' || progress.kind === 'markdownContent') { - // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); - } else { - // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); - } - - // model.acceptResponseProgress(request, progress); - }; - const requestProps: IChatAgentRequest = { - sessionId: generateUuid(), - requestId: generateUuid(), - agentId: 'terminal', - message: this._inlineChatWidget.value || '', - variables: new Map() as any, - variables2: {} as any - }; - const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); - console.log(agentResult); - this._inlineChatWidget.value = ''; + input(): string { + return this._inlineChatWidget.value; + } + setValue(value?: string) { + this._inlineChatWidget.value = value ?? ''; } + // async acceptInput(): Promise { + // // this._widget?.acceptInput(); + // // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); + + // // if (!this._model) { + // // throw new Error('Could not start chat session'); + // // } + // // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); + // // this._activeSession = new Session(EditMode.Live, , this._instance); + // // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + // // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); + // // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + // this._inlineChatWidget.value = ''; + // } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); } From b998b6432bd6c080a26b59ccd8a03f69b06a0169 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:30:12 -0800 Subject: [PATCH 027/753] Add menus for terminal chat to the terminalContrib --- src/vs/platform/actions/common/actions.ts | 1 - .../chat/browser/terminal.chat.contribution.ts | 11 +++++++++-- .../chat/browser/terminalChat.ts | 12 ++++++++++++ .../chat/browser/terminalChatWidget.ts | 17 ++++++++--------- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 3102938a3be..0b3015ddf75 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -211,7 +211,6 @@ export class MenuId { static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); - static readonly TerminalChat = new MenuId('TerminalChat'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly AccessibleView = new MenuId('AccessibleView'); static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 1d909e6fc30..fd88e4c0982 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -15,6 +15,7 @@ import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); @@ -43,13 +44,19 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalCommandId.HideChat, - title: localize2('workbench.action.terminal.hideChat', 'Hide Chat'), + title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), weight: KeybindingWeight.WorkbenchContrib }, + icon: Codicon.close, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 2 + }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -81,7 +88,7 @@ registerActiveXtermAction({ primary: KeyCode.Enter }, menu: { - id: MenuId.TerminalChat, + id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, // when: TerminalContextKeys.chatSessionInProgress.negate(), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts new file mode 100644 index 00000000000..8b74aae7aeb --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MenuId } from 'vs/platform/actions/common/actions'; + +export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); +export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); +export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); +export const MENU_TERMINAL_CHAT_WIDGET_FEEDBACK = MenuId.for('terminalChatWidget.feedback'); +export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index ee0a778cc0f..7f30c3bca53 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,20 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/terminalChatWidget'; import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { localize } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { private _scopedInstantiationService: IInstantiationService; @@ -76,10 +75,10 @@ export class TerminalChatWidget extends Disposable { InlineChatWidget, fakeParentEditor, { - menuId: MenuId.TerminalChat, - widgetMenuId: MENU_CELL_CHAT_WIDGET, - statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + menuId: MENU_TERMINAL_CHAT_INPUT, + widgetMenuId: MENU_TERMINAL_CHAT_WIDGET, + statusMenuId: MENU_TERMINAL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); From e4e9562df987263f6315e4104a7e968bf45ef87a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:33:18 -0800 Subject: [PATCH 028/753] Clean up chat widget --- .../chat/browser/terminalChatWidget.ts | 30 ++++--------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 7f30c3bca53..330be0b35a5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,10 +31,10 @@ export class TerminalChatWidget extends Disposable { constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, - @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService) { + @IChatAgentService private readonly _chatAgentService: IChatAgentService + ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); @@ -44,24 +44,9 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); - // this._widget = this._register(this._scopedInstantiationService.createInstance( - // ChatWidget, - // { viewId: 'terminal' }, - // { supportsFileReferences: false, renderStyle: 'compact' }, - // { - // listForeground: editorForeground, - // listBackground: editorBackground, - // inputEditorBackground: inputBackground, - // resultEditorBackground: editorBackground - // })); - // this._widget.render(this._widgetContainer); - // this._register(this._widget.onDidFocus(() => this._chatWidgetFocused.set(true))); - + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - - // const editorConstructionOptions = this.inputEditorOptions.getEditorConstructionOptions(); - // this.setPlaceholderFontStyles(editorConstructionOptions.fontFamily!, editorConstructionOptions.fontSize!, editorConstructionOptions.lineHeight!); - const fakeParentEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, @@ -91,23 +76,18 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.layout(new Dimension(400, 150)); this._widgetContainer.classList.remove('hide'); - // this._widget.setVisible(true); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); - // this._widget.setInput('@terminal'); - // this._widget.setInputPlaceholder('Request a terminal command'); - // this._widget.focusInput(); this._inlineChatWidget.focus(); } hide(): void { this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); - // this._widget.clear(); this._instance.focus(); } cancel(): void { - // this._widget?.clear(); + // TODO: Impl } input(): string { return this._inlineChatWidget.value; From 32b3e7ed0e0fb868f3b2ede89ffa94b898f7a4c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:45:29 -0800 Subject: [PATCH 029/753] Standardize chat command ns, add feedback placeholders --- .../contrib/terminal/common/terminal.ts | 11 +-- .../browser/terminal.chat.contribution.ts | 70 +++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 5ed9e674af2..f2167db7f17 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,10 +500,13 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', - FocusChat = 'workbench.action.terminal.focusChat', - HideChat = 'workbench.action.terminal.hideChat', - MakeChatRequest = 'workbench.action.terminal.makeChatRequest', - CancelChat = 'workbench.action.terminal.cancelChat', + ChatFocus = 'workbench.action.terminal.chat.focus', + ChatHide = 'workbench.action.terminal.chat.close', + ChatMakeRequest = 'workbench.action.terminal.chat.makeRequest', + ChatCancel = 'workbench.action.terminal.chat.cancel', + ChatFeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', + ChatFeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', + ChatFeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index fd88e4c0982..2972104787f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -15,13 +15,13 @@ import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ - id: TerminalCommandId.FocusChat, + id: TerminalCommandId.ChatFocus, title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, @@ -43,7 +43,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.HideChat, + id: TerminalCommandId.ChatHide, title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -72,7 +72,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.MakeChatRequest, + id: TerminalCommandId.ChatMakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -105,7 +105,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.CancelChat, + id: TerminalCommandId.ChatCancel, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -124,3 +124,63 @@ registerActiveXtermAction({ contr?.chatWidget?.cancel(); } }); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackHelpful, + title: localize2('feedbackHelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 1, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackUnhelpful, + title: localize2('feedbackUnhelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 2, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalCommandId.ChatFeedbackReportIssue, + title: localize2('reportIssue', 'Report Issue'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 3, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); From 611fd5cf88efac77f8fee0aed18bbef8aaeef768 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:46:57 -0800 Subject: [PATCH 030/753] Move chat command IDs into contrib --- .../contrib/terminal/common/terminal.ts | 7 ------- .../chat/browser/terminal.chat.contribution.ts | 17 ++++++++--------- .../chat/browser/terminalChat.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f2167db7f17..fc925b646bb 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -500,13 +500,6 @@ export const enum TerminalCommandId { FontZoomIn = 'workbench.action.terminal.fontZoomIn', FontZoomOut = 'workbench.action.terminal.fontZoomOut', FontZoomReset = 'workbench.action.terminal.fontZoomReset', - ChatFocus = 'workbench.action.terminal.chat.focus', - ChatHide = 'workbench.action.terminal.chat.close', - ChatMakeRequest = 'workbench.action.terminal.chat.makeRequest', - ChatCancel = 'workbench.action.terminal.chat.cancel', - ChatFeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', - ChatFeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', - ChatFeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', // Developer commands diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 2972104787f..f8a1dd4b111 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -13,15 +13,14 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerActiveXtermAction({ - id: TerminalCommandId.ChatFocus, + id: TerminalChatCommandId.Focus, title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, @@ -43,7 +42,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatHide, + id: TerminalChatCommandId.Hide, title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -72,7 +71,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatMakeRequest, + id: TerminalChatCommandId.MakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -105,7 +104,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatCancel, + id: TerminalChatCommandId.Cancel, title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -126,7 +125,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackHelpful, + id: TerminalChatCommandId.FeedbackHelpful, title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -146,7 +145,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackUnhelpful, + id: TerminalChatCommandId.FeedbackUnhelpful, title: localize2('feedbackUnhelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -166,7 +165,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalCommandId.ChatFeedbackReportIssue, + id: TerminalChatCommandId.FeedbackReportIssue, title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 8b74aae7aeb..431a0e9c715 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -5,6 +5,16 @@ import { MenuId } from 'vs/platform/actions/common/actions'; +export const enum TerminalChatCommandId { + Focus = 'workbench.action.terminal.chat.focus', + Hide = 'workbench.action.terminal.chat.close', + MakeRequest = 'workbench.action.terminal.chat.makeRequest', + Cancel = 'workbench.action.terminal.chat.cancel', + FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', + FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', + FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', +} + export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); From 430db3ac9e053dada843e609422050da6938cf45 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:49:30 -0800 Subject: [PATCH 031/753] Move term chat actions into own file --- .../browser/terminal.chat.contribution.ts | 176 +---------------- .../chat/browser/terminalChatActions.ts | 182 ++++++++++++++++++ 2 files changed, 183 insertions(+), 175 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index f8a1dd4b111..a3a7b55757a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,183 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { localize2 } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); -registerActiveXtermAction({ - id: TerminalChatCommandId.Focus, - title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), - keybinding: { - primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), - weight: KeybindingWeight.WorkbenchContrib - }, - f1: true, - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - ), - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.reveal(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.Hide, - title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), - keybinding: { - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), - weight: KeybindingWeight.WorkbenchContrib - }, - icon: Codicon.close, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'main', - order: 2 - }, - f1: true, - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - ), - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.hide(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.MakeRequest, - title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - // TerminalContextKeys.chatInputHasText - ), - icon: Codicon.send, - keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_FOCUSED, - weight: KeybindingWeight.EditorCore + 7, - primary: KeyCode.Enter - }, - menu: { - id: MENU_TERMINAL_CHAT_INPUT, - group: 'main', - order: 1, - // when: TerminalContextKeys.chatSessionInProgress.negate(), - // TODO: - // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.acceptInput(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.Cancel, - title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.debugStop, - menu: { - id: MenuId.ChatExecute, - group: 'navigation', - }, - run: (_xterm, _accessor, activeInstance) => { - if (isDetachedTerminalInstance(activeInstance)) { - return; - } - const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackHelpful, - title: localize2('feedbackHelpful', 'Helpful'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 1, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackUnhelpful, - title: localize2('feedbackUnhelpful', 'Helpful'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 2, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); - -registerActiveXtermAction({ - id: TerminalChatCommandId.FeedbackReportIssue, - title: localize2('reportIssue', 'Report Issue'), - precondition: ContextKeyExpr.and( - ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - ), - icon: Codicon.thumbsup, - menu: { - id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 3, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, - run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl - } -}); +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts new file mode 100644 index 00000000000..161b8515478 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { localize2 } from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +registerActiveXtermAction({ + id: TerminalChatCommandId.Focus, + title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), + keybinding: { + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + weight: KeybindingWeight.WorkbenchContrib + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.reveal(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.Hide, + title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), + keybinding: { + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + weight: KeybindingWeight.WorkbenchContrib + }, + icon: Codicon.close, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 2 + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.hide(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.MakeRequest, + title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + // TerminalContextKeys.chatInputHasText + ), + icon: Codicon.send, + keybinding: { + when: TerminalContextKeys.chatRequestActive.negate(), + // TODO: + // when: CTX_INLINE_CHAT_FOCUSED, + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, + menu: { + id: MENU_TERMINAL_CHAT_INPUT, + group: 'main', + order: 1, + // when: TerminalContextKeys.chatSessionInProgress.negate(), + // TODO: + // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptInput(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.Cancel, + title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.debugStop, + menu: { + id: MenuId.ChatExecute, + group: 'navigation', + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.chatWidget?.cancel(); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackHelpful, + title: localize2('feedbackHelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 1, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackUnhelpful, + title: localize2('feedbackUnhelpful', 'Helpful'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 2, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.FeedbackReportIssue, + title: localize2('reportIssue', 'Report Issue'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + TerminalContextKeys.chatRequestActive, + ), + icon: Codicon.thumbsup, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, + group: 'inline', + order: 3, + // TODO: Fill in ctx + // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + }, + run: (_xterm, _accessor, activeInstance) => { + // TODO: Impl + } +}); From ab50eb72b355613d6f028289e41fccb4fcd9dc91 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:01:19 -0800 Subject: [PATCH 032/753] Fix compile error, add todo for layer breaker --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b41804eb8cc..74ec952ba1c 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,6 +54,7 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -528,7 +529,8 @@ export class StartVoiceChatAction extends Action2 { order: -1 }, { - id: MenuId.TerminalChat, + // TODO: Fix layer breaker, chat can't depend on terminalContrib + id: MENU_TERMINAL_CHAT_INPUT, when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 From 283b211fc8544baaa577021b2d507fb966ac1975 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:09:21 -0600 Subject: [PATCH 033/753] alert single code block --- .../chat/browser/terminalChatController.ts | 13 ++++++++----- .../chat/browser/terminalChatWidget.ts | 4 +--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 8308105d9f5..c8f3ec69958 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -19,6 +19,7 @@ import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { marked } from 'vs/base/common/marked/marked'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -133,12 +134,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: resolvedVariables, variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } }; - // TODO: use token - const agentResult = await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); - console.log(agentResult); - console.log(message); - alert(message); this._chatWidget?.rawValue?.setValue(); + + // TODO: use token + await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + if (codeBlock) { + alert(codeBlock); + } // TODO: accessibility announcement, help dialog } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 330be0b35a5..4a5dbb55994 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -11,7 +11,6 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -32,8 +31,7 @@ export class TerminalChatWidget extends Disposable { private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); From 582b0bea5a9e5f9090573fa3a2b4dd8587030100 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:29:51 -0600 Subject: [PATCH 034/753] add response editor --- .../terminal/browser/media/terminal.css | 4 ++ .../chat/browser/terminalChatController.ts | 6 +-- .../chat/browser/terminalChatWidget.ts | 42 ++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 8fcb3c71172..9053a746f89 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -575,3 +575,7 @@ .monaco-workbench .terminal-inline-chat.hide { visibility: hidden; } + +.monaco-workbench .terminal-inline-chat-response.hide { + visibility: hidden; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c8f3ec69958..05383e3e7f8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -34,7 +34,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; - + private _requestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } // private _sessionCtor: CancelablePromise | undefined; @@ -140,9 +140,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); if (codeBlock) { - alert(codeBlock); + // TODO: check the SR experience + this._chatWidget?.rawValue?.renderResponse(codeBlock, this._requestId++); } - // TODO: accessibility announcement, help dialog } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4a5dbb55994..88f17fe5c58 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -5,12 +5,16 @@ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -23,7 +27,8 @@ export class TerminalChatWidget extends Disposable { private _chatWidgetVisible: IContextKey; private readonly _inlineChatWidget: InlineChatWidget; - + private _responseWidget: CodeEditorWidget | undefined; + private _responseContainer: HTMLElement | undefined; private readonly _focusTracker: IFocusTracker; @@ -31,7 +36,9 @@ export class TerminalChatWidget extends Disposable { private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IModelService private readonly _modelService: IModelService ) { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); @@ -70,6 +77,33 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } + renderResponse(codeBlock: string, requestId: number): void { + this._chatAccessibilityService.acceptResponse(codeBlock, requestId); + if (!this._responseWidget) { + this._responseContainer = document.createElement('div'); + this._responseContainer.classList.add('terminal-inline-chat-response'); + this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseContainer, {}, { isSimpleWidget: true }); + this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { + if (!model || !this._responseWidget) { + return; + } + this._responseWidget.setModel(model); + this._responseWidget.layout(new Dimension(400, 150)); + this._widgetContainer.prepend(this._responseContainer!); + }); + } else { + this._responseWidget.setValue(codeBlock); + } + this._responseContainer?.classList.remove('hide'); + } + + private async _getTextModel(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing && !existing.isDisposed()) { + return existing; + } + return this._modelService.createModel(resource.fragment, null, resource, false); + } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); @@ -79,6 +113,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { + this._responseContainer?.classList.add('hide'); this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); @@ -92,6 +127,9 @@ export class TerminalChatWidget extends Disposable { } setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; + if (!value) { + this._responseContainer?.classList.add('hide'); + } } // async acceptInput(): Promise { // // this._widget?.acceptInput(); From c4519ce005d7e3f1d52a280d022b1cab5df4aa00 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:44:13 -0600 Subject: [PATCH 035/753] wip render message when no code block comes through --- .../terminal/browser/media/terminal.css | 4 +++ .../chat/browser/terminalChatController.ts | 5 +++- .../chat/browser/terminalChatWidget.ts | 26 ++++++++++++------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 9053a746f89..ab84f0258b1 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -579,3 +579,7 @@ .monaco-workbench .terminal-inline-chat-response.hide { visibility: hidden; } + +.monaco-workbench .terminal-inline-chat-response.message { + width: 400px !important; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 05383e3e7f8..c4755a08c47 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -139,9 +139,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: use token await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + this._requestId++; if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderResponse(codeBlock, this._requestId++); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); + } else { + this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 88f17fe5c58..476c808dce1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -28,7 +28,7 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; private _responseWidget: CodeEditorWidget | undefined; - private _responseContainer: HTMLElement | undefined; + private _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; @@ -49,6 +49,10 @@ export class TerminalChatWidget extends Disposable { this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); + this._responseElement = document.createElement('div'); + this._responseElement.classList.add('terminal-inline-chat-response'); + this._widgetContainer.prepend(this._responseElement); + // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); @@ -77,24 +81,28 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } - renderResponse(codeBlock: string, requestId: number): void { + renderTerminalCommand(codeBlock: string, requestId: number): void { + this._responseElement.classList.remove('message', 'hide'); this._chatAccessibilityService.acceptResponse(codeBlock, requestId); if (!this._responseWidget) { - this._responseContainer = document.createElement('div'); - this._responseContainer.classList.add('terminal-inline-chat-response'); - this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseContainer, {}, { isSimpleWidget: true }); + this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; } this._responseWidget.setModel(model); this._responseWidget.layout(new Dimension(400, 150)); - this._widgetContainer.prepend(this._responseContainer!); }); } else { this._responseWidget.setValue(codeBlock); } - this._responseContainer?.classList.remove('hide'); + } + + renderMessage(message: string, requestId: number): void { + this._responseElement?.classList.remove('hide'); + this._responseElement.classList.add('message'); + this._chatAccessibilityService.acceptResponse(message, requestId); + this._responseElement.textContent = message; } private async _getTextModel(resource: URI): Promise { @@ -113,7 +121,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this._responseContainer?.classList.add('hide'); + this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); this._chatWidgetFocused.set(false); this._chatWidgetVisible.set(false); @@ -128,7 +136,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this._responseContainer?.classList.add('hide'); + this._responseElement?.classList.add('hide'); } } // async acceptInput(): Promise { From c9c98f010a12da31afa70b7b60b280260f911bac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:56:08 -0600 Subject: [PATCH 036/753] get chat cancellation action to show up --- .../chat/browser/terminalChatActions.ts | 7 ++--- .../chat/browser/terminalChatController.ts | 29 +++++++++++++++---- .../chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 161b8515478..b9544ce5feb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize2 } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; @@ -73,7 +72,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - // TerminalContextKeys.chatInputHasText + TerminalContextKeys.chatRequestActive.negate() ), icon: Codicon.send, keybinding: { @@ -109,8 +108,8 @@ registerActiveXtermAction({ ), icon: Codicon.debugStop, menu: { - id: MenuId.ChatExecute, - group: 'navigation', + id: MENU_TERMINAL_CHAT_INPUT, + group: 'main', }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c4755a08c47..5465c3abed3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,9 +17,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { marked } from 'vs/base/common/marked/marked'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -39,7 +42,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr // private _sessionCtor: CancelablePromise | undefined; // private _activeSession?: Session; - // private readonly _ctxHasActiveRequest: IContextKey; + private readonly _ctxHasActiveRequest!: IContextKey; + + private _cancellationTokenSource!: CancellationTokenSource; + // private _isVisible: boolean = false; // private _strategy: EditStrategy | undefined; @@ -56,7 +62,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @ITerminalService private readonly _terminalService: ITerminalService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, - // @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService // @IInstantiationService private readonly _instantiationService: IInstantiationService, // @ICommandService private readonly _commandService: ICommandService, // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService @@ -65,7 +72,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - // this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._cancellationTokenSource = new CancellationTokenSource(); // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); } @@ -105,8 +113,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } + cancel(): void { + this._cancellationTokenSource.cancel(); + } + async acceptInput(): Promise { let message = ''; + this._chatAccessibilityService.acceptRequest(); + this._ctxHasActiveRequest.set(true); + const cancellationToken = this._cancellationTokenSource.token; const progressCallback = (progress: IChatProgress) => { // if (token.isCancellationRequested) { // return; @@ -137,15 +152,19 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.setValue(); // TODO: use token - await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], CancellationToken.None); + await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; + if (cancellationToken.isCancellationRequested) { + return; + } if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); } else { this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } + this._ctxHasActiveRequest.set(false); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 476c808dce1..4c84b3656bc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,7 +31,6 @@ export class TerminalChatWidget extends Disposable { private _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; - constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @@ -129,6 +128,7 @@ export class TerminalChatWidget extends Disposable { } cancel(): void { // TODO: Impl + this._inlineChatWidget.value = ''; } input(): string { return this._inlineChatWidget.value; From cf634dfa386325d041f05d86cf24a2654defc04a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:57:24 -0600 Subject: [PATCH 037/753] only show if active --- .../terminalContrib/chat/browser/terminalChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index b9544ce5feb..57e3cee5de0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -86,7 +86,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - // when: TerminalContextKeys.chatSessionInProgress.negate(), + when: TerminalContextKeys.chatRequestActive.negate(), // TODO: // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, @@ -110,6 +110,7 @@ registerActiveXtermAction({ menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', + when: TerminalContextKeys.chatRequestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 7d80e6d9396dc3bf91d8880cd65758a31e3cef81 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:02:49 -0600 Subject: [PATCH 038/753] clean up --- .../chat/browser/terminalChatActions.ts | 2 -- .../chat/browser/terminalChatController.ts | 15 +++------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 57e3cee5de0..bb1a012026a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -87,8 +87,6 @@ registerActiveXtermAction({ group: 'main', order: 1, when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.isEqualTo(false) }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5465c3abed3..a9babf3a5de 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -123,21 +123,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; const progressCallback = (progress: IChatProgress) => { - // if (token.isCancellationRequested) { - // return; - // } - - - // gotProgress = true; + if (cancellationToken.isCancellationRequested) { + return; + } if (progress.kind === 'content' || progress.kind === 'markdownContent') { - // this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); message += progress.content; - } else { - // this.trace('sendRequest', `Provider returned progress: ${JSON.stringify(progress)}`); } - - // model.acceptResponseProgress(request, progress); }; const resolvedVariables: Record = {}; @@ -151,7 +143,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; this._chatWidget?.rawValue?.setValue(); - // TODO: use token await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; From 44d4700aae178618cbb9aa6f888e0b81db5e66f2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:03:37 -0600 Subject: [PATCH 039/753] remove things --- .../chat/browser/terminalChatController.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index a9babf3a5de..22810165660 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -40,20 +40,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _requestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - // private _sessionCtor: CancelablePromise | undefined; - // private _activeSession?: Session; private readonly _ctxHasActiveRequest!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; - // private _isVisible: boolean = false; - // private _strategy: EditStrategy | undefined; - - // private _inlineChatListener: IDisposable | undefined; - // private _toolbar: MenuWorkbenchToolBar | undefined; - // private readonly _ctxLastResponseType: IContextKey; - // private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); - constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -64,9 +54,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService - // @IInstantiationService private readonly _instantiationService: IInstantiationService, - // @ICommandService private readonly _commandService: ICommandService, - // @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -74,7 +61,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._cancellationTokenSource = new CancellationTokenSource(); - // this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); } layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { From c4af34eae27344c9bf3030f7c84d2f2e0cf69d8d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:04:44 -0600 Subject: [PATCH 040/753] more clean up --- .../terminalContrib/chat/browser/terminalChatController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 22810165660..85d7ba44296 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -108,6 +108,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatAccessibilityService.acceptRequest(); this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; + const agentId = 'terminal'; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; @@ -122,14 +123,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId: generateUuid(), - agentId: 'terminal', + agentId, message: this._chatWidget?.rawValue?.input() || '', variables: resolvedVariables, variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } }; this._chatWidget?.rawValue?.setValue(); - await this._chatAgentService.invokeAgent('terminal', requestProps, progressCallback, [], cancellationToken); + await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; if (cancellationToken.isCancellationRequested) { From a9cfba287544762341fa7a2e84fce90f568f4cbb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:11:41 -0600 Subject: [PATCH 041/753] catch error --- .../chat/browser/terminalChatController.ts | 11 ++++++++++- .../chat/browser/terminalChatWidget.ts | 18 ++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 85d7ba44296..432d5fd0cbf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -117,6 +117,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { message += progress.content; } + this._chatWidget?.rawValue?.updateProgress(progress); }; const resolvedVariables: Record = {}; @@ -130,7 +131,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr }; this._chatWidget?.rawValue?.setValue(); - await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + try { + await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + } catch (e) { + // Provider is not ready + this._ctxHasActiveRequest.set(false); + this._chatWidget?.rawValue?.updateProgress(); + return; + } const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); this._requestId++; if (cancellationToken.isCancellationRequested) { @@ -143,6 +151,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(message, this._requestId); } this._ctxHasActiveRequest.set(false); + this._chatWidget?.rawValue?.updateProgress(); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4c84b3656bc..985fc071cb2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,6 +15,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -139,20 +140,9 @@ export class TerminalChatWidget extends Disposable { this._responseElement?.classList.add('hide'); } } - // async acceptInput(): Promise { - // // this._widget?.acceptInput(); - // // this._chatModel ??= this._chatService.startSession('terminal', CancellationToken.None); - - // // if (!this._model) { - // // throw new Error('Could not start chat session'); - // // } - // // this._chatService?.sendRequest(this._chatModel?.sessionId!, this._inlineChatWidget.value); - // // this._activeSession = new Session(EditMode.Live, , this._instance); - // // const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; - // // request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - // // const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - // this._inlineChatWidget.value = ''; - // } + updateProgress(progress?: IChatProgress): void { + this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); + } layout(width: number): void { // this._widget?.layout(100, width < 300 ? 300 : width); } From 1617ebe843cb56d7ff8fdd2c17c3fbaa2de9f7ec Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:24:10 -0600 Subject: [PATCH 042/753] ensure terminal agent is registered --- .../contrib/terminal/common/terminalContextKey.ts | 4 ++++ .../chat/browser/terminalChatActions.ts | 4 +++- .../chat/browser/terminalChatController.ts | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 78fd7df6712..7f777f94f62 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -43,6 +43,7 @@ export const enum TerminalContextKeyStrings { ChatVisible = 'terminalChatVisible', ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', + ChatAgentRegistered = 'terminalChatAgentRegistered', } export namespace TerminalContextKeys { @@ -175,4 +176,7 @@ export namespace TerminalContextKeys { /** Whether the chat input has text */ export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + + /** Whether the terminal chat agent has been registered */ + export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index bb1a012026a..dcf2aec0617 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -72,7 +72,8 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate() + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered ), icon: Codicon.send, keybinding: { @@ -103,6 +104,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatAgentRegistered ), icon: Codicon.debugStop, menu: { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 432d5fd0cbf..b3b9c002aad 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -41,6 +41,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } private readonly _ctxHasActiveRequest!: IContextKey; + private readonly _ctxHasTerminalAgent!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; @@ -60,6 +61,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + if (!this._chatAgentService.hasAgent('terminal')) { + this._register(this._chatAgentService.onDidChangeAgents(() => { + if (this._chatAgentService.getAgent('terminal')) { + this._ctxHasTerminalAgent.set(true); + } + })); + } else { + this._ctxHasTerminalAgent.set(true); + } this._cancellationTokenSource = new CancellationTokenSource(); } From d5527a86b26e1586b487dfaadc43001c32b4bd64 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:56:50 -0600 Subject: [PATCH 043/753] add view in chat action even though it doesn't work atm --- .../inlineChat/browser/inlineChatActions.ts | 17 +++++++++++++---- .../terminal/browser/media/terminal.css | 2 +- .../chat/browser/terminalChatActions.ts | 8 ++++---- .../chat/browser/terminalChatController.ts | 18 +++++++++++------- .../chat/browser/terminalChatWidget.ts | 11 +++++------ 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..635233002c4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,6 +30,9 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +// TODO: fix eslint-disable-next-line local/code-import-patterns +import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -682,13 +685,19 @@ export class ViewInChatAction extends AbstractInlineChatAction { id: ACTION_VIEW_IN_CHAT, title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, - precondition: CTX_INLINE_CHAT_VISIBLE, - menu: { + precondition: ContextKeyExpr.or(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_VISIBLE), + menu: [{ id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_VISIBLE), group: '0_main', order: 1 - } + }, + { + id: MENU_TERMINAL_CHAT_INPUT, + when: ContextKeyExpr.and(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages)), + group: 'inline', + order: 1 + }] }); } override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index ab84f0258b1..1aaf1516c5e 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -580,6 +580,6 @@ visibility: hidden; } -.monaco-workbench .terminal-inline-chat-response.message { +.monaco-workbench .terminal-inline-chat .chatMessageContent { width: 400px !important; } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index dcf2aec0617..60032254028 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,6 +9,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -73,13 +74,12 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), - // TODO: - // when: CTX_INLINE_CHAT_FOCUSED, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.EditorCore + 7, primary: KeyCode.Enter }, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index b3b9c002aad..9312e56e19b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -23,6 +23,7 @@ import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -37,11 +38,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; private _lastLayoutDimensions: IDimension | undefined; - private _requestId: number = 0; + private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } private readonly _ctxHasActiveRequest!: IContextKey; private readonly _ctxHasTerminalAgent!: IContextKey; + private readonly _ctxLastResponseType!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; @@ -54,7 +56,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IInstantiationService private readonly _instantiationService: IInstantiationService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -62,6 +64,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent('terminal')) { this._register(this._chatAgentService.onDidChangeAgents(() => { if (this._chatAgentService.getAgent('terminal')) { @@ -131,10 +134,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(progress); }; const resolvedVariables: Record = {}; - + const requestId = generateUuid(); const requestProps: IChatAgentRequest = { sessionId: generateUuid(), - requestId: generateUuid(), + requestId, agentId, message: this._chatWidget?.rawValue?.input() || '', variables: resolvedVariables, @@ -151,15 +154,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); - this._requestId++; + this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); } else { - this._chatWidget?.rawValue?.renderMessage(message, this._requestId); + this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); + this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); } this._ctxHasActiveRequest.set(false); this._chatWidget?.rawValue?.updateProgress(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 985fc071cb2..761708aec35 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; @@ -82,8 +83,8 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } renderTerminalCommand(codeBlock: string, requestId: number): void { - this._responseElement.classList.remove('message', 'hide'); this._chatAccessibilityService.acceptResponse(codeBlock, requestId); + this._responseElement.classList.remove('hide'); if (!this._responseWidget) { this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { @@ -98,11 +99,9 @@ export class TerminalChatWidget extends Disposable { } } - renderMessage(message: string, requestId: number): void { - this._responseElement?.classList.remove('hide'); - this._responseElement.classList.add('message'); - this._chatAccessibilityService.acceptResponse(message, requestId); - this._responseElement.textContent = message; + renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { + this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); + this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } private async _getTextModel(resource: URI): Promise { From 9af101d1807a52d95714f19621c86425b342ba01 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:59:40 -0600 Subject: [PATCH 044/753] revert View in Chat action since it doesn't work --- .../inlineChat/browser/inlineChatActions.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 635233002c4..85033c64166 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,9 +30,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -// TODO: fix eslint-disable-next-line local/code-import-patterns -import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -685,19 +682,13 @@ export class ViewInChatAction extends AbstractInlineChatAction { id: ACTION_VIEW_IN_CHAT, title: localize('viewInChat', 'View in Chat'), icon: Codicon.commentDiscussion, - precondition: ContextKeyExpr.or(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_VISIBLE), - menu: [{ + precondition: CTX_INLINE_CHAT_VISIBLE, + menu: { id: MENU_INLINE_CHAT_WIDGET_STATUS, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_VISIBLE), + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), group: '0_main', order: 1 - }, - { - id: MENU_TERMINAL_CHAT_INPUT, - when: ContextKeyExpr.and(TerminalContextKeys.chatVisible, CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages)), - group: 'inline', - order: 1 - }] + } }); } override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { From 7ad358bdb5ee2688a7df65fbd1381fb8ebffb0e7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 13:14:43 -0600 Subject: [PATCH 045/753] add accept command action --- .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 36 ++++++++++++++++++- .../chat/browser/terminalChatController.ts | 5 +++ .../chat/browser/terminalChatWidget.ts | 13 +++++-- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 431a0e9c715..882baf8c26b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -13,6 +13,7 @@ export const enum TerminalChatCommandId { FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', + AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', } export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 60032254028..73ebf9631a9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,7 +9,7 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -67,6 +67,40 @@ registerActiveXtermAction({ } }); + + + +registerActiveXtermAction({ + id: TerminalChatCommandId.AcceptCommand, + title: localize2('workbench.action.terminal.acceptCommand', 'Accept Command'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits) + ), + icon: Codicon.check, + keybinding: { + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + weight: KeybindingWeight.EditorCore + 7, + primary: KeyCode.Enter + }, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 0, + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptCommand(); + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9312e56e19b..0a943b362fd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -161,6 +161,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); + this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyEdits); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); @@ -169,6 +170,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(); } + acceptCommand(): void { + this._chatWidget?.rawValue?.acceptCommand(); + } + reveal(): void { this._chatWidget?.rawValue?.reveal(); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 761708aec35..0ccea8a61cd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,8 +91,10 @@ export class TerminalChatWidget extends Disposable { if (!model || !this._responseWidget) { return; } + this._responseWidget.layout(new Dimension(400, 0)); this._responseWidget.setModel(model); - this._responseWidget.layout(new Dimension(400, 150)); + const height = this._responseWidget.getContentHeight(); + this._responseWidget.layout(new Dimension(400, height)); }); } else { this._responseWidget.setValue(codeBlock); @@ -113,7 +115,6 @@ export class TerminalChatWidget extends Disposable { } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); - this._widgetContainer.classList.remove('hide'); this._chatWidgetFocused.set(true); this._chatWidgetVisible.set(true); @@ -139,6 +140,14 @@ export class TerminalChatWidget extends Disposable { this._responseElement?.classList.add('hide'); } } + acceptCommand(): void { + const value = this._responseWidget?.getValue(); + if (!value) { + return; + } + this._instance.sendText(value, false, true); + this.hide(); + } updateProgress(progress?: IChatProgress): void { this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); } From 9992d7da51e1d97858721263038a82e6c80149cd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:18:10 -0800 Subject: [PATCH 046/753] Fix terminal voice menu ids --- .../chat/electron-sandbox/actions/voiceChatActions.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 74ec952ba1c..e41ae049b6e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,7 +54,6 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { MENU_TERMINAL_CHAT_INPUT } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -529,8 +528,7 @@ export class StartVoiceChatAction extends Action2 { order: -1 }, { - // TODO: Fix layer breaker, chat can't depend on terminalContrib - id: MENU_TERMINAL_CHAT_INPUT, + id: MenuId.for('terminalChatInput'), when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 @@ -580,6 +578,11 @@ export class InstallVoiceChatAction extends Action2 { when: HasSpeechProvider.negate(), group: 'main', order: -1 + }, { + id: MenuId.for('terminalChatInput'), + when: HasSpeechProvider.negate(), + group: 'main', + order: -1 }] }); } From 702535987ca16b9ba1ed8d86443a1d24a5400538 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 13:29:56 -0600 Subject: [PATCH 047/753] add accept command keybinding --- .../terminal/common/terminalContextKey.ts | 4 +++ .../chat/browser/terminalChatActions.ts | 8 ++--- .../chat/browser/terminalChatController.ts | 2 +- .../chat/browser/terminalChatWidget.ts | 35 ++++++++++++------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 7f777f94f62..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -44,6 +44,7 @@ export const enum TerminalContextKeyStrings { ChatActiveRequest = 'terminalChatActiveRequest', ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', + ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', } export namespace TerminalContextKeys { @@ -179,4 +180,7 @@ export namespace TerminalContextKeys { /** Whether the terminal chat agent has been registered */ export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + + /** Whether the chat response editor is focused */ + export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 73ebf9631a9..37cf45c77fc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -78,19 +78,19 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits) + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) ), icon: Codicon.check, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseEditorFocused, TerminalContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.EditorCore + 7, - primary: KeyCode.Enter + primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyEdits), + when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 0a943b362fd..00b117d6661 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -65,6 +65,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + if (!this._chatAgentService.hasAgent('terminal')) { this._register(this._chatAgentService.onDidChangeAgents(() => { if (this._chatAgentService.getAgent('terminal')) { @@ -161,7 +162,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); - this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyEdits); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0ccea8a61cd..41332e575fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -23,15 +23,16 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { - private _scopedInstantiationService: IInstantiationService; - private _widgetContainer: HTMLElement; - private _chatWidgetFocused: IContextKey; - private _chatWidgetVisible: IContextKey; + private readonly _scopedInstantiationService: IInstantiationService; + private readonly _widgetContainer: HTMLElement; + private readonly _ctxChatWidgetFocused: IContextKey; + private readonly _ctxChatWidgetVisible: IContextKey; + private readonly _ctxResponseEditorFocused!: IContextKey; private readonly _inlineChatWidget: InlineChatWidget; - private _responseWidget: CodeEditorWidget | undefined; - private _responseElement: HTMLElement; + private readonly _responseElement: HTMLElement; private readonly _focusTracker: IFocusTracker; + private _responseWidget: CodeEditorWidget | undefined; constructor( private readonly _container: HTMLElement, @@ -44,8 +45,10 @@ export class TerminalChatWidget extends Disposable { super(); const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._ctxChatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._ctxChatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._ctxResponseEditorFocused = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._widgetContainer = document.createElement('div'); this._widgetContainer.classList.add('terminal-inline-chat'); this._container.appendChild(this._widgetContainer); @@ -86,7 +89,7 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { - this._responseWidget = this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true }); + this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; @@ -96,6 +99,12 @@ export class TerminalChatWidget extends Disposable { const height = this._responseWidget.getContentHeight(); this._responseWidget.layout(new Dimension(400, height)); }); + this._register(this._responseWidget.onDidFocusEditorText(() => { + this._ctxResponseEditorFocused.set(true); + })); + this._register(this._responseWidget.onDidBlurEditorText(() => { + this._ctxResponseEditorFocused.set(false); + })); } else { this._responseWidget.setValue(codeBlock); } @@ -116,15 +125,15 @@ export class TerminalChatWidget extends Disposable { reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); this._widgetContainer.classList.remove('hide'); - this._chatWidgetFocused.set(true); - this._chatWidgetVisible.set(true); + this._ctxChatWidgetFocused.set(true); + this._ctxChatWidgetVisible.set(true); this._inlineChatWidget.focus(); } hide(): void { this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); - this._chatWidgetFocused.set(false); - this._chatWidgetVisible.set(false); + this._ctxChatWidgetFocused.set(false); + this._ctxChatWidgetVisible.set(false); this._instance.focus(); } cancel(): void { From b93cd296b072da5ae170af15695e00d865c449f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:15:54 -0800 Subject: [PATCH 048/753] Working terminal voice chat --- .../actions/media/voiceChatActions.css | 9 ++- .../actions/voiceChatActions.ts | 77 ++++++++++++++++--- .../electron-sandbox/chat.contribution.ts | 3 +- .../chat/browser/terminalChatController.ts | 52 +++++++++++++ .../chat/browser/terminalChatWidget.ts | 8 ++ 5 files changed, 136 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index 7a43cef7d12..87b1d68f988 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -7,7 +7,8 @@ * Replace with "microphone" icon. */ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ec1c"; } @@ -15,7 +16,8 @@ * Clear animation styles when reduced motion is enabled. */ .monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), +.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { animation: none; } @@ -23,6 +25,7 @@ * Replace with "stop" icon when reduced motion is enabled. */ .monaco-workbench.reduce-motion .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, -.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { +.monaco-workbench.reduce-motion .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench.reduce-motion .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { content: "\ead7"; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index e41ae049b6e..aeb85706fc6 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -54,18 +54,23 @@ import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +// TODO: The chat needs to move into contrib/terminal/ as we don't want anything importing from terminalContrib/ +// eslint-disable-next-line local/code-import-patterns +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); const CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS = new RawContextKey('quickVoiceChatInProgress', false, { type: 'boolean', description: localize('quickVoiceChatInProgress', "True when voice recording from microphone is in progress for quick chat.") }); const CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS = new RawContextKey('inlineVoiceChatInProgress', false, { type: 'boolean', description: localize('inlineVoiceChatInProgress', "True when voice recording from microphone is in progress for inline chat.") }); +const CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS = new RawContextKey('terminalVoiceChatInProgress', false, { type: 'boolean', description: localize('terminalVoiceChatInProgress', "True when voice recording from microphone is in progress for terminal chat.") }); const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voiceChatInViewInProgress', false, { type: 'boolean', description: localize('voiceChatInViewInProgress', "True when voice recording from microphone is in progress in the chat view.") }); const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); const CanVoiceChat = ContextKeyExpr.and(CONTEXT_PROVIDER_EXISTS, HasSpeechProvider); -type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor'; +type VoiceChatSessionContext = 'inline' | 'terminal' | 'quick' | 'view' | 'editor'; interface IVoiceChatSessionController { @@ -89,8 +94,9 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'quick'): Promise; static create(accessor: ServicesAccessor, context: 'view'): Promise; static create(accessor: ServicesAccessor, context: 'focused'): Promise; - static create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise; - static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { + static create(accessor: ServicesAccessor, context: 'terminal'): Promise; + static create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise; + static async create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); @@ -98,6 +104,7 @@ class VoiceChatSessionControllerFactory { const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); + const terminalService = accessor.get(ITerminalService); // Currently Focused Context if (context === 'focused') { @@ -132,6 +139,15 @@ class VoiceChatSessionControllerFactory { return VoiceChatSessionControllerFactory.doCreateForInlineChat(inlineChat); } } + + // Try with the terminal chat + const activeInstance = terminalService.activeInstance; + if (activeInstance) { + const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + if (terminalChat?.hasFocus()) { + return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); + } + } } // View Chat @@ -156,6 +172,17 @@ class VoiceChatSessionControllerFactory { } } + // Terminal Chat + if (context === 'terminal') { + const activeInstance = terminalService.activeInstance; + if (activeInstance) { + const terminalChat = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + if (terminalChat) { + return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); + } + } + } + // Quick Chat if (context === 'quick') { quickChatService.open(); @@ -224,6 +251,20 @@ class VoiceChatSessionControllerFactory { clearInputPlaceholder: () => inlineChat.resetPlaceholder() }; } + + private static doCreateForTerminalChat(terminalChat: TerminalChatController): IVoiceChatSessionController { + return { + context: 'terminal', + onDidAcceptInput: terminalChat.onDidAcceptInput, + onDidCancelInput: terminalChat.onDidCancelInput, + focusInput: () => terminalChat.focus(), + acceptInput: () => terminalChat.acceptInput(), + updateInput: text => terminalChat.updateInput(text, false), + getInput: () => terminalChat.getInput(), + setInputPlaceholder: text => terminalChat.setPlaceholder(text), + clearInputPlaceholder: () => terminalChat.resetPlaceholder() + }; + } } interface IVoiceChatSession { @@ -255,6 +296,7 @@ class VoiceChatSessions { private quickVoiceChatInProgressKey = CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); private inlineVoiceChatInProgressKey = CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); + private terminalVoiceChatInProgressKey = CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInViewInProgressKey = CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInEditorInProgressKey = CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.bindTo(this.contextKeyService); @@ -345,6 +387,9 @@ class VoiceChatSessions { case 'inline': this.inlineVoiceChatInProgressKey.set(true); break; + case 'terminal': + this.terminalVoiceChatInProgressKey.set(true); + break; case 'quick': this.quickVoiceChatInProgressKey.set(true); break; @@ -387,6 +432,7 @@ class VoiceChatSessions { this.quickVoiceChatInProgressKey.set(false); this.inlineVoiceChatInProgressKey.set(false); + this.terminalVoiceChatInProgressKey.set(false); this.voiceChatInViewInProgressKey.set(false); this.voiceChatInEditorInProgressKey.set(false); } @@ -510,7 +556,8 @@ export class StartVoiceChatAction extends Action2 { CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate(), - CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate() + CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate(), + CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.negate() ), primary: KeyMod.CtrlCmd | KeyCode.KeyI }, @@ -529,7 +576,7 @@ export class StartVoiceChatAction extends Action2 { }, { id: MenuId.for('terminalChatInput'), - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS.negate()), + when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS.negate()), group: 'main', order: -1 }] @@ -629,7 +676,7 @@ class BaseStopListeningAction extends Action2 { constructor( desc: { id: string; icon?: ThemeIcon; f1?: boolean }, - private readonly target: 'inline' | 'quick' | 'view' | 'editor' | undefined, + private readonly target: 'inline' | 'terminal' | 'quick' | 'view' | 'editor' | undefined, context: RawContextKey, menu: MenuId | undefined, group: 'navigation' | 'main' = 'navigation' @@ -703,6 +750,15 @@ export class StopListeningInInlineChatAction extends BaseStopListeningAction { } } +export class StopListeningInTerminalChatAction extends BaseStopListeningAction { + + static readonly ID = 'workbench.action.chat.stopListeningInTerminalChat'; + + constructor() { + super({ id: StopListeningInTerminalChatAction.ID, icon: spinningLoading }, 'terminal', CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS, MenuId.for('terminalChatInput'), 'main'); + } +} + export class StopListeningAndSubmitAction extends Action2 { static readonly ID = 'workbench.action.chat.stopListeningAndSubmit'; @@ -745,7 +801,8 @@ registerThemingParticipant((theme, collector) => { // Show a "microphone" icon when recording is in progress that glows via outline. collector.addRule(` .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { color: ${activeRecordingColor}; outline: 1px solid ${activeRecordingColor}; outline-offset: -1px; @@ -754,7 +811,8 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { position: absolute; outline: 1px solid ${activeRecordingColor}; outline-offset: 2px; @@ -764,7 +822,8 @@ registerThemingParticipant((theme, collector) => { } .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, - .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, + .monaco-workbench:not(.reduce-motion) .terminal-inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { content: ''; position: absolute; outline: 1px solid ${activeRecordingDimmedColor}; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 2f69e1b13f8..02383b6b3f3 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction, StopListeningInTerminalChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; @@ -21,5 +21,6 @@ registerAction2(StopListeningInChatViewAction); registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); +registerAction2(StopListeningInTerminalChatAction); registerWorkbenchContribution2(KeywordActivationContribution.ID, KeywordActivationContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 00b117d6661..86287257f8f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -24,6 +24,18 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Emitter, Event } from 'vs/base/common/event'; + +const enum Message { + NONE = 0, + ACCEPT_SESSION = 1 << 0, + CANCEL_SESSION = 1 << 1, + PAUSE_SESSION = 1 << 2, + CANCEL_REQUEST = 1 << 3, + CANCEL_INPUT = 1 << 4, + ACCEPT_INPUT = 1 << 5, + RERUN_INPUT = 1 << 6, +} export class TerminalChatController extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.Chat'; @@ -47,6 +59,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _cancellationTokenSource!: CancellationTokenSource; + private _messages = this._store.add(new Emitter()); + + readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); + readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -118,6 +135,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource.cancel(); } + setPlaceholder(text: string): void { + // TODO: Impl + // this._forcedPlaceholder = text; + // this._updatePlaceholder(); + } + + resetPlaceholder(): void { + // TODO: Impl + // this._forcedPlaceholder = undefined; + // this._updatePlaceholder(); + } + async acceptInput(): Promise { let message = ''; this._chatAccessibilityService.acceptRequest(); @@ -168,6 +197,29 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._ctxHasActiveRequest.set(false); this._chatWidget?.rawValue?.updateProgress(); + this._messages.fire(Message.ACCEPT_INPUT); + } + + updateInput(text: string, selectAll = true): void { + const widget = this._chatWidget?.rawValue?.inlineChatWidget; + if (widget) { + widget.value = text; + if (selectAll) { + widget.selectAll(); + } + } + } + + getInput(): string { + return this._chatWidget?.rawValue?.input() ?? ''; + } + + focus(): void { + this._chatWidget?.rawValue?.focus(); + } + + hasFocus(): boolean { + return !!this._chatWidget?.rawValue?.hasFocus(); } acceptCommand(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 41332e575fb..6374e1cc991 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -34,6 +34,8 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; private _responseWidget: CodeEditorWidget | undefined; + public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } + constructor( private readonly _container: HTMLElement, private readonly _instance: ITerminalInstance, @@ -140,6 +142,12 @@ export class TerminalChatWidget extends Disposable { // TODO: Impl this._inlineChatWidget.value = ''; } + focus(): void { + this._inlineChatWidget.focus(); + } + hasFocus(): boolean { + return this._inlineChatWidget.hasFocus(); + } input(): string { return this._inlineChatWidget.value; } From 3960db9ce3b00189765b88601fefa1913ffdd1e9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:19:10 -0800 Subject: [PATCH 049/753] Add voice placeholder --- .../chat/browser/terminalChatController.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 86287257f8f..f3d7ef85326 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -135,16 +135,29 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource.cancel(); } + private _forcedPlaceholder: string | undefined = undefined; + + private _updatePlaceholder(): void { + const inlineChatWidget = this._chatWidget?.rawValue?.inlineChatWidget; + if (inlineChatWidget) { + inlineChatWidget.placeholder = this._getPlaceholderText(); + } + } + + private _getPlaceholderText(): string { + return this._forcedPlaceholder ?? ''; + // TODO: Pass through session placeholder + // return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; + } + setPlaceholder(text: string): void { - // TODO: Impl - // this._forcedPlaceholder = text; - // this._updatePlaceholder(); + this._forcedPlaceholder = text; + this._updatePlaceholder(); } resetPlaceholder(): void { - // TODO: Impl - // this._forcedPlaceholder = undefined; - // this._updatePlaceholder(); + this._forcedPlaceholder = undefined; + this._updatePlaceholder(); } async acceptInput(): Promise { From bf1bb656259791cf84051759a1d264409b5239c0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:50:05 -0600 Subject: [PATCH 050/753] clear on hide --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 6374e1cc991..0e8b82043ea 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -134,6 +134,8 @@ export class TerminalChatWidget extends Disposable { hide(): void { this._responseElement?.classList.add('hide'); this._widgetContainer.classList.add('hide'); + this._inlineChatWidget.value = ''; + this._responseWidget?.setValue(''); this._ctxChatWidgetFocused.set(false); this._ctxChatWidgetVisible.set(false); this._instance.focus(); From 83f8ee26b6c832c68f7ba37f7b3f083bc2b76292 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:53:59 -0600 Subject: [PATCH 051/753] rm unused --- .../chat/browser/terminalChatController.ts | 14 -------------- .../chat/browser/terminalChatWidget.ts | 11 ++--------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f3d7ef85326..83adeff265d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -6,7 +6,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IDimension } from 'vs/base/browser/dom'; import { Lazy } from 'vs/base/common/lazy'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; @@ -49,7 +48,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; - private _lastLayoutDimensions: IDimension | undefined; private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } @@ -95,14 +93,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._cancellationTokenSource = new CancellationTokenSource(); } - layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { - if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { - return; - } - this._lastLayoutDimensions = dimension; - this._chatWidget?.rawValue?.layout(dimension.width); - } - xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; @@ -123,10 +113,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr throw new Error('FindWidget expected terminal DOM to be initialized'); } - if (this._lastLayoutDimensions) { - chatWidget.layout(this._lastLayoutDimensions.width); - } - return chatWidget; }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0e8b82043ea..61aa2c4a409 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -92,6 +92,8 @@ export class TerminalChatWidget extends Disposable { this._responseElement.classList.remove('hide'); if (!this._responseWidget) { this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); + this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); + this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { if (!model || !this._responseWidget) { return; @@ -101,12 +103,6 @@ export class TerminalChatWidget extends Disposable { const height = this._responseWidget.getContentHeight(); this._responseWidget.layout(new Dimension(400, height)); }); - this._register(this._responseWidget.onDidFocusEditorText(() => { - this._ctxResponseEditorFocused.set(true); - })); - this._register(this._responseWidget.onDidBlurEditorText(() => { - this._ctxResponseEditorFocused.set(false); - })); } else { this._responseWidget.setValue(codeBlock); } @@ -170,9 +166,6 @@ export class TerminalChatWidget extends Disposable { updateProgress(progress?: IChatProgress): void { this._inlineChatWidget.updateProgress(progress?.kind === 'content' || progress?.kind === 'markdownContent'); } - layout(width: number): void { - // this._widget?.layout(100, width < 300 ? 300 : width); - } public get focusTracker(): IFocusTracker { return this._focusTracker; } From 1d262b3146aa7f3c254d28fc0144f9ae08bf9717 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:05:59 -0600 Subject: [PATCH 052/753] update variables after rob's change --- .../terminalContrib/chat/browser/terminalChatController.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 83adeff265d..9c9cb0a2ff8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,7 +17,6 @@ import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/ import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -162,15 +161,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatWidget?.rawValue?.updateProgress(progress); }; - const resolvedVariables: Record = {}; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId, agentId, message: this._chatWidget?.rawValue?.input() || '', - variables: resolvedVariables, - variables2: { message: this._chatWidget?.rawValue?.input() || '', variables: [] } + variables: { variables: [] }, }; this._chatWidget?.rawValue?.setValue(); From cd261754e852064d15516f6321a65ac17c906a3f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:06:30 -0800 Subject: [PATCH 053/753] Simplify response editor presentation --- .../chat/browser/terminalChatWidget.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 61aa2c4a409..0c174ed3555 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,7 +91,41 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { - this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, {}, { isSimpleWidget: true })); + this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { + padding: { top: 2, bottom: 2 }, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + hideCursorInOverviewRuler: true, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + useShadows: false, + vertical: 'hidden', + horizontal: 'auto', + alwaysConsumeMouseWheel: false + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { enabled: false }, + guides: { indentation: false }, + rulers: [], + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + quickSuggestions: false, + suggest: { + showIcons: false, + showSnippets: false, + showWords: true, + showStatusBar: false, + }, + }, { isSimpleWidget: true })); this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { From 84fb0821a9347a1b8c563f29259962e758ca6e87 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:56:53 -0600 Subject: [PATCH 054/753] extract code content, language --- .../terminalContrib/chat/browser/terminalChatController.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9c9cb0a2ff8..f7edc4b5fd6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -179,7 +179,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.updateProgress(); return; } - const codeBlock = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw.replaceAll('```', ''); + const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; + const regex = /```(?\w+)\n(?[\s\S]*?)```/g; + const match = regex.exec(firstCodeBlockContent); + const codeBlock = match?.groups?.content; + // TODO: map to editor known language, set editor language + // const language = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; From fa111adf63ac35b6b8921db9ece8cb7880a7831c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 16:08:25 -0600 Subject: [PATCH 055/753] add language support --- .../chat/browser/terminalChatController.ts | 5 ++--- .../chat/browser/terminalChatWidget.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f7edc4b5fd6..221c010600b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -183,15 +183,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content; - // TODO: map to editor known language, set editor language - // const language = match?.groups?.language; + const shellType = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { // TODO: check the SR experience - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); } else { this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0c174ed3555..abb8807cdd9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -87,7 +87,7 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._widgetContainer)); } - renderTerminalCommand(codeBlock: string, requestId: number): void { + renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); this._responseElement.classList.remove('hide'); if (!this._responseWidget) { @@ -140,6 +140,20 @@ export class TerminalChatWidget extends Disposable { } else { this._responseWidget.setValue(codeBlock); } + this._responseWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + } + + private _getLanguageFromShell(shell?: string): string { + switch (shell) { + case 'sh': + case 'bash': + case 'zsh': + return 'shellscript'; + case 'pwsh': + return 'powershell'; + default: + return 'plaintext'; + } } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { From 16f1f4bfc1cb918a397c5c4cd4a1904d88ec983b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:29:44 -0800 Subject: [PATCH 056/753] Add shell language id fallback --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index abb8807cdd9..c3efebdc5ca 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -9,6 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; @@ -42,6 +43,7 @@ export class TerminalChatWidget extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService ) { super(); @@ -145,9 +147,13 @@ export class TerminalChatWidget extends Disposable { private _getLanguageFromShell(shell?: string): string { switch (shell) { - case 'sh': - case 'bash': + case 'fish': + return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; case 'zsh': + return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; + case 'bash': + return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; + case 'sh': return 'shellscript'; case 'pwsh': return 'powershell'; From 8112339b211fcdb807e4e7f9b57f39142fbe289b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:49:37 -0800 Subject: [PATCH 057/753] Improve progress feedback --- .../chat/browser/terminalChatController.ts | 17 +++++++++++++---- .../chat/browser/terminalChatWidget.ts | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 221c010600b..36db8ad2ab9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -23,6 +23,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; const enum Message { NONE = 0, @@ -172,12 +173,20 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.setValue(); try { - await this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); + this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); + // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); + await task; } catch (e) { - // Provider is not ready + + } finally { this._ctxHasActiveRequest.set(false); - this._chatWidget?.rawValue?.updateProgress(); - return; + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index c3efebdc5ca..402f8638558 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -84,7 +84,7 @@ export class TerminalChatWidget extends Disposable { } ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); this._widgetContainer.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._widgetContainer)); From 3c306e9b87596190decd7749f68b00b0acc52525 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:52:43 -0800 Subject: [PATCH 058/753] Fix feedback icons --- .../chat/browser/terminalChatActions.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 37cf45c77fc..a62ed944245 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -162,6 +162,7 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), + // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -177,12 +178,13 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.FeedbackUnhelpful, - title: localize2('feedbackUnhelpful', 'Helpful'), + title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), - icon: Codicon.thumbsup, + // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), + icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', @@ -202,14 +204,19 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, ), - icon: Codicon.thumbsup, - menu: { + // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), + icon: Codicon.report, + menu: [/*{ + // TODO: Enable this id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - group: 'inline', - order: 3, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), - }, + when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), + group: '2_feedback', + order: 3 + }, */{ + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'config', + order: 3 + }], run: (_xterm, _accessor, activeInstance) => { // TODO: Impl } From 3119ae83f7fc220079e3a8ef9b42d254fc0ead34 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:57:38 -0800 Subject: [PATCH 059/753] Move accept to status menu --- .../terminalContrib/chat/browser/terminalChatActions.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index a62ed944245..348d5bee159 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -13,7 +13,7 @@ import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONS import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ @@ -72,7 +72,8 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.AcceptCommand, - title: localize2('workbench.action.terminal.acceptCommand', 'Accept Command'), + title: localize2('acceptCommand', 'Accept Command'), + shortTitle: localize2('accept', 'Accept'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -87,8 +88,8 @@ registerActiveXtermAction({ primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'main', + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', order: 0, when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), }, From 802e74686aab506468f23b1097034c72a22c057c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:58:43 -0800 Subject: [PATCH 060/753] Simplify localize id --- .../terminalContrib/chat/browser/terminalChatActions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 348d5bee159..d0205008d74 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -18,7 +18,7 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Focus, - title: localize2('workbench.action.terminal.focusChat', 'Focus Chat'), + title: localize2('focusChat', 'Focus Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -40,7 +40,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('workbench.action.terminal.closeChat', 'Close Chat'), + title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -104,7 +104,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('workbench.action.terminal.submitChat', 'Make Chat Request'), + title: localize2('makeChatRequest', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -135,7 +135,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'), + title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From ee47ee25bb67793e707129ed258b219419152216 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:18:38 -0600 Subject: [PATCH 061/753] get accept action to show up, add view in chat action --- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../chat/browser/terminalChat.ts | 1 + .../chat/browser/terminalChatActions.ts | 29 ++++++++++++- .../chat/browser/terminalChatController.ts | 41 ++++++++++++++----- .../chat/browser/terminalChatWidget.ts | 1 + 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ca909ba98e0..537c7b8951f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { + } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES, 'workbench.action.terminal.chat.acceptCommand', 'workbench.action.terminal.chat.viewInChat'].includes(action.id)) { return { isSecondary: false }; } else { return { isSecondary: true }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 882baf8c26b..ddea1c2a53b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -14,6 +14,7 @@ export const enum TerminalChatCommandId { FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', + ViewInChat = 'workbench.action.terminal.chat.viewInChat', } export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d0205008d74..d6ba18a272e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -91,7 +91,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -102,6 +102,32 @@ registerActiveXtermAction({ } }); +registerActiveXtermAction({ + id: TerminalChatCommandId.ViewInChat, + title: localize2('viewInChat', 'View in Chat'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages) + ), + icon: Codicon.commentDiscussion, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.viewInChat(); + } +}); + registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, title: localize2('makeChatRequest', 'Make Chat Request'), @@ -222,3 +248,4 @@ registerActiveXtermAction({ // TODO: Impl } }); + diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 36db8ad2ab9..da56f4d4041 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -14,13 +14,13 @@ import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/te import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -62,6 +62,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -72,6 +75,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IChatService private readonly _chatService: IChatService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -147,18 +152,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - let message = ''; + this._lastInput = this._chatWidget?.rawValue?.input(); + if (!this._lastInput) { + return; + } this._chatAccessibilityService.acceptRequest(); this._ctxHasActiveRequest.set(true); const cancellationToken = this._cancellationTokenSource.token; const agentId = 'terminal'; + let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { return; } if (progress.kind === 'content' || progress.kind === 'markdownContent') { - message += progress.content; + responseContent += progress.content; } this._chatWidget?.rawValue?.updateProgress(progress); }; @@ -167,11 +176,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId, - message: this._chatWidget?.rawValue?.input() || '', + message: this._lastInput, variables: { variables: [] }, }; - this._chatWidget?.rawValue?.setValue(); - try { const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -188,7 +195,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } - const firstCodeBlockContent = marked.lexer(message).filter(token => token.type === 'code')?.[0]?.raw; + this._lastResponseContent = responseContent; + const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content; @@ -200,12 +208,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (codeBlock) { // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); + this._ctxLastResponseType.set(InlineChatResponseTypes.Empty); } else { - this._chatWidget?.rawValue?.renderMessage(message, this._accessibilityRequestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); } - this._ctxHasActiveRequest.set(false); - this._chatWidget?.rawValue?.updateProgress(); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -239,6 +247,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.reveal(); } + async viewInChat(): Promise { + if (!this._lastInput || !this._lastResponseContent) { + return; + } + const widget = await this._chatWidgetService.revealViewForProvider('copilot'); + if (widget && widget.viewModel) { + this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + widget.focusLastMessage(); + } + } + override dispose() { super.dispose(); this._chatWidget?.rawValue?.dispose(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 402f8638558..b8c73ef1ffb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -163,6 +163,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { + this._responseElement.classList.add('hide'); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } From bfc4df0453af87e35090ddb39e1a992c3fcf1eaa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:24:03 -0600 Subject: [PATCH 062/753] add show/hide commands for editor --- .../chat/browser/terminalChatWidget.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index b8c73ef1ffb..e522765f213 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -91,7 +91,7 @@ export class TerminalChatWidget extends Disposable { } renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(codeBlock, requestId); - this._responseElement.classList.remove('hide'); + this.showTerminalCommandEditor(); if (!this._responseWidget) { this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { padding: { top: 2, bottom: 2 }, @@ -163,7 +163,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this._responseElement.classList.add('hide'); + this.hideTerminalCommandEditor(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -183,7 +183,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this._responseElement?.classList.add('hide'); + this.hideTerminalCommandEditor(); this._widgetContainer.classList.add('hide'); this._inlineChatWidget.value = ''; this._responseWidget?.setValue(''); @@ -207,7 +207,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this._responseElement?.classList.add('hide'); + this.hideTerminalCommandEditor(); } } acceptCommand(): void { @@ -224,4 +224,10 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } + hideTerminalCommandEditor(): void { + this._responseElement.classList.add('hide'); + } + showTerminalCommandEditor(): void { + this._responseElement.classList.remove('hide'); + } } From 2b7e5e52d276a902db01ee05e7c9e157e6a0a265 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:36:27 -0600 Subject: [PATCH 063/753] rename and reorder things in terminalChatWidget --- .../chat/browser/terminalChatWidget.ts | 106 +++++++++--------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index e522765f213..49eef7120fa 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -24,21 +24,24 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { - private readonly _scopedInstantiationService: IInstantiationService; - private readonly _widgetContainer: HTMLElement; - private readonly _ctxChatWidgetFocused: IContextKey; - private readonly _ctxChatWidgetVisible: IContextKey; - private readonly _ctxResponseEditorFocused!: IContextKey; + + private readonly _container: HTMLElement; private readonly _inlineChatWidget: InlineChatWidget; - private readonly _responseElement: HTMLElement; + public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } + + private readonly _terminalCommandWidgetContainer: HTMLElement; + private _terminalCommandWidget: CodeEditorWidget | undefined; + private readonly _focusTracker: IFocusTracker; - private _responseWidget: CodeEditorWidget | undefined; - public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } + private readonly _scopedInstantiationService: IInstantiationService; + private readonly _focusedContextKey: IContextKey; + private readonly _visibleContextKey: IContextKey; + private readonly _responseEditorFocusedContextKey!: IContextKey; constructor( - private readonly _container: HTMLElement, + terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -47,19 +50,19 @@ export class TerminalChatWidget extends Disposable { @IModelService private readonly _modelService: IModelService ) { super(); - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container)); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(terminalElement)); this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - this._ctxChatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._ctxChatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - this._ctxResponseEditorFocused = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); - this._widgetContainer = document.createElement('div'); - this._widgetContainer.classList.add('terminal-inline-chat'); - this._container.appendChild(this._widgetContainer); + this._container = document.createElement('div'); + this._container.classList.add('terminal-inline-chat'); + terminalElement.appendChild(this._container); - this._responseElement = document.createElement('div'); - this._responseElement.classList.add('terminal-inline-chat-response'); - this._widgetContainer.prepend(this._responseElement); + this._terminalCommandWidgetContainer = document.createElement('div'); + this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._terminalCommandWidgetContainer); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. @@ -85,15 +88,16 @@ export class TerminalChatWidget extends Disposable { ); this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); - this._widgetContainer.appendChild(this._inlineChatWidget.domNode); + this._container.appendChild(this._inlineChatWidget.domNode); - this._focusTracker = this._register(trackFocus(this._widgetContainer)); + this._focusTracker = this._register(trackFocus(this._container)); } - renderTerminalCommand(codeBlock: string, requestId: number, shellType?: string): void { - this._chatAccessibilityService.acceptResponse(codeBlock, requestId); - this.showTerminalCommandEditor(); - if (!this._responseWidget) { - this._responseWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._responseElement, { + + renderTerminalCommand(command: string, requestId: number, shellType?: string): void { + this._chatAccessibilityService.acceptResponse(command, requestId); + this.showTerminalCommandWidget(); + if (!this._terminalCommandWidget) { + this._terminalCommandWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { padding: { top: 2, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, @@ -128,21 +132,21 @@ export class TerminalChatWidget extends Disposable { showStatusBar: false, }, }, { isSimpleWidget: true })); - this._register(this._responseWidget.onDidFocusEditorText(() => this._ctxResponseEditorFocused.set(true))); - this._register(this._responseWidget.onDidBlurEditorText(() => this._ctxResponseEditorFocused.set(false))); - this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: codeBlock })).then((model) => { - if (!model || !this._responseWidget) { + this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); + this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { + if (!model || !this._terminalCommandWidget) { return; } - this._responseWidget.layout(new Dimension(400, 0)); - this._responseWidget.setModel(model); - const height = this._responseWidget.getContentHeight(); - this._responseWidget.layout(new Dimension(400, height)); + this._terminalCommandWidget.layout(new Dimension(400, 0)); + this._terminalCommandWidget.setModel(model); + const height = this._terminalCommandWidget.getContentHeight(); + this._terminalCommandWidget.layout(new Dimension(400, height)); }); } else { - this._responseWidget.setValue(codeBlock); + this._terminalCommandWidget.setValue(command); } - this._responseWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + this._terminalCommandWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); } private _getLanguageFromShell(shell?: string): string { @@ -163,7 +167,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this.hideTerminalCommandEditor(); + this.hideTerminalCommandWidget(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -177,18 +181,18 @@ export class TerminalChatWidget extends Disposable { } reveal(): void { this._inlineChatWidget.layout(new Dimension(400, 150)); - this._widgetContainer.classList.remove('hide'); - this._ctxChatWidgetFocused.set(true); - this._ctxChatWidgetVisible.set(true); + this._container.classList.remove('hide'); + this._focusedContextKey.set(true); + this._visibleContextKey.set(true); this._inlineChatWidget.focus(); } hide(): void { - this.hideTerminalCommandEditor(); - this._widgetContainer.classList.add('hide'); + this.hideTerminalCommandWidget(); + this._container.classList.add('hide'); this._inlineChatWidget.value = ''; - this._responseWidget?.setValue(''); - this._ctxChatWidgetFocused.set(false); - this._ctxChatWidgetVisible.set(false); + this._terminalCommandWidget?.setValue(''); + this._focusedContextKey.set(false); + this._visibleContextKey.set(false); this._instance.focus(); } cancel(): void { @@ -207,11 +211,11 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this.hideTerminalCommandEditor(); + this.hideTerminalCommandWidget(); } } acceptCommand(): void { - const value = this._responseWidget?.getValue(); + const value = this._terminalCommandWidget?.getValue(); if (!value) { return; } @@ -224,10 +228,10 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } - hideTerminalCommandEditor(): void { - this._responseElement.classList.add('hide'); + hideTerminalCommandWidget(): void { + this._terminalCommandWidgetContainer.classList.add('hide'); } - showTerminalCommandEditor(): void { - this._responseElement.classList.remove('hide'); + showTerminalCommandWidget(): void { + this._terminalCommandWidgetContainer.classList.remove('hide'); } } From 1565341d4496a71da7860467d3ea32a72103dbab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:50:49 -0600 Subject: [PATCH 064/753] reset everything on hide of widget --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 - .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index da56f4d4041..a5d73cd459a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -169,7 +169,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { responseContent += progress.content; } - this._chatWidget?.rawValue?.updateProgress(progress); }; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 49eef7120fa..a485dd40c4f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -191,6 +191,10 @@ export class TerminalChatWidget extends Disposable { this._container.classList.add('hide'); this._inlineChatWidget.value = ''; this._terminalCommandWidget?.setValue(''); + this._inlineChatWidget.updateChatMessage(undefined); + this._inlineChatWidget.updateFollowUps(undefined); + this._inlineChatWidget.updateProgress(false); + this._inlineChatWidget.updateToolbar(false); this._focusedContextKey.set(false); this._visibleContextKey.set(false); this._instance.focus(); From 55aca1a766b3223d5bd2fa164cbb17ff54615b9f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:06:47 -0600 Subject: [PATCH 065/753] more clean up --- .../terminal/common/terminalContextKey.ts | 1 + .../chat/browser/terminalChatController.ts | 46 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..a7805bfac46 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,6 +45,7 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatLastResponseType = 'terminalChatLastResponseType' } export namespace TerminalContextKeys { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index a5d73cd459a..5c105fee353 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -21,9 +21,9 @@ import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const enum Message { NONE = 0, @@ -48,22 +48,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr */ static activeChatWidget?: TerminalChatController; private _chatWidget: Lazy | undefined; - private _accessibilityRequestId: number = 0; get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; } - private readonly _ctxHasActiveRequest!: IContextKey; - private readonly _ctxHasTerminalAgent!: IContextKey; - private readonly _ctxLastResponseType!: IContextKey; + private readonly _requestActiveContextKey!: IContextKey; + private readonly _terminalAgentRegisteredContextKey!: IContextKey; + private readonly _lastResponseTypeContextKey!: IContextKey; private _cancellationTokenSource!: CancellationTokenSource; + private _accessibilityRequestId: number = 0; + private _messages = this._store.add(new Emitter()); + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); - private _lastInput: string | undefined; - private _lastResponseContent: string | undefined; + private _terminalAgentId = 'terminal'; constructor( private readonly _instance: ITerminalInstance, @@ -82,18 +85,18 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._ctxHasActiveRequest = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._ctxHasTerminalAgent = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._ctxLastResponseType = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._lastResponseTypeContextKey = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); - if (!this._chatAgentService.hasAgent('terminal')) { + if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { - if (this._chatAgentService.getAgent('terminal')) { - this._ctxHasTerminalAgent.set(true); + if (this._chatAgentService.getAgent(this._terminalAgentId)) { + this._terminalAgentRegisteredContextKey.set(true); } })); } else { - this._ctxHasTerminalAgent.set(true); + this._terminalAgentRegisteredContextKey.set(true); } this._cancellationTokenSource = new CancellationTokenSource(); } @@ -157,9 +160,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatAccessibilityService.acceptRequest(); - this._ctxHasActiveRequest.set(true); + this._requestActiveContextKey.set(true); const cancellationToken = this._cancellationTokenSource.token; - const agentId = 'terminal'; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -174,12 +176,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr const requestProps: IChatAgentRequest = { sessionId: generateUuid(), requestId, - agentId, + agentId: this._terminalAgentId, message: this._lastInput, + // TODO: ? variables: { variables: [] }, }; try { - const task = this._chatAgentService.invokeAgent(agentId, requestProps, progressCallback, [], cancellationToken); + const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); @@ -189,7 +192,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } catch (e) { } finally { - this._ctxHasActiveRequest.set(false); + this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); @@ -205,12 +208,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } if (codeBlock) { - // TODO: check the SR experience this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); - this._ctxLastResponseType.set(InlineChatResponseTypes.Empty); + this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); } else { this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); - this._ctxLastResponseType.set(InlineChatResponseTypes.OnlyMessages); + this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); From 57857d6546a713f47a0639a55db18ea4224edf54 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:22:39 -0600 Subject: [PATCH 066/753] rm unused --- src/vs/workbench/contrib/terminal/common/terminalContextKey.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index a7805bfac46..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,7 +45,6 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', - ChatLastResponseType = 'terminalChatLastResponseType' } export namespace TerminalContextKeys { From a9b4e6fe4446bd2e90c5507e0d32f08e196b292c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:43:46 -0600 Subject: [PATCH 067/753] add accessible view --- .../browser/accessibilityConfiguration.ts | 8 +++- .../terminal/common/terminalContextKey.ts | 3 ++ .../browser/terminal.chat.contribution.ts | 6 +++ .../browser/terminalChatAccessibleView.ts | 41 +++++++++++++++++++ .../chat/browser/terminalChatController.ts | 1 + 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9..a5d30356fed 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -52,7 +52,8 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - Comments = 'accessibility.verbosity.comments' + Comments = 'accessibility.verbosity.comments', + TerminalInlineChat = 'accessibility.verbosity.terminalInlineChat' } export const enum AccessibleViewProviderId { @@ -62,6 +63,7 @@ export const enum AccessibleViewProviderId { Chat = 'panelChat', InlineChat = 'inlineChat', InlineCompletions = 'inlineCompletions', + TerminalInlineChat = 'terminalInlineChat', KeybindingsEditor = 'keybindingsEditor', Notebook = 'notebook', Editor = 'editor', @@ -168,6 +170,10 @@ const configuration: IConfigurationNode = { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty }, + [AccessibilityVerbositySettingId.TerminalInlineChat]: { + description: localize('verbosity.terminalInlineChat', 'Provide information about actions that can be taken in the terminal inline chat widget.'), + ...baseVerbosityProperty + }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..5ac4cdca5ac 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -183,4 +183,7 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** Whether the chat response editor is focused */ + export const chatResponseMessageFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseMessageFocusedContextKey', "Whether the chat response message is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index a3a7b55757a..eebaa8b3b8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -5,7 +5,13 @@ import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts new file mode 100644 index 00000000000..949a2d8cc51 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalInlineChatAccessibleViewContribution extends Disposable { + static ID: 'terminalInlineChatAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(105, 'terminalInlineChat', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; + if (!controller?.lastResponseContent) { + return false; + } + const responseContent = controller.lastResponseContent; + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalInlineChat, + provideContent(): string { return responseContent; }, + onClose() { + controller.focus(); + }, + + options: { type: AccessibleViewType.View } + }); + return true; + }, ContextKeyExpr.and(TerminalContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + } +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 5c105fee353..515d7b895e8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -62,6 +62,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _lastInput: string | undefined; private _lastResponseContent: string | undefined; + get lastResponseContent(): string | undefined { return this._lastResponseContent; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); From d881ddbae52bec5cdc5e87351d6c3f8f7fa5c67f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 15:30:36 -0600 Subject: [PATCH 068/753] Add accessibility help dialog --- .../browser/accessibilityConfiguration.ts | 5 -- .../inlineChat/browser/inlineChatActions.ts | 3 +- .../terminal/common/terminalContextKey.ts | 3 - .../browser/terminal.chat.contribution.ts | 2 + .../chat/browser/terminalChat.ts | 2 +- .../browser/terminalChatAccessibilityHelp.ts | 64 +++++++++++++++++++ .../browser/terminalChatAccessibleView.ts | 3 +- .../chat/browser/terminalChatActions.ts | 14 ++-- 8 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a5d30356fed..bba5a1f648e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -53,7 +53,6 @@ export const enum AccessibilityVerbositySettingId { Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', Comments = 'accessibility.verbosity.comments', - TerminalInlineChat = 'accessibility.verbosity.terminalInlineChat' } export const enum AccessibleViewProviderId { @@ -170,10 +169,6 @@ const configuration: IConfigurationNode = { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseVerbosityProperty }, - [AccessibilityVerbositySettingId.TerminalInlineChat]: { - description: localize('verbosity.terminalInlineChat', 'Provide information about actions that can be taken in the terminal inline chat widget.'), - ...baseVerbosityProperty - }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c64166..02cf92cfa93 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,6 +30,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -745,6 +746,6 @@ export class InlineAccessibilityHelpContribution extends Disposable { return; } runAccessibilityHelpAction(accessor, codeEditor, 'inlineChat'); - }, ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED))); + }, ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED), TerminalContextKeys.chatFocused.negate()))); } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 5ac4cdca5ac..8ea1469a4b1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -183,7 +183,4 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); - - /** Whether the chat response editor is focused */ - export const chatResponseMessageFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseMessageFocusedContextKey', "Whether the chat response message is focused.")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index eebaa8b3b8a..70804842c3e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -13,5 +13,7 @@ import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; +import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibilityHelpContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index ddea1c2a53b..f75c9c3d9a7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -6,7 +6,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; export const enum TerminalChatCommandId { - Focus = 'workbench.action.terminal.chat.focus', + Start = 'workbench.action.terminal.chat.start', Hide = 'workbench.action.terminal.chat.close', MakeRequest = 'workbench.action.terminal.chat.makeRequest', Cancel = 'workbench.action.terminal.chat.cancel', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts new file mode 100644 index 00000000000..5bbe76db85f --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { + const terminalService = accessor.get(ITerminalService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; + if (controller === undefined) { + return false; + } + const helpContent = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, + provideContent(): string { return helpContent; }, + onClose() { + controller.focus(); + }, + options: { type: AccessibleViewType.Help } + }); + return true; + }, ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + } +} + + +export function getAccessibilityHelpText(accessor: ServicesAccessor): string { + const keybindingService = accessor.get(IKeybindingService); + const content = []; + const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); + const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.AcceptCommand)?.getAriaLabel(); + const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); + //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. + const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); + content.push(localize('inlineChat.overview', "Inline chat occurs within a terminal. It is useful for suggesting terminal commands. Keep in mind that AI generated code may be incorrect.")); + content.push(localize('inlineChat.access', "It can be activated using the command: Terminal: Start Chat ({0}), which will focus the input box.", startChatKeybinding)); + content.push(makeRequestKeybinding ? localize('inlineChat.input', "The input box is where the user can type a request and can make the request ({0}). The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.", makeRequestKeybinding) : localize('inlineChat.inputNoKb', "The input box is where the user can type a request and can make the request by tabbing to the Make Request button, which is not currently triggerable via keybindings. The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.")); + content.push(localize('inlineChat.results', "A result may contain a terminal command or just a message. In either case, the result will be announced.")); + content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'If just a message comes back, it can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); + content.push(localize('inlineChat.inspectTerminalCommand', 'If a terminal command comes back, it can be inspected in an editor reached via Shift+Tab.')); + content.push(acceptCommandKeybinding ? localize('inlineChat.acceptCommand', 'With focus in the command editor, the Terminal: Accept Chat Command ({0}) action.', acceptCommandKeybinding) : localize('inlineChat.acceptCommandNoKb', 'Accept a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); + content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); + return content.join('\n\n'); +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 949a2d8cc51..8c146698482 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -27,12 +27,11 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { const responseContent = controller.lastResponseContent; accessibleViewService.show({ id: AccessibleViewProviderId.TerminalInlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.TerminalInlineChat, + verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, provideContent(): string { return responseContent; }, onClose() { controller.focus(); }, - options: { type: AccessibleViewType.View } }); return true; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d6ba18a272e..641737229dc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -17,8 +17,8 @@ import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ - id: TerminalChatCommandId.Focus, - title: localize2('focusChat', 'Focus Chat'), + id: TerminalChatCommandId.Start, + title: localize2('startChat', 'Terminal: Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -40,7 +40,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('closeChat', 'Close Chat'), + title: localize2('closeChat', 'Terminal: Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -72,7 +72,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.AcceptCommand, - title: localize2('acceptCommand', 'Accept Command'), + title: localize2('acceptCommand', 'Terminal: Accept Chat Command'), shortTitle: localize2('accept', 'Accept'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -104,7 +104,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, - title: localize2('viewInChat', 'View in Chat'), + title: localize2('viewInChat', 'Terminal: View in Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -130,7 +130,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('makeChatRequest', 'Make Chat Request'), + title: localize2('makeChatRequest', 'Terminal: Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -161,7 +161,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('cancelChat', 'Cancel Chat'), + title: localize2('cancelChat', 'Terminal: Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From b01c303b90fe931c2f4e816172fa8fdaf13ee963 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 10:50:32 -0600 Subject: [PATCH 069/753] add session support, get cancellation to work --- .../inlineChat/browser/inlineChatSession.ts | 9 +- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 107 ++++++++++++++---- .../chat/browser/terminalChatWidget.ts | 17 +-- 4 files changed, 96 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 693fde05bab..678c8177976 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -316,7 +316,7 @@ export class SessionExchange { constructor( readonly prompt: SessionPrompt, - readonly response: ReplyResponse | EmptyResponse | ErrorResponse + readonly response: ReplyResponse | EmptyResponse | ErrorResponse | TerminalResponse ) { } } @@ -324,6 +324,13 @@ export class EmptyResponse { } +export class TerminalResponse { + readonly message: string; + constructor(message: string) { + this.message = message; + } +} + export class ErrorResponse { readonly message: string; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 641737229dc..7aef66f5631 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); + contr?.cancel(); } }); @@ -234,7 +234,7 @@ registerActiveXtermAction({ // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, menu: [/*{ - // TODO: Enable this + // TODO: Enable id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), group: '2_feedback', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 515d7b895e8..944fba8dbba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -16,14 +16,24 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; const enum Message { NONE = 0, @@ -54,15 +64,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _cancellationTokenSource!: CancellationTokenSource; + private _scopedInstantiationService: IInstantiationService | undefined; private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); - private _lastInput: string | undefined; - private _lastResponseContent: string | undefined; - get lastResponseContent(): string | undefined { return this._lastResponseContent; } + private _activeSession?: Session; + + private _fakeEditor: CodeEditorWidget | undefined; + + get lastResponseContent(): string | undefined { return (this._activeSession?.lastExchange?.response as TerminalResponse).message; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); @@ -80,7 +92,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IModelService private readonly _modelService: IModelService ) { super(); if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { @@ -99,7 +113,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } - this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -107,7 +120,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. + const fakeParentEditorElement = document.createElement('div'); + this._fakeEditor = this._scopedInstantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + extraEditorClassName: 'ignore-panel-bg' + }, + { isSimpleWidget: true } + ); + + const path = `terminal-chat-input-${this._instance.instanceId}`; + const inputUri = URI.from({ path: path, scheme: Schemas.untitled, fragment: '' }); + const result: ITextModel = this._modelService.createModel('', null, inputUri, false); + this._fakeEditor.setModel(result); + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._fakeEditor!, this._instance); chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { @@ -127,7 +158,28 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - this._cancellationTokenSource.cancel(); + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + this._activeSession = undefined; + } + } + + private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } + + const session = await this._inlineChatSessionService.createSession( + editor, + { editMode: EditMode.Live }, + token + ); + + if (!session) { + return; + } + + this._activeSession = session; } private _forcedPlaceholder: string | undefined = undefined; @@ -156,13 +208,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - this._lastInput = this._chatWidget?.rawValue?.input(); - if (!this._lastInput) { - return; - } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = this._cancellationTokenSource.token; + const cancellationToken = new CancellationTokenSource().token; + if (this._fakeEditor?.hasModel()) { + await this._startSession(this._fakeEditor, cancellationToken); + } + assertType(this._activeSession); + const inputValue = this.chatWidget!.inlineChatWidget.value; + this._activeSession!.addInput(new SessionPrompt(inputValue)); let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -178,10 +232,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId: this._terminalAgentId, - message: this._lastInput, + message: inputValue, // TODO: ? variables: { variables: [] }, }; + let response: EmptyResponse | TerminalResponse = EmptyResponse; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -191,14 +246,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { - + response = e; } finally { this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + if (response === EmptyResponse) { + response = new TerminalResponse(responseContent); + } } - this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); @@ -215,6 +272,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } + if (this._activeSession?.lastInput) { + this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -250,12 +310,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { - if (!this._lastInput || !this._lastResponseContent) { - return; - } const widget = await this._chatWidgetService.revealViewForProvider('copilot'); if (widget && widget.viewModel) { - this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + const request = this._activeSession?.lastExchange; + const input = request?.prompt.value; + const response = request?.response as TerminalResponse; + if (!input || !response) { + return; + } + this._chatService.addCompleteRequest(widget.viewModel.sessionId, input, undefined, response); widget.focusLastMessage(); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index a485dd40c4f..bbe822e01a6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -42,6 +42,7 @@ export class TerminalChatWidget extends Disposable { constructor( terminalElement: HTMLElement, + fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -64,18 +65,6 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - // The inline chat widget requires a parent editor that it bases the diff view on, since the - // terminal doesn't use that feature we can just pass in an unattached editor instance. - const fakeParentEditorElement = document.createElement('div'); - const fakeParentEditor = this._scopedInstantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - extraEditorClassName: 'ignore-panel-bg' - }, - { isSimpleWidget: true } - ); - this._inlineChatWidget = this._scopedInstantiationService.createInstance( InlineChatWidget, fakeParentEditor, @@ -199,10 +188,6 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } - cancel(): void { - // TODO: Impl - this._inlineChatWidget.value = ''; - } focus(): void { this._inlineChatWidget.focus(); } From 216b941cd96e4022e50b7c92d446d090c64379ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:28:12 -0800 Subject: [PATCH 070/753] Move to registerWorkbenchContribution2 --- .../chat/browser/terminal.chat.contribution.ts | 16 +++++++--------- .../browser/terminalChatAccessibilityHelp.ts | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 70804842c3e..0c2dba50035 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -3,17 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; +import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; +import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; + +import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); -import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; -import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibleViewContribution, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(TerminalInlineChatAccessibilityHelpContribution, LifecyclePhase.Eventually); +registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 5bbe76db85f..2fe29ea5d8a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -18,6 +18,7 @@ import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { + static ID: 'terminalInlineChatAccessibilityHelpContribution'; constructor() { super(); this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { From 58f5274002e29d30dba54da2ac53a4dc8c5a0b3b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:36:59 -0800 Subject: [PATCH 071/753] Remove scoped services, they were causing an error and breaking language highlighting --- .../chat/browser/terminalChatController.ts | 56 +++++++++---------- .../chat/browser/terminalChatWidget.ts | 12 ++-- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 944fba8dbba..86c17c99d22 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -3,37 +3,36 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { Lazy } from 'vs/base/common/lazy'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { Schemas } from 'vs/base/common/network'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { Emitter, Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { assertType } from 'vs/base/common/types'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModel } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; const enum Message { NONE = 0, @@ -64,8 +63,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _scopedInstantiationService: IInstantiationService | undefined; - private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -97,6 +94,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IModelService private readonly _modelService: IModelService ) { super(); + if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } @@ -120,12 +118,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._scopedInstantiationService.createInstance( + this._fakeEditor = this._instantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bbe822e01a6..67d671aae28 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -15,7 +15,6 @@ import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; @@ -35,7 +34,7 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - private readonly _scopedInstantiationService: IInstantiationService; + // private readonly _scopedInstantiationService: IInstantiationService; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; private readonly _responseEditorFocusedContextKey!: IContextKey; @@ -44,15 +43,14 @@ export class TerminalChatWidget extends Disposable { terminalElement: HTMLElement, fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @ILanguageService private readonly _languageService: ILanguageService, @IModelService private readonly _modelService: IModelService ) { super(); - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(terminalElement)); - this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); + this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); @@ -65,7 +63,7 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - this._inlineChatWidget = this._scopedInstantiationService.createInstance( + this._inlineChatWidget = this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, { @@ -86,7 +84,7 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { - this._terminalCommandWidget = this._register(this._scopedInstantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { + this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { padding: { top: 2, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, From 804a4816393bb289678a0457e8653ce72032d1b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:00:18 -0800 Subject: [PATCH 072/753] Improve command suggestion presentation --- .../chat/browser/media/terminalChatWidget.css | 23 ++++++++++++++++--- .../chat/browser/terminalChatWidget.ts | 23 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 633b6778dfe..a916f2dd0d7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -10,6 +10,23 @@ z-index: 100; height: auto !important; } -/* .terminal-inline-chat .inline-chat .body { - display: flex; -} */ + +.terminal-inline-chat .terminal-inline-chat-response { + border: 1px solid var(--vscode-input-border, transparent); + background-color: var(--vscode-interactive-result-editor-background-color); +} + +.terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { + border-color: var(--vscode-focusBorder, transparent); +} + +.terminal-inline-chat .terminal-inline-chat-response, +.terminal-inline-chat .terminal-inline-chat-response .monaco-editor, +.terminal-inline-chat .terminal-inline-chat-response .monaco-editor .overflow-guard { + border-radius: 4px; +} + +.terminal-inline-chat .terminal-inline-chat-response { + margin: 0 0 8px 0; + padding-left: 8px; +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 67d671aae28..9f4f960ee5e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -13,8 +13,10 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; @@ -34,7 +36,6 @@ export class TerminalChatWidget extends Disposable { private readonly _focusTracker: IFocusTracker; - // private readonly _scopedInstantiationService: IInstantiationService; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; private readonly _responseEditorFocusedContextKey!: IContextKey; @@ -44,6 +45,7 @@ export class TerminalChatWidget extends Disposable { fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @ILanguageService private readonly _languageService: ILanguageService, @@ -80,12 +82,25 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._container)); } + + private _getAriaLabel(): string { + const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); + if (verbose) { + // TODO: Add verbose description + } + return localize('terminalChatInput', "Terminal Chat Input"); + } + renderTerminalCommand(command: string, requestId: number, shellType?: string): void { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { - padding: { top: 2, bottom: 2 }, + readOnly: false, + ariaLabel: this._getAriaLabel(), + fontSize: 13, + lineHeight: 20, + padding: { top: 8, bottom: 8 }, overviewRulerLanes: 0, glyphMargin: false, lineNumbers: 'off', @@ -133,7 +148,9 @@ export class TerminalChatWidget extends Disposable { } else { this._terminalCommandWidget.setValue(command); } - this._terminalCommandWidget.getModel()?.setLanguage(this._getLanguageFromShell(shellType)); + const languageId = this._getLanguageFromShell(shellType); + console.log('languageId', languageId); + this._terminalCommandWidget.getModel()?.setLanguage(languageId); } private _getLanguageFromShell(shell?: string): string { From bf87f8dc1e9afcef04eb8465f221628ee13645d3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 12:20:22 -0600 Subject: [PATCH 073/753] Revert "add session support, get cancellation to work" This reverts commit b01c303b90fe931c2f4e816172fa8fdaf13ee963. --- .../inlineChat/browser/inlineChatSession.ts | 9 +- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 102 +++++++----------- .../chat/browser/terminalChatWidget.ts | 17 ++- 4 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 678c8177976..693fde05bab 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -316,7 +316,7 @@ export class SessionExchange { constructor( readonly prompt: SessionPrompt, - readonly response: ReplyResponse | EmptyResponse | ErrorResponse | TerminalResponse + readonly response: ReplyResponse | EmptyResponse | ErrorResponse ) { } } @@ -324,13 +324,6 @@ export class EmptyResponse { } -export class TerminalResponse { - readonly message: string; - constructor(message: string) { - this.message = message; - } -} - export class ErrorResponse { readonly message: string; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 7aef66f5631..641737229dc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.cancel(); + contr?.chatWidget?.cancel(); } }); @@ -234,7 +234,7 @@ registerActiveXtermAction({ // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, menu: [/*{ - // TODO: Enable + // TODO: Enable this id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), group: '2_feedback', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 86c17c99d22..54430cd8fee 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -25,14 +25,25 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { generateUuid } from 'vs/base/common/uuid'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { marked } from 'vs/base/common/marked/marked'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { Emitter, Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; +import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { assertType } from 'vs/base/common/types'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModel } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; const enum Message { NONE = 0, @@ -63,15 +74,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; + private _scopedInstantiationService: IInstantiationService | undefined; + private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); - private _activeSession?: Session; - - private _fakeEditor: CodeEditorWidget | undefined; - - get lastResponseContent(): string | undefined { return (this._activeSession?.lastExchange?.response as TerminalResponse).message; } + private _lastInput: string | undefined; + private _lastResponseContent: string | undefined; + get lastResponseContent(): string | undefined { return this._lastResponseContent; } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); @@ -89,9 +100,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, - @IModelService private readonly _modelService: IModelService + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); @@ -111,6 +120,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } + this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -118,10 +128,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { + const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); + this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._instantiationService.createInstance( + this._fakeEditor = this._scopedInstantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { @@ -154,28 +166,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - this._activeSession = undefined; - } - } - - private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } - - const session = await this._inlineChatSessionService.createSession( - editor, - { editMode: EditMode.Live }, - token - ); - - if (!session) { - return; - } - - this._activeSession = session; + this._cancellationTokenSource.cancel(); } private _forcedPlaceholder: string | undefined = undefined; @@ -204,15 +195,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { + this._lastInput = this._chatWidget?.rawValue?.input(); + if (!this._lastInput) { + return; + } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = new CancellationTokenSource().token; - if (this._fakeEditor?.hasModel()) { - await this._startSession(this._fakeEditor, cancellationToken); - } - assertType(this._activeSession); - const inputValue = this.chatWidget!.inlineChatWidget.value; - this._activeSession!.addInput(new SessionPrompt(inputValue)); + const cancellationToken = this._cancellationTokenSource.token; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { @@ -228,11 +217,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr sessionId: generateUuid(), requestId, agentId: this._terminalAgentId, - message: inputValue, + message: this._lastInput, // TODO: ? variables: { variables: [] }, }; - let response: EmptyResponse | TerminalResponse = EmptyResponse; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -242,16 +230,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { - response = e; + } finally { this._requestActiveContextKey.set(false); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); - if (response === EmptyResponse) { - response = new TerminalResponse(responseContent); - } } + this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); @@ -268,9 +254,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } - if (this._activeSession?.lastInput) { - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); - } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); } @@ -306,15 +289,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { + if (!this._lastInput || !this._lastResponseContent) { + return; + } const widget = await this._chatWidgetService.revealViewForProvider('copilot'); if (widget && widget.viewModel) { - const request = this._activeSession?.lastExchange; - const input = request?.prompt.value; - const response = request?.response as TerminalResponse; - if (!input || !response) { - return; - } - this._chatService.addCompleteRequest(widget.viewModel.sessionId, input, undefined, response); + this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); widget.focusLastMessage(); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 9f4f960ee5e..39892d8a381 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -42,7 +42,6 @@ export class TerminalChatWidget extends Disposable { constructor( terminalElement: HTMLElement, - fakeParentEditor: CodeEditorWidget, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -65,6 +64,18 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); + // The inline chat widget requires a parent editor that it bases the diff view on, since the + // terminal doesn't use that feature we can just pass in an unattached editor instance. + const fakeParentEditorElement = document.createElement('div'); + const fakeParentEditor = this._instantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + extraEditorClassName: 'ignore-panel-bg' + }, + { isSimpleWidget: true } + ); + this._inlineChatWidget = this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, @@ -203,6 +214,10 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } + cancel(): void { + // TODO: Impl + this._inlineChatWidget.value = ''; + } focus(): void { this._inlineChatWidget.focus(); } From 55b7c86d9a818977b50d602a01d2bdfe7e72f74b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:00:27 -0600 Subject: [PATCH 074/753] use chat model --- .../chat/browser/terminalChatActions.ts | 4 +- .../chat/browser/terminalChatController.ts | 73 +++++++++++++++++-- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 641737229dc..a1561e9b63c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -63,7 +63,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.hide(); + contr?.clear(); } }); @@ -178,7 +178,7 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.chatWidget?.cancel(); + contr?.cancel(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 54430cd8fee..6daeb2bd3cb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -44,6 +44,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { ChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; const enum Message { NONE = 0, @@ -80,15 +81,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _messages = this._store.add(new Emitter()); + private _currentRequest: ChatRequestModel | undefined; + private _lastInput: string | undefined; private _lastResponseContent: string | undefined; - get lastResponseContent(): string | undefined { return this._lastResponseContent; } + get lastResponseContent(): string | undefined { + // TODO: use model + return this._lastResponseContent; + } readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store); readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store); private _terminalAgentId = 'terminal'; + private _model: ChatModel | undefined; + constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -99,8 +107,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService ) { super(); @@ -166,7 +174,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr } cancel(): void { - this._cancellationTokenSource.cancel(); + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } } private _forcedPlaceholder: string | undefined = undefined; @@ -194,7 +204,24 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._updatePlaceholder(); } + clear(): void { + this._model?.dispose(); + this._model = undefined; + this.updateModel(); + this._chatWidget?.rawValue?.hide(); + this._chatWidget?.rawValue?.setValue(undefined); + } + + private updateModel(): void { + const providerInfo = this._chatService.getProviderInfos()?.[0]; + if (!providerInfo) { + return; + } + this._model ??= this._chatService.startSession(providerInfo.id, CancellationToken.None); + } + async acceptInput(): Promise { + this.updateModel(); this._lastInput = this._chatWidget?.rawValue?.input(); if (!this._lastInput) { return; @@ -211,6 +238,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (progress.kind === 'content' || progress.kind === 'markdownContent') { responseContent += progress.content; } + if (this._currentRequest) { + this._model?.acceptResponseProgress(this._currentRequest, progress); + } }; const requestId = generateUuid(); const requestProps: IChatAgentRequest = { @@ -221,6 +251,16 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: ? variables: { variables: [] }, }; + // TODO: fix requester usrname, responder username + this._model?.initialize({ id: this._accessibilityRequestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + const request: IParsedChatRequest = { + text: this._lastInput, + parts: [] + }; + const requestVarData: IChatRequestVariableData = { + variables: [] + }; + this._currentRequest = this._model?.addRequest(request, requestVarData); try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -236,6 +276,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); + if (this._currentRequest) { + this._model?.completeResponse(this._currentRequest); + } } this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; @@ -289,18 +332,34 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { - if (!this._lastInput || !this._lastResponseContent) { + const providerInfo = this._chatService.getProviderInfos()?.[0]; + if (!providerInfo) { return; } - const widget = await this._chatWidgetService.revealViewForProvider('copilot'); - if (widget && widget.viewModel) { - this._chatService.addCompleteRequest(widget.viewModel.sessionId, this._lastInput, undefined, { message: this._lastResponseContent }); + const widget = await this._chatWidgetService.revealViewForProvider(providerInfo.id); + if (widget && widget.viewModel && this._model) { + for (const request of this._model.getRequests()) { + if (request.response?.response.value || request.response?.result) { + this._chatService.addCompleteRequest(widget.viewModel.sessionId, + request.message as IParsedChatRequest, + request.variableData, + { + message: request.response.response.value, + result: request.response.result, + followups: request.response.followups + }); + } + } widget.focusLastMessage(); } } override dispose() { + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } super.dispose(); + this.clear(); this._chatWidget?.rawValue?.dispose(); } } From 79ef477d826dc46e9617a8b2db3477841c89e4e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:11:42 -0600 Subject: [PATCH 075/753] rm unused --- .../chat/browser/terminalChatController.ts | 66 ++++--------------- 1 file changed, 14 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 6daeb2bd3cb..bfafbb102fc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -9,42 +9,25 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { Schemas } from 'vs/base/common/network'; -import { assertType } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { marked } from 'vs/base/common/marked/marked'; +import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { InlineChatResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { Emitter, Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { CTX_INLINE_CHAT_RESPONSE_TYPES, EditMode, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { EmptyResponse, Session, SessionExchange, SessionPrompt, TerminalResponse } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { assertType } from 'vs/base/common/types'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModel } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { ChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; + + +import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; const enum Message { NONE = 0, @@ -74,9 +57,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - - private _scopedInstantiationService: IInstantiationService | undefined; - private _accessibilityRequestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -128,7 +108,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } - this._cancellationTokenSource = new CancellationTokenSource(); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { @@ -136,25 +115,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return; } this._chatWidget = new Lazy(() => { - const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._instance.domElement!)); - this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); - // The inline chat widget requires a parent editor that it bases the diff view on, since the - // terminal doesn't use that feature we can just pass in an unattached editor instance. - const fakeParentEditorElement = document.createElement('div'); - this._fakeEditor = this._scopedInstantiationService.createInstance( - CodeEditorWidget, - fakeParentEditorElement, - { - extraEditorClassName: 'ignore-panel-bg' - }, - { isSimpleWidget: true } - ); - - const path = `terminal-chat-input-${this._instance.instanceId}`; - const inputUri = URI.from({ path: path, scheme: Schemas.untitled, fragment: '' }); - const result: ITextModel = this._modelService.createModel('', null, inputUri, false); - this._fakeEditor.setModel(result); - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._fakeEditor!, this._instance); + + const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { @@ -228,7 +190,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatAccessibilityService.acceptRequest(); this._requestActiveContextKey.set(true); - const cancellationToken = this._cancellationTokenSource.token; + const cancellationToken = new CancellationTokenSource().token; let responseContent = ''; const progressCallback = (progress: IChatProgress) => { if (cancellationToken.isCancellationRequested) { From f7915fc65f7639a109599dc133f7dd2fa17282a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:06:19 -0800 Subject: [PATCH 076/753] Accept -> Run, add insert button, improve keybindings --- .../chat/browser/terminalChat.ts | 3 +- .../browser/terminalChatAccessibilityHelp.ts | 2 +- .../chat/browser/terminalChatActions.ts | 44 ++++++++++++++++--- .../chat/browser/terminalChatController.ts | 4 +- .../chat/browser/terminalChatWidget.ts | 4 +- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index f75c9c3d9a7..d409361d517 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -13,7 +13,8 @@ export const enum TerminalChatCommandId { FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful', FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue', - AcceptCommand = 'workbench.action.terminal.chat.acceptCommand', + RunCommand = 'workbench.action.terminal.chat.runCommand', + InsertCommand = 'workbench.action.terminal.chat.insertCommand', ViewInChat = 'workbench.action.terminal.chat.viewInChat', } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 2fe29ea5d8a..d851609de14 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,7 +48,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); const content = []; const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); - const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.AcceptCommand)?.getAriaLabel(); + const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index a1561e9b63c..901f536f0bf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -71,9 +71,9 @@ registerActiveXtermAction({ registerActiveXtermAction({ - id: TerminalChatCommandId.AcceptCommand, - title: localize2('acceptCommand', 'Terminal: Accept Chat Command'), - shortTitle: localize2('accept', 'Accept'), + id: TerminalChatCommandId.RunCommand, + title: localize2('runCommand', 'Terminal: Run Chat Command'), + shortTitle: localize2('run', 'Run'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -83,8 +83,8 @@ registerActiveXtermAction({ ), icon: Codicon.check, keybinding: { - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseEditorFocused, TerminalContextKeys.chatRequestActive.negate()), - weight: KeybindingWeight.EditorCore + 7, + when: TerminalContextKeys.chatRequestActive.negate(), + weight: KeybindingWeight.WorkbenchContrib + 7, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { @@ -98,7 +98,39 @@ registerActiveXtermAction({ return; } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - contr?.acceptCommand(); + contr?.acceptCommand(true); + } +}); + +registerActiveXtermAction({ + id: TerminalChatCommandId.InsertCommand, + title: localize2('insertCommand', 'Terminal: Insert Chat Command'), + shortTitle: localize2('insert', 'Insert'), + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatAgentRegistered, + CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + ), + icon: Codicon.check, + keybinding: { + when: TerminalContextKeys.chatRequestActive.negate(), + weight: KeybindingWeight.WorkbenchContrib + 7, + primary: KeyMod.Alt | KeyCode.Enter, + }, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + }, + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptCommand(false); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index bfafbb102fc..903c81b74fb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -285,8 +285,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr return !!this._chatWidget?.rawValue?.hasFocus(); } - acceptCommand(): void { - this._chatWidget?.rawValue?.acceptCommand(); + acceptCommand(shouldExecute: boolean): void { + this._chatWidget?.rawValue?.acceptCommand(shouldExecute); } reveal(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 39892d8a381..53a67093ffb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -233,12 +233,12 @@ export class TerminalChatWidget extends Disposable { this.hideTerminalCommandWidget(); } } - acceptCommand(): void { + acceptCommand(shouldExecute: boolean): void { const value = this._terminalCommandWidget?.getValue(); if (!value) { return; } - this._instance.sendText(value, false, true); + this._instance.runCommand(value, shouldExecute); this.hide(); } updateProgress(progress?: IChatProgress): void { From a9c300cd68ec0b2f1290aa32141367fbb0abf6da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:11:28 -0800 Subject: [PATCH 077/753] Trim suggestion to prevent running on alt+enter --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 903c81b74fb..d0d9590813e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -246,7 +246,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); - const codeBlock = match?.groups?.content; + const codeBlock = match?.groups?.content.trim(); const shellType = match?.groups?.language; this._accessibilityRequestId++; if (cancellationToken.isCancellationRequested) { From 4d81b4e49afb5581431a2534463ed522894a0b36 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:15:53 -0600 Subject: [PATCH 078/753] cancel request on clear --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index d0d9590813e..6f292f51fde 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -167,9 +167,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr } clear(): void { + if (this._currentRequest) { + this._model?.cancelRequest(this._currentRequest); + } this._model?.dispose(); this._model = undefined; - this.updateModel(); this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); } From 50a0433578968dea132f628203144e55c991246f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:17:31 -0600 Subject: [PATCH 079/753] rename --- .../chat/browser/terminalChatController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 6f292f51fde..2585ae766da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -57,7 +57,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _lastResponseTypeContextKey!: IContextKey; - private _accessibilityRequestId: number = 0; + private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -216,7 +216,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: { variables: [] }, }; // TODO: fix requester usrname, responder username - this._model?.initialize({ id: this._accessibilityRequestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + this._model?.initialize({ id: this._requestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); const request: IParsedChatRequest = { text: this._lastInput, parts: [] @@ -250,15 +250,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr const match = regex.exec(firstCodeBlockContent); const codeBlock = match?.groups?.content.trim(); const shellType = match?.groups?.language; - this._accessibilityRequestId++; + this._requestId++; if (cancellationToken.isCancellationRequested) { return; } if (codeBlock) { - this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._accessibilityRequestId, shellType); + this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); } else { - this._chatWidget?.rawValue?.renderMessage(responseContent, this._accessibilityRequestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); From 1a0199379a05a6a2f9f5c9e80ec410b2808334a0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:22:56 -0800 Subject: [PATCH 080/753] More tweaks to response style --- .../terminalContrib/chat/browser/media/terminalChatWidget.css | 3 ++- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 ++ .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index a916f2dd0d7..1feb4831c23 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -13,7 +13,8 @@ .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); - background-color: var(--vscode-interactive-result-editor-background-color); + /* TODO: Make themeable */ + background-color: #181818; } .terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 901f536f0bf..50c1587b124 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -35,6 +35,8 @@ registerActiveXtermAction({ } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); + // TODO: Remove this before merging to main + contr?.chatWidget?.setValue('list files'); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 53a67093ffb..637e2f77104 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -122,7 +122,7 @@ export class TerminalChatWidget extends Disposable { scrollbar: { useShadows: false, vertical: 'hidden', - horizontal: 'auto', + horizontal: 'hidden', alwaysConsumeMouseWheel: false }, lineDecorationsWidth: 0, From 64e1a39e1bcf91e38860ead296912c06976f8f60 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:23:34 -0800 Subject: [PATCH 081/753] Remove category prefix from commands --- .../chat/browser/terminalChatActions.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 50c1587b124..19d9fdf140c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -18,7 +18,7 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Start, - title: localize2('startChat', 'Terminal: Start Chat'), + title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), @@ -42,7 +42,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Hide, - title: localize2('closeChat', 'Terminal: Close Chat'), + title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], @@ -74,7 +74,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.RunCommand, - title: localize2('runCommand', 'Terminal: Run Chat Command'), + title: localize2('runCommand', 'Run Chat Command'), shortTitle: localize2('run', 'Run'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -106,7 +106,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.InsertCommand, - title: localize2('insertCommand', 'Terminal: Insert Chat Command'), + title: localize2('insertCommand', 'Insert Chat Command'), shortTitle: localize2('insert', 'Insert'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), @@ -138,7 +138,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, - title: localize2('viewInChat', 'Terminal: View in Chat'), + title: localize2('viewInChat', 'View in Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -164,7 +164,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.MakeRequest, - title: localize2('makeChatRequest', 'Terminal: Make Chat Request'), + title: localize2('makeChatRequest', 'Make Chat Request'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -195,7 +195,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Cancel, - title: localize2('cancelChat', 'Terminal: Cancel Chat'), + title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatRequestActive, From 85aaa473fbf47de6762d990d6982c42e53886340 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:25:54 -0800 Subject: [PATCH 082/753] Hide command border before it's shown --- .../chat/browser/terminalChatWidget.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 637e2f77104..bc7442b5f48 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -31,7 +31,7 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } - private readonly _terminalCommandWidgetContainer: HTMLElement; + private _terminalCommandWidgetContainer: HTMLElement | undefined; private _terminalCommandWidget: CodeEditorWidget | undefined; private readonly _focusTracker: IFocusTracker; @@ -60,10 +60,6 @@ export class TerminalChatWidget extends Disposable { this._container.classList.add('terminal-inline-chat'); terminalElement.appendChild(this._container); - this._terminalCommandWidgetContainer = document.createElement('div'); - this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); - this._container.prepend(this._terminalCommandWidgetContainer); - // The inline chat widget requires a parent editor that it bases the diff view on, since the // terminal doesn't use that feature we can just pass in an unattached editor instance. const fakeParentEditorElement = document.createElement('div'); @@ -106,6 +102,9 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); if (!this._terminalCommandWidget) { + this._terminalCommandWidgetContainer = document.createElement('div'); + this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._terminalCommandWidgetContainer); this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { readOnly: false, ariaLabel: this._getAriaLabel(), @@ -248,9 +247,9 @@ export class TerminalChatWidget extends Disposable { return this._focusTracker; } hideTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer.classList.add('hide'); + this._terminalCommandWidgetContainer?.classList.add('hide'); } showTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer.classList.remove('hide'); + this._terminalCommandWidgetContainer?.classList.remove('hide'); } } From 45e54c6f405e759e91e736a35ae8a58b1209cceb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:28:54 -0800 Subject: [PATCH 083/753] Wider widget, resize suggestion based on content height --- .../chat/browser/terminalChatWidget.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index bc7442b5f48..d65b9a0ffc3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -105,7 +105,7 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer = document.createElement('div'); this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); this._container.prepend(this._terminalCommandWidgetContainer); - this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { + const widget = this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { readOnly: false, ariaLabel: this._getAriaLabel(), fontSize: 13, @@ -146,14 +146,18 @@ export class TerminalChatWidget extends Disposable { }, { isSimpleWidget: true })); this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._register(this._terminalCommandWidget.onDidChangeModelContent(e => { + const height = widget.getContentHeight(); + widget.layout(new Dimension(640, height)); + })); this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { if (!model || !this._terminalCommandWidget) { return; } - this._terminalCommandWidget.layout(new Dimension(400, 0)); + this._terminalCommandWidget.layout(new Dimension(640, 0)); this._terminalCommandWidget.setModel(model); const height = this._terminalCommandWidget.getContentHeight(); - this._terminalCommandWidget.layout(new Dimension(400, height)); + this._terminalCommandWidget.layout(new Dimension(640, height)); }); } else { this._terminalCommandWidget.setValue(command); @@ -194,7 +198,7 @@ export class TerminalChatWidget extends Disposable { return this._modelService.createModel(resource.fragment, null, resource, false); } reveal(): void { - this._inlineChatWidget.layout(new Dimension(400, 150)); + this._inlineChatWidget.layout(new Dimension(640, 150)); this._container.classList.remove('hide'); this._focusedContextKey.set(true); this._visibleContextKey.set(true); From 0dadd8f5402a6e2b21c696d16750c72b34f03ec3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:29:53 -0800 Subject: [PATCH 084/753] Trim command before accepting --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index d65b9a0ffc3..c049e71d013 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -237,7 +237,8 @@ export class TerminalChatWidget extends Disposable { } } acceptCommand(shouldExecute: boolean): void { - const value = this._terminalCommandWidget?.getValue(); + // Trim command to remove any whitespace, otherwise this may execute the command + const value = this._terminalCommandWidget?.getValue().trim(); if (!value) { return; } From 69592b6d498fa3dab8c4c7a1710843f7c12aae37 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:33:37 -0800 Subject: [PATCH 085/753] Standardize on workbench contrib weight --- .../chat/browser/terminalChatActions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 19d9fdf140c..3ad13dbb898 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -22,7 +22,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, }, f1: true, precondition: ContextKeyExpr.and( @@ -47,7 +47,7 @@ registerActiveXtermAction({ primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, menu: { @@ -86,7 +86,7 @@ registerActiveXtermAction({ icon: Codicon.check, keybinding: { when: TerminalContextKeys.chatRequestActive.negate(), - weight: KeybindingWeight.WorkbenchContrib + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { @@ -118,7 +118,7 @@ registerActiveXtermAction({ icon: Codicon.check, keybinding: { when: TerminalContextKeys.chatRequestActive.negate(), - weight: KeybindingWeight.WorkbenchContrib + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, menu: { @@ -175,7 +175,7 @@ registerActiveXtermAction({ icon: Codicon.send, keybinding: { when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), - weight: KeybindingWeight.EditorCore + 7, + weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, menu: { From 06afd593735a230025ed5b9b5010574ef5748d22 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:36:50 -0800 Subject: [PATCH 086/753] Await model (fixes lang mode) --- .../chat/browser/terminalChatWidget.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index c049e71d013..91bec4acda0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -98,9 +98,10 @@ export class TerminalChatWidget extends Disposable { return localize('terminalChatInput', "Terminal Chat Input"); } - renderTerminalCommand(command: string, requestId: number, shellType?: string): void { + async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); + let model: ITextModel | null | void = null; if (!this._terminalCommandWidget) { this._terminalCommandWidgetContainer = document.createElement('div'); this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); @@ -150,7 +151,7 @@ export class TerminalChatWidget extends Disposable { const height = widget.getContentHeight(); widget.layout(new Dimension(640, height)); })); - this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { + model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { if (!model || !this._terminalCommandWidget) { return; } @@ -162,9 +163,13 @@ export class TerminalChatWidget extends Disposable { } else { this._terminalCommandWidget.setValue(command); } - const languageId = this._getLanguageFromShell(shellType); - console.log('languageId', languageId); - this._terminalCommandWidget.getModel()?.setLanguage(languageId); + if (!model) { + model = this._terminalCommandWidget.getModel(); + } + if (model) { + const languageId = this._getLanguageFromShell(shellType); + model.setLanguage(languageId); + } } private _getLanguageFromShell(shell?: string): string { From b269a4b6a536614f5f6357fa686572e2bc8aa1b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:44:46 -0800 Subject: [PATCH 087/753] Improve handling of wrapping in response editor --- .../chat/browser/terminalChatWidget.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 91bec4acda0..32e1e685ba8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -144,21 +145,22 @@ export class TerminalChatWidget extends Disposable { showWords: true, showStatusBar: false, }, + wordWrap: 'on' }, { isSimpleWidget: true })); this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); - this._register(this._terminalCommandWidget.onDidChangeModelContent(e => { + this._register(Event.any(this._terminalCommandWidget.onDidChangeModelContent, this._terminalCommandWidget.onDidChangeModelDecorations)(() => { const height = widget.getContentHeight(); widget.layout(new Dimension(640, height)); })); model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { - if (!model || !this._terminalCommandWidget) { + if (!model) { return; } - this._terminalCommandWidget.layout(new Dimension(640, 0)); - this._terminalCommandWidget.setModel(model); - const height = this._terminalCommandWidget.getContentHeight(); - this._terminalCommandWidget.layout(new Dimension(640, height)); + widget.layout(new Dimension(640, 0)); + widget.setModel(model); + const height = widget.getContentHeight(); + widget.layout(new Dimension(640, height)); }); } else { this._terminalCommandWidget.setValue(command); From aa5626dc23146a651ae078db2860e436d9e9104a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 13:57:34 -0600 Subject: [PATCH 088/753] fix issue with cancel --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 2585ae766da..8f9c3defee0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -139,6 +139,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.cancelRequest(this._currentRequest); } + this._requestActiveContextKey.set(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(false); + this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(''); + this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); } private _forcedPlaceholder: string | undefined = undefined; From a1ac397ba2b9c4404836b3606b5b52bc580a880f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:06:24 -0800 Subject: [PATCH 089/753] Pull response editor into separate class --- .../chat/browser/terminalChatWidget.ts | 256 ++++++++++-------- 1 file changed, 145 insertions(+), 111 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 32e1e685ba8..014f746c786 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -6,7 +6,7 @@ import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -33,29 +33,24 @@ export class TerminalChatWidget extends Disposable { public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } private _terminalCommandWidgetContainer: HTMLElement | undefined; - private _terminalCommandWidget: CodeEditorWidget | undefined; + private _responseEditor: TerminalChatResponseEditor | undefined; private readonly _focusTracker: IFocusTracker; private readonly _focusedContextKey: IContextKey; private readonly _visibleContextKey: IContextKey; - private readonly _responseEditorFocusedContextKey!: IContextKey; constructor( terminalElement: HTMLElement, private readonly _instance: ITerminalInstance, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, - @ILanguageService private readonly _languageService: ILanguageService, - @IModelService private readonly _modelService: IModelService ) { super(); this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); - this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -90,105 +85,13 @@ export class TerminalChatWidget extends Disposable { this._focusTracker = this._register(trackFocus(this._container)); } - - private _getAriaLabel(): string { - const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); - if (verbose) { - // TODO: Add verbose description - } - return localize('terminalChatInput', "Terminal Chat Input"); - } - async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); - let model: ITextModel | null | void = null; - if (!this._terminalCommandWidget) { - this._terminalCommandWidgetContainer = document.createElement('div'); - this._terminalCommandWidgetContainer.classList.add('terminal-inline-chat-response'); - this._container.prepend(this._terminalCommandWidgetContainer); - const widget = this._terminalCommandWidget = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._terminalCommandWidgetContainer, { - readOnly: false, - ariaLabel: this._getAriaLabel(), - fontSize: 13, - lineHeight: 20, - padding: { top: 8, bottom: 8 }, - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - hideCursorInOverviewRuler: true, - selectOnLineNumbers: false, - selectionHighlight: false, - scrollbar: { - useShadows: false, - vertical: 'hidden', - horizontal: 'hidden', - alwaysConsumeMouseWheel: false - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - dragAndDrop: false, - revealHorizontalRightPadding: 5, - minimap: { enabled: false }, - guides: { indentation: false }, - rulers: [], - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - quickSuggestions: false, - suggest: { - showIcons: false, - showSnippets: false, - showWords: true, - showStatusBar: false, - }, - wordWrap: 'on' - }, { isSimpleWidget: true })); - this._register(this._terminalCommandWidget.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); - this._register(this._terminalCommandWidget.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); - this._register(Event.any(this._terminalCommandWidget.onDidChangeModelContent, this._terminalCommandWidget.onDidChangeModelDecorations)(() => { - const height = widget.getContentHeight(); - widget.layout(new Dimension(640, height)); - })); - model = await this._getTextModel(URI.from({ path: `terminal-inline-chat-${this._instance.instanceId}`, scheme: 'terminal-inline-chat', fragment: command })).then((model) => { - if (!model) { - return; - } - widget.layout(new Dimension(640, 0)); - widget.setModel(model); - const height = widget.getContentHeight(); - widget.layout(new Dimension(640, height)); - }); - } else { - this._terminalCommandWidget.setValue(command); - } - if (!model) { - model = this._terminalCommandWidget.getModel(); - } - if (model) { - const languageId = this._getLanguageFromShell(shellType); - model.setLanguage(languageId); - } - } - - private _getLanguageFromShell(shell?: string): string { - switch (shell) { - case 'fish': - return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; - case 'zsh': - return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; - case 'bash': - return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; - case 'sh': - return 'shellscript'; - case 'pwsh': - return 'powershell'; - default: - return 'plaintext'; + if (!this._responseEditor) { + this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); } + this._responseEditor.setValue(command); } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { @@ -197,13 +100,6 @@ export class TerminalChatWidget extends Disposable { this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } - private async _getTextModel(resource: URI): Promise { - const existing = this._modelService.getModel(resource); - if (existing && !existing.isDisposed()) { - return existing; - } - return this._modelService.createModel(resource.fragment, null, resource, false); - } reveal(): void { this._inlineChatWidget.layout(new Dimension(640, 150)); this._container.classList.remove('hide'); @@ -215,7 +111,8 @@ export class TerminalChatWidget extends Disposable { this.hideTerminalCommandWidget(); this._container.classList.add('hide'); this._inlineChatWidget.value = ''; - this._terminalCommandWidget?.setValue(''); + this._responseEditor?.dispose(); + this._responseEditor = undefined; this._inlineChatWidget.updateChatMessage(undefined); this._inlineChatWidget.updateFollowUps(undefined); this._inlineChatWidget.updateProgress(false); @@ -245,7 +142,7 @@ export class TerminalChatWidget extends Disposable { } acceptCommand(shouldExecute: boolean): void { // Trim command to remove any whitespace, otherwise this may execute the command - const value = this._terminalCommandWidget?.getValue().trim(); + const value = this._responseEditor?.getValue().trim(); if (!value) { return; } @@ -265,3 +162,140 @@ export class TerminalChatWidget extends Disposable { this._terminalCommandWidgetContainer?.classList.remove('hide'); } } + +class TerminalChatResponseEditor extends Disposable { + private readonly _editorContainer: HTMLElement; + private readonly _editor: CodeEditorWidget; + + private readonly _responseEditorFocusedContextKey: IContextKey; + + readonly model: Promise; + + constructor( + initialCommandResponse: string, + shellType: string | undefined, + private readonly _container: HTMLElement, + private readonly _instance: ITerminalInstance, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILanguageService private readonly _languageService: ILanguageService, + @IModelService private readonly _modelService: IModelService, + ) { + super(); + + this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + + this._editorContainer = document.createElement('div'); + this._editorContainer.classList.add('terminal-inline-chat-response'); + this._container.prepend(this._editorContainer); + this._register(toDisposable(() => this._editorContainer.remove())); + const editor = this._register(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, { + readOnly: false, + ariaLabel: this._getAriaLabel(), + fontSize: 13, + lineHeight: 20, + padding: { top: 8, bottom: 8 }, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + hideCursorInOverviewRuler: true, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + useShadows: false, + vertical: 'hidden', + horizontal: 'hidden', + alwaysConsumeMouseWheel: false + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + dragAndDrop: false, + revealHorizontalRightPadding: 5, + minimap: { enabled: false }, + guides: { indentation: false }, + rulers: [], + renderWhitespace: 'none', + dropIntoEditor: { enabled: true }, + quickSuggestions: false, + suggest: { + showIcons: false, + showSnippets: false, + showWords: true, + showStatusBar: false, + }, + wordWrap: 'on' + }, { isSimpleWidget: true })); + this._editor = editor; + this._register(editor.onDidFocusEditorText(() => this._responseEditorFocusedContextKey.set(true))); + this._register(editor.onDidBlurEditorText(() => this._responseEditorFocusedContextKey.set(false))); + this._register(Event.any(editor.onDidChangeModelContent, editor.onDidChangeModelDecorations)(() => { + const height = editor.getContentHeight(); + editor.layout(new Dimension(640, height)); + })); + + this.model = this._getTextModel(URI.from({ + path: `terminal-inline-chat-${this._instance.instanceId}`, + scheme: 'terminal-inline-chat', + fragment: initialCommandResponse + })); + this.model.then(model => { + if (model) { + // Initial layout + editor.layout(new Dimension(640, 0)); + editor.setModel(model); + const height = editor.getContentHeight(); + editor.layout(new Dimension(640, height)); + + // Initialize language + const languageId = this._getLanguageFromShell(shellType); + model.setLanguage(languageId); + } + }); + } + + private _getAriaLabel(): string { + const verbose = this._configurationService.getValue(AccessibilityVerbositySettingId.Chat); + if (verbose) { + // TODO: Add verbose description + } + return localize('terminalChatInput', "Terminal Chat Input"); + } + + private async _getTextModel(resource: URI): Promise { + const existing = this._modelService.getModel(resource); + if (existing && !existing.isDisposed()) { + return existing; + } + return this._modelService.createModel(resource.fragment, null, resource, false); + } + + private _getLanguageFromShell(shell?: string): string { + switch (shell) { + case 'fish': + return this._languageService.isRegisteredLanguageId('fish') ? 'fish' : 'shellscript'; + case 'zsh': + return this._languageService.isRegisteredLanguageId('zsh') ? 'zsh' : 'shellscript'; + case 'bash': + return this._languageService.isRegisteredLanguageId('bash') ? 'bash' : 'shellscript'; + case 'sh': + return 'shellscript'; + case 'pwsh': + return 'powershell'; + default: + return 'plaintext'; + } + } + + setValue(value: string) { + this._editor.setValue(value); + } + + getValue(): string { + return this._editor.getValue(); + } +} From 98e01f9016d920127d70b468731b75e5ae65dfb4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:07:54 -0600 Subject: [PATCH 090/753] rm unused --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 014f746c786..8448d7e5c61 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -121,10 +121,6 @@ export class TerminalChatWidget extends Disposable { this._visibleContextKey.set(false); this._instance.focus(); } - cancel(): void { - // TODO: Impl - this._inlineChatWidget.value = ''; - } focus(): void { this._inlineChatWidget.focus(); } From 204e5ba8295196b5305ae15747ddfbb7159405e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:08:26 -0800 Subject: [PATCH 091/753] Register chat widget against contr --- .../chat/browser/terminalChatController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 8f9c3defee0..e609ce9067f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -116,17 +116,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._chatWidget = new Lazy(() => { - const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance); - chatWidget.focusTracker.onDidFocus(() => { + const chatWidget = this._register(this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance)); + this._register(chatWidget.focusTracker.onDidFocus(() => { TerminalChatController.activeChatWidget = this; if (!isDetachedTerminalInstance(this._instance)) { this._terminalService.setActiveInstance(this._instance); } - }); - chatWidget.focusTracker.onDidBlur(() => { + })); + this._register(chatWidget.focusTracker.onDidBlur(() => { TerminalChatController.activeChatWidget = undefined; this._instance.resetScrollbarVisibility(); - }); + })); if (!this._instance.domElement) { throw new Error('FindWidget expected terminal DOM to be initialized'); } From 2453accbe856fa1a9266459fda3d84572b1fdd89 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:09:49 -0800 Subject: [PATCH 092/753] Pull placeholder from model --- .../terminalContrib/chat/browser/terminalChatController.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index e609ce9067f..4c0ef9145d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -155,9 +155,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? ''; - // TODO: Pass through session placeholder - // return this._forcedPlaceholder ?? this._session?.session.placeholder ?? ''; + return this._forcedPlaceholder ?? this._model?.inputPlaceholder ?? ''; } setPlaceholder(text: string): void { From 342570da6a7973b7030b00ae45ecb524944bb64a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:14:59 -0800 Subject: [PATCH 093/753] Bring back initial placeholder and info on hide --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 8448d7e5c61..a4bf3b55990 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -78,13 +78,17 @@ export class TerminalChatWidget extends Disposable { feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK } ); - this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); - this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); + this._reset(); this._container.appendChild(this._inlineChatWidget.domNode); this._focusTracker = this._register(trackFocus(this._container)); } + private _reset() { + this._inlineChatWidget.placeholder = localize('default.placeholder', "Ask how to do something in the terminal"); + this._inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated commands may be incorrect")); + } + async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); this.showTerminalCommandWidget(); @@ -110,7 +114,7 @@ export class TerminalChatWidget extends Disposable { hide(): void { this.hideTerminalCommandWidget(); this._container.classList.add('hide'); - this._inlineChatWidget.value = ''; + this._reset(); this._responseEditor?.dispose(); this._responseEditor = undefined; this._inlineChatWidget.updateChatMessage(undefined); From 4de80e1c389db99170b22f785587c7b93bcb5f3f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:19:11 -0800 Subject: [PATCH 094/753] Handle hide/show in editor class --- .../chat/browser/terminalChatWidget.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index a4bf3b55990..499d7e375da 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, IFocusTracker, trackFocus } from 'vs/base/browser/dom'; +import { Dimension, IFocusTracker, hide, show, trackFocus } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -32,7 +32,6 @@ export class TerminalChatWidget extends Disposable { private readonly _inlineChatWidget: InlineChatWidget; public get inlineChatWidget(): InlineChatWidget { return this._inlineChatWidget; } - private _terminalCommandWidgetContainer: HTMLElement | undefined; private _responseEditor: TerminalChatResponseEditor | undefined; private readonly _focusTracker: IFocusTracker; @@ -91,7 +90,7 @@ export class TerminalChatWidget extends Disposable { async renderTerminalCommand(command: string, requestId: number, shellType?: string): Promise { this._chatAccessibilityService.acceptResponse(command, requestId); - this.showTerminalCommandWidget(); + this._responseEditor?.show(); if (!this._responseEditor) { this._responseEditor = this._instantiationService.createInstance(TerminalChatResponseEditor, command, shellType, this._container, this._instance); } @@ -99,7 +98,7 @@ export class TerminalChatWidget extends Disposable { } renderMessage(message: string, accessibilityRequestId: number, requestId: string): void { - this.hideTerminalCommandWidget(); + this._responseEditor?.hide(); this._inlineChatWidget.updateChatMessage({ message: new MarkdownString(message), requestId, providerId: 'terminal' }); this._chatAccessibilityService.acceptResponse(message, accessibilityRequestId); } @@ -112,7 +111,6 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.focus(); } hide(): void { - this.hideTerminalCommandWidget(); this._container.classList.add('hide'); this._reset(); this._responseEditor?.dispose(); @@ -137,7 +135,7 @@ export class TerminalChatWidget extends Disposable { setValue(value?: string) { this._inlineChatWidget.value = value ?? ''; if (!value) { - this.hideTerminalCommandWidget(); + this._responseEditor?.hide(); } } acceptCommand(shouldExecute: boolean): void { @@ -155,12 +153,6 @@ export class TerminalChatWidget extends Disposable { public get focusTracker(): IFocusTracker { return this._focusTracker; } - hideTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer?.classList.add('hide'); - } - showTerminalCommandWidget(): void { - this._terminalCommandWidgetContainer?.classList.remove('hide'); - } } class TerminalChatResponseEditor extends Disposable { @@ -298,4 +290,12 @@ class TerminalChatResponseEditor extends Disposable { getValue(): string { return this._editor.getValue(); } + + hide() { + hide(this._editorContainer); + } + + show() { + show(this._editorContainer); + } } From 556c0e67c9df1257fefbf9fada21a519ee6c4897 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:25:55 -0600 Subject: [PATCH 095/753] await initialization --- .../terminalContrib/chat/browser/terminalChatController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 4c0ef9145d8..c96dd7326eb 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -217,8 +217,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr // TODO: ? variables: { variables: [] }, }; - // TODO: fix requester usrname, responder username - this._model?.initialize({ id: this._requestId, requesterUsername: 'userGesture', responderUsername: 'terminal' }, undefined); + await this._model?.waitForInitialization(); const request: IParsedChatRequest = { text: this._lastInput, parts: [] From 7162515cd775bae7eec4af2b04f3ba6d6574bb08 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:40:35 -0600 Subject: [PATCH 096/753] open in chat view --- .../terminalContrib/chat/browser/terminalChatActions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 3ad13dbb898..1a813210025 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -144,15 +144,20 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages) ), icon: Codicon.commentDiscussion, - menu: { + menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), }, + { + id: MENU_TERMINAL_CHAT_WIDGET, + group: 'main', + order: 1, + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { return; From 2408cc098b7feb83e9b48054e3e59b90dfb444e2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 14:48:23 -0600 Subject: [PATCH 097/753] if no model, focus input like quick chat does --- .../terminalContrib/chat/browser/terminalChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c96dd7326eb..9c274d92b9a 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -316,6 +316,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr } } widget.focusLastMessage(); + } else if (!this._model) { + widget?.focusInput(); } } From a3602c3db9bb1c8fc41a050c727fe8a5090dfbc6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:53:30 -0800 Subject: [PATCH 098/753] Remove margin between chat and response --- .../chat/browser/media/terminalChatWidget.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 1feb4831c23..25d492a063c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -11,6 +11,10 @@ height: auto !important; } +.terminal-inline-chat .inline-chat { + margin-top: 0 !important; +} + .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); /* TODO: Make themeable */ @@ -28,6 +32,5 @@ } .terminal-inline-chat .terminal-inline-chat-response { - margin: 0 0 8px 0; padding-left: 8px; } From 3c0c5106b81dc4f2c169415e816b361d6d13010a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 15:08:56 -0600 Subject: [PATCH 099/753] add chatResponseType context key for terminal --- .../terminal/common/terminalContextKey.ts | 9 +++++++++ .../chat/browser/terminalChatActions.ts | 16 ++++++++-------- .../chat/browser/terminalChatController.ts | 11 +++++------ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 8ea1469a4b1..dcca7e64e68 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -45,6 +45,12 @@ export const enum TerminalContextKeyStrings { ChatInputHasText = 'terminalChatInputHasText', ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatResponseType = 'terminalChatResponseType', +} + +export const enum TerminalChatResponseTypes { + Message = 'message', + TerminalCommand = 'terminalCommand' } export namespace TerminalContextKeys { @@ -183,4 +189,7 @@ export namespace TerminalContextKeys { /** Whether the chat response editor is focused */ export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** The type of chat response, if any */ + export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 1a813210025..aa8f4e045d1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,10 +9,10 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; @@ -81,7 +81,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { @@ -93,7 +93,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -113,7 +113,7 @@ registerActiveXtermAction({ ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatRequestActive.negate(), TerminalContextKeys.chatAgentRegistered, - CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty) + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { @@ -125,7 +125,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -150,13 +150,13 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.OnlyMessages), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalContextKeys.chatRequestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), CTX_INLINE_CHAT_RESPONSE_TYPES.isEqualTo(InlineChatResponseTypes.Empty), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 9c274d92b9a..fd5f072846b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -19,11 +19,10 @@ import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/cont import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { InlineChatResponseTypes, CTX_INLINE_CHAT_RESPONSE_TYPES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; @@ -56,7 +55,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; - private readonly _lastResponseTypeContextKey!: IContextKey; + private readonly _responseTypeContextKey!: IContextKey; private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -97,7 +96,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._lastResponseTypeContextKey = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -257,10 +256,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr } if (codeBlock) { this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); - this._lastResponseTypeContextKey.set(InlineChatResponseTypes.Empty); + this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); } else { this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); - this._lastResponseTypeContextKey.set(InlineChatResponseTypes.OnlyMessages); + this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); this._messages.fire(Message.ACCEPT_INPUT); From de3d5a4c4b128f59afa06a25253c1d243dd720e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 15:30:14 -0600 Subject: [PATCH 100/753] rm terminal stuff from inline chat actions --- .../contrib/inlineChat/electron-sandbox/inlineChatActions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index 57c29d41e9d..99862b01d7b 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -20,17 +20,16 @@ import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/c import { localize2 } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; export class HoldToSpeak extends AbstractInlineChatAction { constructor() { super({ id: 'inlineChat.holdForSpeech', - precondition: ContextKeyExpr.and(HasSpeechProvider, ContextKeyExpr.or(CTX_INLINE_CHAT_VISIBLE, TerminalContextKeys.chatVisible)), + precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), title: localize2('holdForSpeech', "Hold for Speech"), keybinding: { - when: ContextKeyExpr.or(EditorContextKeys.textInputFocus, TerminalContextKeys.chatFocused), + when: EditorContextKeys.textInputFocus, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyI, }, From a9f226d8eba2370051e88cfedf8e47f716e261f8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:02:41 -0600 Subject: [PATCH 101/753] fix requestId, sessionId, wip feedback --- .../chat/browser/terminalChatActions.ts | 28 ++++++++---- .../chat/browser/terminalChatController.ts | 43 +++++++++++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index aa8f4e045d1..187d16f6d51 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -226,7 +226,7 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, @@ -234,11 +234,14 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(true); } }); @@ -247,7 +250,7 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), icon: Codicon.thumbsdown, @@ -255,11 +258,14 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - // TODO: Fill in ctx - // when: CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.notEqualsTo(undefined), + when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(false); } }); @@ -284,7 +290,11 @@ registerActiveXtermAction({ order: 3 }], run: (_xterm, _accessor, activeInstance) => { - // TODO: Impl + // if (isDetachedTerminalInstance(activeInstance)) { + // return; + // } + // const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + // contr?.acceptFeedback(true); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index fd5f072846b..030e2bb8fdf 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -9,7 +9,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { generateUuid } from 'vs/base/common/uuid'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -18,7 +17,7 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService, IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IChatProgress, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -134,6 +133,25 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } + acceptFeedback(helpful: boolean): void { + const providerId = this._chatService.getProviderInfos()?.[0]?.id; + if (!providerId || !this._currentRequest || !this._model) { + return; + } + this._chatService.notifyUserAction({ + providerId, + sessionId: this._model?.sessionId, + requestId: this._currentRequest.id, + agentId: this._terminalAgentId, + //TODO: fill in error details if any etc. + result: {}, + action: { + kind: 'vote', + direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down + }, + }); + } + cancel(): void { if (this._currentRequest) { this._model?.cancelRequest(this._currentRequest); @@ -175,6 +193,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model = undefined; this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); + this._responseTypeContextKey.reset(); } private updateModel(): void { @@ -207,15 +226,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._model?.acceptResponseProgress(this._currentRequest, progress); } }; - const requestId = generateUuid(); - const requestProps: IChatAgentRequest = { - sessionId: generateUuid(), - requestId, - agentId: this._terminalAgentId, - message: this._lastInput, - // TODO: ? - variables: { variables: [] }, - }; + await this._model?.waitForInitialization(); const request: IParsedChatRequest = { text: this._lastInput, @@ -225,6 +236,14 @@ export class TerminalChatController extends Disposable implements ITerminalContr variables: [] }; this._currentRequest = this._model?.addRequest(request, requestVarData); + const requestProps: IChatAgentRequest = { + sessionId: this._model!.sessionId, + requestId: this._currentRequest!.id, + agentId: this._terminalAgentId, + message: this._lastInput, + // TODO: ? + variables: { variables: [] }, + }; try { const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, [], cancellationToken); this._chatWidget?.rawValue?.inlineChatWidget.updateChatMessage(undefined); @@ -258,7 +277,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.renderTerminalCommand(codeBlock, this._requestId, shellType); this._responseTypeContextKey.set(TerminalChatResponseTypes.TerminalCommand); } else { - this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, requestId); + this._chatWidget?.rawValue?.renderMessage(responseContent, this._requestId, this._currentRequest!.id); this._responseTypeContextKey.set(TerminalChatResponseTypes.Message); } this._chatWidget?.rawValue?.inlineChatWidget.updateToolbar(true); From 4b295eda6f9de1762e44a818085d1736976bef8f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:08:57 -0600 Subject: [PATCH 102/753] fix feedback action --- .../chat/browser/terminalChatController.ts | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 030e2bb8fdf..baa3943274c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -138,18 +138,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!providerId || !this._currentRequest || !this._model) { return; } - this._chatService.notifyUserAction({ - providerId, - sessionId: this._model?.sessionId, - requestId: this._currentRequest.id, - agentId: this._terminalAgentId, - //TODO: fill in error details if any etc. - result: {}, - action: { - kind: 'vote', - direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down - }, - }); + // TODO:extract into helper method + for (const request of this._model.getRequests()) { + if (request.response?.response.value || request.response?.result) { + this._chatService.notifyUserAction({ + providerId, + sessionId: request.session.sessionId, + requestId: request.id, + agentId: request.response?.agent?.id, + result: request.response?.result, + action: { + kind: 'vote', + direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down + }, + }); + } + } } cancel(): void { From ed4a3ce3ec379421c165b7fb1af78c5c241dd95d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:12:04 -0600 Subject: [PATCH 103/753] fix feedback action, add styling --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index baa3943274c..f8e97671171 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -154,6 +154,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } } + this._chatWidget?.rawValue?.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); } cancel(): void { From 559a3413bc85864e22f29df1fb7a207d5aba7590 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 08:57:13 -0600 Subject: [PATCH 104/753] add support for issue reporting when terminal agent supports that --- .../terminal/common/terminalContextKey.ts | 4 +++ .../chat/browser/terminalChatActions.ts | 36 +++++++++---------- .../chat/browser/terminalChatController.ts | 24 +++++++++---- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index dcca7e64e68..d823a5b7fd9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -46,6 +46,7 @@ export const enum TerminalContextKeyStrings { ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', ChatResponseType = 'terminalChatResponseType', + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting' } export const enum TerminalChatResponseTypes { @@ -192,4 +193,7 @@ export namespace TerminalContextKeys { /** The type of chat response, if any */ export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + + /** Whether the response supports issue reporting */ + export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 187d16f6d51..d1586f8c4ba 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -228,7 +228,6 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), - // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('helpful'), icon: Codicon.thumbsup, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -252,7 +251,6 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), - // TODO: toggled: CTX_INLINE_CHAT_LAST_FEEDBACK.isEqualTo('unhelpful'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, @@ -274,27 +272,29 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, + TerminalContextKeys.chatRequestActive.negate(), + TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalContextKeys.chatResponseSupportsIssueReporting ), - // TODO: precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), icon: Codicon.report, - menu: [/*{ - // TODO: Enable this + menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(CTX_TERMINAL_CHAT_SUPPORT_ISSUE_REPORTING, CTX_TERMINAL_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)), - group: '2_feedback', + when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + group: 'inline', order: 3 - }, */{ - id: MENU_TERMINAL_CHAT_WIDGET, - group: 'config', - order: 3 - }], + }], + // { + // id: MENU_TERMINAL_CHAT_WIDGET, + // when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + // group: 'config', + // order: 3 + // }], run: (_xterm, _accessor, activeInstance) => { - // if (isDetachedTerminalInstance(activeInstance)) { - // return; - // } - // const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); - // contr?.acceptFeedback(true); + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.acceptFeedback(); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index f8e97671171..996effa0743 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -17,7 +17,7 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService, IChatProgress, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IChatProgress, InteractiveSessionVoteDirection, ChatUserAction } from 'vs/workbench/contrib/chat/common/chatService'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -55,6 +55,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _responseTypeContextKey!: IContextKey; + private readonly __responseSupportsIssueReportingContextKey!: IContextKey; + private _requestId: number = 0; private _messages = this._store.add(new Emitter()); @@ -96,6 +98,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); + this.__responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -133,11 +136,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr }); } - acceptFeedback(helpful: boolean): void { + acceptFeedback(helpful?: boolean): void { const providerId = this._chatService.getProviderInfos()?.[0]?.id; if (!providerId || !this._currentRequest || !this._model) { return; } + let action: ChatUserAction; + if (helpful === undefined) { + action = { kind: 'bug' }; + } else { + action = { kind: 'vote', direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down }; + } // TODO:extract into helper method for (const request of this._model.getRequests()) { if (request.response?.response.value || request.response?.result) { @@ -147,10 +156,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr requestId: request.id, agentId: request.response?.agent?.id, result: request.response?.result, - action: { - kind: 'vote', - direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down - }, + action }); } } @@ -267,8 +273,12 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._model?.completeResponse(this._currentRequest); } + const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; + if (supportIssueReporting !== undefined) { + this.__responseSupportsIssueReportingContextKey.set(supportIssueReporting); + } + this._lastResponseContent = responseContent; } - this._lastResponseContent = responseContent; const firstCodeBlockContent = marked.lexer(responseContent).filter(token => token.type === 'code')?.[0]?.raw; const regex = /```(?\w+)\n(?[\s\S]*?)```/g; const match = regex.exec(firstCodeBlockContent); From 705d61ad975a564f0b7b30d6c3e12fefda85dfb3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 09:02:11 -0600 Subject: [PATCH 105/753] rm todo --- .../terminalContrib/chat/browser/terminalChatController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 996effa0743..70ee41baa68 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -66,7 +66,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr private _lastInput: string | undefined; private _lastResponseContent: string | undefined; get lastResponseContent(): string | undefined { - // TODO: use model return this._lastResponseContent; } From 881321da5cfac6fe0ce6894fe40b2ba6623cba0c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 09:22:53 -0600 Subject: [PATCH 106/753] fix aria label for terminal response editor --- .../chat/browser/terminalChatAccessibilityHelp.ts | 6 ++++-- .../terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index d851609de14..890fb103292 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,7 +48,8 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); const content = []; const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); - const acceptCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); + const runCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); + const insertCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.InsertCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); @@ -58,7 +59,8 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { content.push(localize('inlineChat.results', "A result may contain a terminal command or just a message. In either case, the result will be announced.")); content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponseMessage', 'If just a message comes back, it can be inspected in the accessible view ({0}).', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.inspectTerminalCommand', 'If a terminal command comes back, it can be inspected in an editor reached via Shift+Tab.')); - content.push(acceptCommandKeybinding ? localize('inlineChat.acceptCommand', 'With focus in the command editor, the Terminal: Accept Chat Command ({0}) action.', acceptCommandKeybinding) : localize('inlineChat.acceptCommandNoKb', 'Accept a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(runCommandKeybinding ? localize('inlineChat.runCommand', 'With focus in the input box or command editor, the Terminal: Run Chat Command ({0}) action.', runCommandKeybinding) : localize('inlineChat.runCommandNoKb', 'Run a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); + content.push(insertCommandKeybinding ? localize('inlineChat.insertCommand', 'With focus in the input box command editor, the Terminal: Insert Chat Command ({0}) action.', insertCommandKeybinding) : localize('inlineChat.insertCommandNoKb', 'Insert a command by tabbing to the button as the action is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); content.push(localize('chat.signals', "Accessibility Signals can be changed via settings with a prefix of signals.chat. By default, if a request takes more than 4 seconds, you will hear a sound indicating that progress is still occurring.")); return content.join('\n\n'); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 499d7e375da..2cc70c7eb07 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -255,7 +255,7 @@ class TerminalChatResponseEditor extends Disposable { if (verbose) { // TODO: Add verbose description } - return localize('terminalChatInput', "Terminal Chat Input"); + return localize('terminalResponseEditor', "Terminal Response Editor"); } private async _getTextModel(resource: URI): Promise { From ee1cb48bb1005d7299e97f8445783a7c976f9c08 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:17:11 -0600 Subject: [PATCH 107/753] add discard action --- .../chat/browser/terminalChat.ts | 3 +- .../chat/browser/terminalChatActions.ts | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index d409361d517..a2349bc25c1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -7,7 +7,8 @@ import { MenuId } from 'vs/platform/actions/common/actions'; export const enum TerminalChatCommandId { Start = 'workbench.action.terminal.chat.start', - Hide = 'workbench.action.terminal.chat.close', + Close = 'workbench.action.terminal.chat.close', + Discard = 'workbench.action.terminal.chat.discard', MakeRequest = 'workbench.action.terminal.chat.makeRequest', Cancel = 'workbench.action.terminal.chat.cancel', FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index d1586f8c4ba..abf356fbdff 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -41,7 +41,7 @@ registerActiveXtermAction({ }); registerActiveXtermAction({ - id: TerminalChatCommandId.Hide, + id: TerminalChatCommandId.Close, title: localize2('closeChat', 'Close Chat'), keybinding: { primary: KeyCode.Escape, @@ -70,6 +70,32 @@ registerActiveXtermAction({ }); +registerActiveXtermAction({ + id: TerminalChatCommandId.Discard, + title: localize2('discard', 'Discard'), + icon: Codicon.discard, + menu: { + id: MENU_TERMINAL_CHAT_WIDGET_STATUS, + group: '0_main', + order: 2, + when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + }, + f1: true, + precondition: ContextKeyExpr.and( + ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), + ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalContextKeys.chatFocused, + TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + ), + run: (_xterm, _accessor, activeInstance) => { + if (isDetachedTerminalInstance(activeInstance)) { + return; + } + const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); + contr?.clear(); + } +}); registerActiveXtermAction({ From 29924c918fc21025c13354b62ff260678fda83db Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:32:42 -0600 Subject: [PATCH 108/753] add feedback context key, get styling to apply --- .../contrib/terminal/common/terminalContextKey.ts | 6 +++++- .../chat/browser/terminalChatActions.ts | 2 ++ .../chat/browser/terminalChatController.ts | 11 ++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index d823a5b7fd9..0c577a9df8a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -46,7 +46,8 @@ export const enum TerminalContextKeyStrings { ChatAgentRegistered = 'terminalChatAgentRegistered', ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', ChatResponseType = 'terminalChatResponseType', - ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting' + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', + ChatSessionResponseVote = 'terminalChatSessionResponseVote', } export const enum TerminalChatResponseTypes { @@ -196,4 +197,7 @@ export namespace TerminalContextKeys { /** Whether the response supports issue reporting */ export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + + /** The chat vote, if any for the response, if any */ + export const chatSessionResponseVote = new RawContextKey(TerminalContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index abf356fbdff..cd984a4d61e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -255,6 +255,7 @@ registerActiveXtermAction({ TerminalContextKeys.chatResponseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, + toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', @@ -277,6 +278,7 @@ registerActiveXtermAction({ ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), TerminalContextKeys.chatResponseType.notEqualsTo(undefined), ), + toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 70ee41baa68..ca3bdbab893 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -55,7 +55,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr private readonly _requestActiveContextKey!: IContextKey; private readonly _terminalAgentRegisteredContextKey!: IContextKey; private readonly _responseTypeContextKey!: IContextKey; - private readonly __responseSupportsIssueReportingContextKey!: IContextKey; + private readonly _responseSupportsIssueReportingContextKey!: IContextKey; + private readonly _sessionResponseVoteContextKey!: IContextKey; private _requestId: number = 0; @@ -97,7 +98,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); - this.__responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { @@ -144,6 +146,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (helpful === undefined) { action = { kind: 'bug' }; } else { + this._sessionResponseVoteContextKey.set(helpful ? 'up' : 'down'); action = { kind: 'vote', direction: helpful ? InteractiveSessionVoteDirection.Up : InteractiveSessionVoteDirection.Down }; } // TODO:extract into helper method @@ -204,6 +207,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.hide(); this._chatWidget?.rawValue?.setValue(undefined); this._responseTypeContextKey.reset(); + this._sessionResponseVoteContextKey.reset(); + this._requestActiveContextKey.reset(); } private updateModel(): void { @@ -274,7 +279,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting; if (supportIssueReporting !== undefined) { - this.__responseSupportsIssueReportingContextKey.set(supportIssueReporting); + this._responseSupportsIssueReportingContextKey.set(supportIssueReporting); } this._lastResponseContent = responseContent; } From ba24d929651af34cd13b52b21cf88d5521068194 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 13:07:26 -0600 Subject: [PATCH 109/753] move css to right file --- .../terminal/browser/media/terminal.css | 18 ------------------ .../chat/browser/media/terminalChatWidget.css | 12 ++++++++++++ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 1aaf1516c5e..488815cf258 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -565,21 +565,3 @@ .monaco-workbench .xterm.terminal.hide { visibility: hidden; } - -.monaco-workbench .terminal-chat-widget { - z-index: 33 !important; - position: absolute; - top: 10px; -} - -.monaco-workbench .terminal-inline-chat.hide { - visibility: hidden; -} - -.monaco-workbench .terminal-inline-chat-response.hide { - visibility: hidden; -} - -.monaco-workbench .terminal-inline-chat .chatMessageContent { - width: 400px !important; -} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 25d492a063c..602560618cc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -15,6 +15,18 @@ margin-top: 0 !important; } +.terminal-inline-chat.hide { + visibility: hidden; +} + +.terminal-inline-chat-response.hide { + visibility: hidden; +} + +.terminal-inline-chat .chatMessageContent { + width: 400px !important; +} + .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); /* TODO: Make themeable */ From c57d3061b82d738f3062365127a60660af3929da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:23:50 -0800 Subject: [PATCH 110/753] Move a11y help provider into terminal chat folder --- .../browser/accessibilityConfiguration.ts | 2 + .../inlineChat/browser/inlineChatActions.ts | 3 +- .../browser/terminal.chat.contribution.ts | 2 + ...rminalChatAccessibilityHelpContribution.ts | 48 +++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index bba5a1f648e..ca7ebdca1a0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -45,6 +45,7 @@ export const enum AccessibilityVerbositySettingId { DiffEditor = 'accessibility.verbosity.diffEditor', Chat = 'accessibility.verbosity.panelChat', InlineChat = 'accessibility.verbosity.inlineChat', + TerminalChat = 'accessibility.verbosity.terminalChat', InlineCompletions = 'accessibility.verbosity.inlineCompletions', KeybindingsEditor = 'accessibility.verbosity.keybindingsEditor', Notebook = 'accessibility.verbosity.notebook', @@ -57,6 +58,7 @@ export const enum AccessibilityVerbositySettingId { export const enum AccessibleViewProviderId { Terminal = 'terminal', + TerminalChat = 'terminal-chat', TerminalHelp = 'terminal-help', DiffEditor = 'diffEditor', Chat = 'panelChat', diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index d9f725ed61d..8deb70a073e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -30,7 +30,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -786,6 +785,6 @@ export class InlineAccessibilityHelpContribution extends Disposable { return; } runAccessibilityHelpAction(accessor, codeEditor, 'inlineChat'); - }, ContextKeyExpr.and(ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED), TerminalContextKeys.chatFocused.negate()))); + }, ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0c2dba50035..0049340fadd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -8,6 +8,7 @@ import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/brow import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; +import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution'; import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; @@ -15,3 +16,4 @@ registerTerminalContribution(TerminalChatController.ID, TerminalChatController, registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(TerminalChatAccessibilityHelpContribution.ID, TerminalChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts new file mode 100644 index 00000000000..6e5f79e1cb1 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; + +export class TerminalChatAccessibilityHelpContribution extends Disposable { + static ID = 'terminalChatAccessiblityHelp'; + constructor() { + super(); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalContextKeys.chatFocused)); + } +} + +export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + + const instance = terminalService.activeInstance; + if (!instance) { + return; + } + + const helpText = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, + provideContent: () => helpText, + onClose: () => TerminalChatController.get(instance)?.focus(), + options: { type: AccessibleViewType.Help } + }); +} + +export function getAccessibilityHelpText(accessor: ServicesAccessor): string { + const content = []; + // TODO: Fill in more help text + content.push(localize('chat.overview', 'The terminal chat view is comprised of an input box, an editor where suggested commands are provided (Shift+Tab) and buttons to action the suggestion.')); + return content.join('\n\n'); +} From ab75d8d74b5dc30c84c65581e3674ee19f3744bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:27:24 -0800 Subject: [PATCH 111/753] Remove terminal action ids from inline button config provider --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 537c7b8951f..3aeb25cd2e2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES, 'workbench.action.terminal.chat.acceptCommand', 'workbench.action.terminal.chat.viewInChat'].includes(action.id)) { + } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES].includes(action.id)) { return { isSecondary: false }; } else { return { isSecondary: true }; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index cd984a4d61e..f374aee77e8 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -116,6 +116,7 @@ registerActiveXtermAction({ primary: KeyMod.CtrlCmd | KeyCode.Enter, }, menu: { + // TODO: Allow action to be made primary, the action list is hardcoded within InlineChatWidget id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, From ae04cff1450064534d4490d7b21fa138fb06e03e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:31:56 -0800 Subject: [PATCH 112/753] Move context keys and response types into terminalContrib --- .../terminal/common/terminalContextKey.ts | 42 ---------- .../chat/browser/terminalChat.ts | 49 ++++++++++++ .../browser/terminalChatAccessibilityHelp.ts | 5 +- ...rminalChatAccessibilityHelpContribution.ts | 4 +- .../browser/terminalChatAccessibleView.ts | 4 +- .../chat/browser/terminalChatActions.ts | 80 +++++++++---------- .../chat/browser/terminalChatController.ts | 12 +-- .../chat/browser/terminalChatWidget.ts | 9 +-- 8 files changed, 105 insertions(+), 100 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 0c577a9df8a..72335480aee 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -39,20 +39,6 @@ export const enum TerminalContextKeyStrings { ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled', - ChatFocus = 'terminalChatFocus', - ChatVisible = 'terminalChatVisible', - ChatActiveRequest = 'terminalChatActiveRequest', - ChatInputHasText = 'terminalChatInputHasText', - ChatAgentRegistered = 'terminalChatAgentRegistered', - ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', - ChatResponseType = 'terminalChatResponseType', - ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', - ChatSessionResponseVote = 'terminalChatSessionResponseVote', -} - -export const enum TerminalChatResponseTypes { - Message = 'message', - TerminalCommand = 'terminalCommand' } export namespace TerminalContextKeys { @@ -172,32 +158,4 @@ export namespace TerminalContextKeys { ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always') ) ); - - - /** Whether the chat widget is focused */ - export const chatFocused = new RawContextKey(TerminalContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); - - /** Whether the chat widget is visible */ - export const chatVisible = new RawContextKey(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); - - /** Whether there is an active chat request */ - export const chatRequestActive = new RawContextKey(TerminalContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); - - /** Whether the chat input has text */ - export const chatInputHasText = new RawContextKey(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); - - /** Whether the terminal chat agent has been registered */ - export const chatAgentRegistered = new RawContextKey(TerminalContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); - - /** Whether the chat response editor is focused */ - export const chatResponseEditorFocused = new RawContextKey(TerminalContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); - - /** The type of chat response, if any */ - export const chatResponseType = new RawContextKey(TerminalContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); - - /** Whether the response supports issue reporting */ - export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); - - /** The chat vote, if any for the response, if any */ - export const chatSessionResponseVote = new RawContextKey(TerminalContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index a2349bc25c1..5aefe757469 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const enum TerminalChatCommandId { Start = 'workbench.action.terminal.chat.start', @@ -24,3 +26,50 @@ export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget'); export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status'); export const MENU_TERMINAL_CHAT_WIDGET_FEEDBACK = MenuId.for('terminalChatWidget.feedback'); export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar'); + +export const enum TerminalChatContextKeyStrings { + ChatFocus = 'terminalChatFocus', + ChatVisible = 'terminalChatVisible', + ChatActiveRequest = 'terminalChatActiveRequest', + ChatInputHasText = 'terminalChatInputHasText', + ChatAgentRegistered = 'terminalChatAgentRegistered', + ChatResponseEditorFocused = 'terminalChatResponseEditorFocused', + ChatResponseType = 'terminalChatResponseType', + ChatResponseSupportsIssueReporting = 'terminalChatResponseSupportsIssueReporting', + ChatSessionResponseVote = 'terminalChatSessionResponseVote', +} + +export const enum TerminalChatResponseTypes { + Message = 'message', + TerminalCommand = 'terminalCommand' +} + +export namespace TerminalChatContextKeys { + + /** Whether the chat widget is focused */ + export const chatFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + + /** Whether the chat widget is visible */ + export const chatVisible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + + /** Whether there is an active chat request */ + export const chatRequestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); + + /** Whether the chat input has text */ + export const chatInputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + + /** Whether the terminal chat agent has been registered */ + export const chatAgentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + + /** Whether the chat response editor is focused */ + export const chatResponseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + + /** The type of chat response, if any */ + export const chatResponseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + + /** Whether the response supports issue reporting */ + export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + + /** The chat vote, if any for the response, if any */ + export const chatSessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); +} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 890fb103292..c9631f7fa1d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -13,8 +13,7 @@ import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { @@ -39,7 +38,7 @@ export class TerminalInlineChatAccessibilityHelpContribution extends Disposable options: { type: AccessibleViewType.Help } }); return true; - }, ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.or(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts index 6e5f79e1cb1..afc24a15163 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -10,14 +10,14 @@ import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/wo import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalChatAccessibilityHelpContribution extends Disposable { static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalContextKeys.chatFocused)); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.chatFocused)); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 8c146698482..93854689479 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -10,7 +10,7 @@ import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; export class TerminalInlineChatAccessibleViewContribution extends Disposable { @@ -35,6 +35,6 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { options: { type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.and(TerminalContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index f374aee77e8..7f8dc50524d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -12,8 +12,8 @@ import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; registerActiveXtermAction({ @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, @@ -46,7 +46,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatVisible), weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, @@ -78,15 +78,15 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 2, - when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatFocused, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatFocused, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -105,13 +105,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, @@ -120,7 +120,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -138,13 +138,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, - TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, @@ -152,7 +152,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -169,21 +169,21 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, ), icon: Codicon.commentDiscussion, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.chatRequestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -200,13 +200,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatAgentRegistered, + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatAgentRegistered, CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.chatRequestActive.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, @@ -214,7 +214,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - when: TerminalContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.chatRequestActive.negate(), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -230,14 +230,14 @@ registerActiveXtermAction({ title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive, - TerminalContextKeys.chatAgentRegistered + TerminalChatContextKeys.chatRequestActive, + TerminalChatContextKeys.chatAgentRegistered ), icon: Codicon.debugStop, menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', - when: TerminalContextKeys.chatRequestActive, + when: TerminalChatContextKeys.chatRequestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -253,15 +253,15 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined) + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, - toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('up'), + toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -277,15 +277,15 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), ), - toggled: TerminalContextKeys.chatSessionResponseVote.isEqualTo('down'), + toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - when: TerminalContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -301,20 +301,20 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalContextKeys.chatRequestActive.negate(), - TerminalContextKeys.chatResponseType.notEqualsTo(undefined), - TerminalContextKeys.chatResponseSupportsIssueReporting + TerminalChatContextKeys.chatRequestActive.negate(), + TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.chatResponseSupportsIssueReporting ), icon: Codicon.report, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), group: 'inline', order: 3 }], // { // id: MENU_TERMINAL_CHAT_WIDGET, - // when: ContextKeyExpr.and(TerminalContextKeys.chatResponseType.notEqualsTo(undefined), TerminalContextKeys.chatResponseSupportsIssueReporting), + // when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), // group: 'config', // order: 3 // }], diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index ca3bdbab893..c3409674e12 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -21,11 +21,11 @@ import { IChatService, IChatProgress, InteractiveSessionVoteDirection, ChatUserA import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalChatResponseTypes, TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget'; import { ChatModel, ChatRequestModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { TerminalChatContextKeys, TerminalChatResponseTypes } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; const enum Message { NONE = 0, @@ -95,11 +95,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._requestActiveContextKey = TerminalContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._terminalAgentRegisteredContextKey = TerminalContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._responseTypeContextKey = TerminalContextKeys.chatResponseType.bindTo(this._contextKeyService); - this._responseSupportsIssueReportingContextKey = TerminalContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); - this._sessionResponseVoteContextKey = TerminalContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalChatContextKeys.chatRequestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalChatContextKeys.chatResponseType.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalChatContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 2cc70c7eb07..82f68ccf9f4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -22,8 +22,7 @@ import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/cha import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; export class TerminalChatWidget extends Disposable { @@ -48,8 +47,8 @@ export class TerminalChatWidget extends Disposable { ) { super(); - this._focusedContextKey = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService); - this._visibleContextKey = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalChatContextKeys.chatFocused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalChatContextKeys.chatVisible.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -176,7 +175,7 @@ class TerminalChatResponseEditor extends Disposable { ) { super(); - this._responseEditorFocusedContextKey = TerminalContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalChatContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('terminal-inline-chat-response'); From 9b38a276ce04615099802f185e911b2f086274e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:33:19 -0800 Subject: [PATCH 113/753] Remove 'chat' from context keys as it's in the namespace --- .../chat/browser/terminalChat.ts | 18 ++--- .../browser/terminalChatAccessibilityHelp.ts | 2 +- ...rminalChatAccessibilityHelpContribution.ts | 2 +- .../browser/terminalChatAccessibleView.ts | 2 +- .../chat/browser/terminalChatActions.ts | 74 +++++++++---------- .../chat/browser/terminalChatController.ts | 10 +-- .../chat/browser/terminalChatWidget.ts | 6 +- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts index 5aefe757469..89893d6d31e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.ts @@ -47,29 +47,29 @@ export const enum TerminalChatResponseTypes { export namespace TerminalChatContextKeys { /** Whether the chat widget is focused */ - export const chatFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); + export const focused = new RawContextKey(TerminalChatContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused.")); /** Whether the chat widget is visible */ - export const chatVisible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); + export const visible = new RawContextKey(TerminalChatContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible.")); /** Whether there is an active chat request */ - export const chatRequestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); + export const requestActive = new RawContextKey(TerminalChatContextKeyStrings.ChatActiveRequest, false, localize('chatRequestActiveContextKey', "Whether there is an active chat request.")); /** Whether the chat input has text */ - export const chatInputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); + export const inputHasText = new RawContextKey(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text.")); /** Whether the terminal chat agent has been registered */ - export const chatAgentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); + export const agentRegistered = new RawContextKey(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered.")); /** Whether the chat response editor is focused */ - export const chatResponseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); + export const responseEditorFocused = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseEditorFocused, false, localize('chatResponseEditorFocusedContextKey', "Whether the chat response editor is focused.")); /** The type of chat response, if any */ - export const chatResponseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); + export const responseType = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseType, undefined, localize('chatResponseTypeContextKey', "The type of chat response, if any")); /** Whether the response supports issue reporting */ - export const chatResponseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); + export const responseSupportsIssueReporting = new RawContextKey(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting")); /** The chat vote, if any for the response, if any */ - export const chatSessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); + export const sessionResponseVote = new RawContextKey(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index c9631f7fa1d..d330683af2e 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -38,7 +38,7 @@ export class TerminalInlineChatAccessibilityHelpContribution extends Disposable options: { type: AccessibleViewType.Help } }); return true; - }, ContextKeyExpr.or(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatResponseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.or(TerminalChatContextKeys.focused, TerminalChatContextKeys.responseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts index afc24a15163..61d11998690 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts @@ -17,7 +17,7 @@ export class TerminalChatAccessibilityHelpContribution extends Disposable { static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.chatFocused)); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 93854689479..798c0bf9774 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -35,6 +35,6 @@ export class TerminalInlineChatAccessibleViewContribution extends Disposable { options: { type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + }, ContextKeyExpr.and(TerminalChatContextKeys.focused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 7f8dc50524d..fc0777f9cde 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalChatContextKeys.focused.negate(), TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, @@ -46,7 +46,7 @@ registerActiveXtermAction({ keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, TerminalChatContextKeys.chatVisible), + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, TerminalChatContextKeys.visible), weight: KeybindingWeight.WorkbenchContrib, }, icon: Codicon.close, @@ -78,15 +78,15 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 2, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatFocused, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) + when: ContextKeyExpr.and(TerminalChatContextKeys.focused, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand)) }, f1: true, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatFocused, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.focused, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -105,13 +105,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Enter, }, @@ -120,7 +120,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 0, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -138,13 +138,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, - TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, + TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand) ), icon: Codicon.check, keybinding: { - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.Enter, }, @@ -152,7 +152,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -169,21 +169,21 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, ), icon: Codicon.commentDiscussion, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_STATUS, group: '0_main', order: 1, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.Message), TerminalChatContextKeys.requestActive.negate()), }, { id: MENU_TERMINAL_CHAT_WIDGET, group: 'main', order: 1, - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.chatResponseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EMPTY.negate(), TerminalChatContextKeys.responseType.isEqualTo(TerminalChatResponseTypes.TerminalCommand), TerminalChatContextKeys.requestActive.negate()), }], run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -200,13 +200,13 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatAgentRegistered, + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.agentRegistered, CTX_INLINE_CHAT_EMPTY.negate() ), icon: Codicon.send, keybinding: { - when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.chatRequestActive.negate()), + when: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, TerminalChatContextKeys.requestActive.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Enter }, @@ -214,7 +214,7 @@ registerActiveXtermAction({ id: MENU_TERMINAL_CHAT_INPUT, group: 'main', order: 1, - when: TerminalChatContextKeys.chatRequestActive.negate(), + when: TerminalChatContextKeys.requestActive.negate(), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -230,14 +230,14 @@ registerActiveXtermAction({ title: localize2('cancelChat', 'Cancel Chat'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatRequestActive, - TerminalChatContextKeys.chatAgentRegistered + TerminalChatContextKeys.requestActive, + TerminalChatContextKeys.agentRegistered ), icon: Codicon.debugStop, menu: { id: MENU_TERMINAL_CHAT_INPUT, group: 'main', - when: TerminalChatContextKeys.chatRequestActive, + when: TerminalChatContextKeys.requestActive, }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -253,15 +253,15 @@ registerActiveXtermAction({ title: localize2('feedbackHelpful', 'Helpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined) + TerminalChatContextKeys.responseType.notEqualsTo(undefined) ), icon: Codicon.thumbsup, - toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('up'), + toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('up'), menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 1, - when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -277,15 +277,15 @@ registerActiveXtermAction({ title: localize2('feedbackUnhelpful', 'Unhelpful'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseType.notEqualsTo(undefined), ), - toggled: TerminalChatContextKeys.chatSessionResponseVote.isEqualTo('down'), + toggled: TerminalChatContextKeys.sessionResponseVote.isEqualTo('down'), icon: Codicon.thumbsdown, menu: { id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, group: 'inline', order: 2, - when: TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), + when: TerminalChatContextKeys.responseType.notEqualsTo(undefined), }, run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { @@ -301,14 +301,14 @@ registerActiveXtermAction({ title: localize2('reportIssue', 'Report Issue'), precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), - TerminalChatContextKeys.chatRequestActive.negate(), - TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), - TerminalChatContextKeys.chatResponseSupportsIssueReporting + TerminalChatContextKeys.requestActive.negate(), + TerminalChatContextKeys.responseType.notEqualsTo(undefined), + TerminalChatContextKeys.responseSupportsIssueReporting ), icon: Codicon.report, menu: [{ id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, - when: ContextKeyExpr.and(TerminalChatContextKeys.chatResponseType.notEqualsTo(undefined), TerminalChatContextKeys.chatResponseSupportsIssueReporting), + when: ContextKeyExpr.and(TerminalChatContextKeys.responseType.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting), group: 'inline', order: 3 }], diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index c3409674e12..0226d6536f0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -95,11 +95,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) { return; } - this._requestActiveContextKey = TerminalChatContextKeys.chatRequestActive.bindTo(this._contextKeyService); - this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.chatAgentRegistered.bindTo(this._contextKeyService); - this._responseTypeContextKey = TerminalChatContextKeys.chatResponseType.bindTo(this._contextKeyService); - this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.chatResponseSupportsIssueReporting.bindTo(this._contextKeyService); - this._sessionResponseVoteContextKey = TerminalChatContextKeys.chatSessionResponseVote.bindTo(this._contextKeyService); + this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService); + this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.agentRegistered.bindTo(this._contextKeyService); + this._responseTypeContextKey = TerminalChatContextKeys.responseType.bindTo(this._contextKeyService); + this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService); + this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService); if (!this._chatAgentService.hasAgent(this._terminalAgentId)) { this._register(this._chatAgentService.onDidChangeAgents(() => { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 82f68ccf9f4..86b3fd8de33 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -47,8 +47,8 @@ export class TerminalChatWidget extends Disposable { ) { super(); - this._focusedContextKey = TerminalChatContextKeys.chatFocused.bindTo(this._contextKeyService); - this._visibleContextKey = TerminalChatContextKeys.chatVisible.bindTo(this._contextKeyService); + this._focusedContextKey = TerminalChatContextKeys.focused.bindTo(this._contextKeyService); + this._visibleContextKey = TerminalChatContextKeys.visible.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('terminal-inline-chat'); @@ -175,7 +175,7 @@ class TerminalChatResponseEditor extends Disposable { ) { super(); - this._responseEditorFocusedContextKey = TerminalChatContextKeys.chatResponseEditorFocused.bindTo(this._contextKeyService); + this._responseEditorFocusedContextKey = TerminalChatContextKeys.responseEditorFocused.bindTo(this._contextKeyService); this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('terminal-inline-chat-response'); From 31df2b2df478f28cc65dc45ce7c7d11500052ba0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:34:40 -0800 Subject: [PATCH 114/753] Revert unneeded change to InlineChatWidget --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 3aeb25cd2e2..ca909ba98e0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -388,7 +388,7 @@ export class InlineChatWidget { buttonConfigProvider: action => { if (action.id === ACTION_REGENERATE_RESPONSE) { return { showIcon: true, showLabel: false, isSecondary: true }; - } else if ([ACTION_VIEW_IN_CHAT, ACTION_ACCEPT_CHANGES].includes(action.id)) { + } else if (action.id === ACTION_VIEW_IN_CHAT || action.id === ACTION_ACCEPT_CHANGES) { return { isSecondary: false }; } else { return { isSecondary: true }; From b9f9060711db2ab23c91e16687065523ff5b6161 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:37:54 -0800 Subject: [PATCH 115/753] Clarify lint suppression reason --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index aeb85706fc6..fe9ddb2f8f0 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -55,7 +55,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ThemeIcon } from 'vs/base/common/themables'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -// TODO: The chat needs to move into contrib/terminal/ as we don't want anything importing from terminalContrib/ + +// This is a one-off/safe import, changing the eslint rules would require duplicating/complicating the rules // eslint-disable-next-line local/code-import-patterns import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; From ec00f84dce8115176af91c856894bc749e2367db Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:01:13 -0800 Subject: [PATCH 116/753] Remove test string --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index fc0777f9cde..72eac948274 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -35,8 +35,6 @@ registerActiveXtermAction({ } const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance); contr?.chatWidget?.reveal(); - // TODO: Remove this before merging to main - contr?.chatWidget?.setValue('list files'); } }); From 29d9a36ba6ea6f23dba59caec8ab344a4db10606 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 15:08:12 -0600 Subject: [PATCH 117/753] set vertical position --- .../chat/browser/terminalChatWidget.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 86b3fd8de33..0653f403de0 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -108,6 +108,18 @@ export class TerminalChatWidget extends Disposable { this._focusedContextKey.set(true); this._visibleContextKey.set(true); this._inlineChatWidget.focus(); + const font = this._instance.xterm?.getFont(); + if (!font?.charHeight) { + return; + } + const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; + const height = font.charHeight * font.lineHeight; + const top = (cursorY + .5) * height; + this._container.style.top = `${top}px`; + const terminalHeight = this._instance.domElement.clientHeight; + if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { + this._container.style.top = ''; + } } hide(): void { this._container.classList.add('hide'); From f70e23725040fca3ae5517c2d26a4b0765a9e444 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:09:23 -0800 Subject: [PATCH 118/753] Resolve simple todos, remove duplication --- .../chat/browser/media/terminalChatWidget.css | 3 +- .../browser/terminal.chat.contribution.ts | 4 +- .../browser/terminalChatAccessibilityHelp.ts | 44 ++++++++--------- ...rminalChatAccessibilityHelpContribution.ts | 48 ------------------- .../chat/browser/terminalChatController.ts | 2 - 5 files changed, 23 insertions(+), 78 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css index 602560618cc..64253f67db5 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/media/terminalChatWidget.css @@ -29,8 +29,7 @@ .terminal-inline-chat .terminal-inline-chat-response { border: 1px solid var(--vscode-input-border, transparent); - /* TODO: Make themeable */ - background-color: #181818; + background-color: var(--vscode-panel-background); } .terminal-inline-chat .terminal-inline-chat-response:has(.monaco-editor.focused) { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts index 0049340fadd..44eabbf13f6 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution.ts @@ -5,15 +5,13 @@ import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; -import { TerminalInlineChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; import { TerminalInlineChatAccessibleViewContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution'; import 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions'; +import { TerminalChatAccessibilityHelpContribution } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp'; registerTerminalContribution(TerminalChatController.ID, TerminalChatController, false); registerWorkbenchContribution2(TerminalInlineChatAccessibleViewContribution.ID, TerminalInlineChatAccessibleViewContribution, WorkbenchPhase.Eventually); -registerWorkbenchContribution2(TerminalInlineChatAccessibilityHelpContribution.ID, TerminalInlineChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(TerminalChatAccessibilityHelpContribution.ID, TerminalChatAccessibilityHelpContribution, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index d330683af2e..c29c7394c3f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -5,43 +5,41 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; -export class TerminalInlineChatAccessibilityHelpContribution extends Disposable { - static ID: 'terminalInlineChatAccessibilityHelpContribution'; +export class TerminalChatAccessibilityHelpContribution extends Disposable { + static ID = 'terminalChatAccessiblityHelp'; constructor() { super(); - this._register(AccessibilityHelpAction.addImplementation(106, 'terminalInlineChat', accessor => { - const terminalService = accessor.get(ITerminalService); - const accessibleViewService = accessor.get(IAccessibleViewService); - const controller: TerminalChatController | undefined = terminalService.activeInstance?.getContribution(TerminalChatController.ID) ?? undefined; - if (controller === undefined) { - return false; - } - const helpContent = getAccessibilityHelpText(accessor); - accessibleViewService.show({ - id: AccessibleViewProviderId.TerminalInlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, - provideContent(): string { return helpContent; }, - onClose() { - controller.focus(); - }, - options: { type: AccessibleViewType.Help } - }); - return true; - }, ContextKeyExpr.or(TerminalChatContextKeys.focused, TerminalChatContextKeys.responseEditorFocused, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); + this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); } } +export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { + const accessibleViewService = accessor.get(IAccessibleViewService); + const terminalService = accessor.get(ITerminalService); + + const instance = terminalService.activeInstance; + if (!instance) { + return; + } + + const helpText = getAccessibilityHelpText(accessor); + accessibleViewService.show({ + id: AccessibleViewProviderId.TerminalChat, + verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, + provideContent: () => helpText, + onClose: () => TerminalChatController.get(instance)?.focus(), + options: { type: AccessibleViewType.Help } + }); +} export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts deleted file mode 100644 index 61d11998690..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelpContribution.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { localize } from 'vs/nls'; -import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; -import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; - -export class TerminalChatAccessibilityHelpContribution extends Disposable { - static ID = 'terminalChatAccessiblityHelp'; - constructor() { - super(); - this._register(AccessibilityHelpAction.addImplementation(110, 'terminalChat', runAccessibilityHelpAction, TerminalChatContextKeys.focused)); - } -} - -export async function runAccessibilityHelpAction(accessor: ServicesAccessor): Promise { - const accessibleViewService = accessor.get(IAccessibleViewService); - const terminalService = accessor.get(ITerminalService); - - const instance = terminalService.activeInstance; - if (!instance) { - return; - } - - const helpText = getAccessibilityHelpText(accessor); - accessibleViewService.show({ - id: AccessibleViewProviderId.TerminalChat, - verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, - provideContent: () => helpText, - onClose: () => TerminalChatController.get(instance)?.focus(), - options: { type: AccessibleViewType.Help } - }); -} - -export function getAccessibilityHelpText(accessor: ServicesAccessor): string { - const content = []; - // TODO: Fill in more help text - content.push(localize('chat.overview', 'The terminal chat view is comprised of an input box, an editor where suggested commands are provided (Shift+Tab) and buttons to action the suggestion.')); - return content.join('\n\n'); -} diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 0226d6536f0..47025af317b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -256,7 +256,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr requestId: this._currentRequest!.id, agentId: this._terminalAgentId, message: this._lastInput, - // TODO: ? variables: { variables: [] }, }; try { @@ -265,7 +264,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr this._chatWidget?.rawValue?.inlineChatWidget.updateFollowUps(undefined); this._chatWidget?.rawValue?.inlineChatWidget.updateProgress(true); this._chatWidget?.rawValue?.inlineChatWidget.updateInfo(localize('thinking', "Thinking\u2026")); - // TODO: this._zone.value.widget.updateInfo(!this._session.lastExchange ? localize('thinking', "Thinking\u2026") : ''); await task; } catch (e) { From fa685221f2d1598d0da2d229ce3885a16b61a492 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 15:19:53 -0600 Subject: [PATCH 119/753] rm todo --- .../chat/browser/terminalChatAccessibilityHelp.ts | 3 +-- .../terminalContrib/chat/browser/terminalChatActions.ts | 2 +- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index c29c7394c3f..dad7747e7f1 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -48,8 +48,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const runCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.RunCommand)?.getAriaLabel(); const insertCommandKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.InsertCommand)?.getAriaLabel(); const makeRequestKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.MakeRequest)?.getAriaLabel(); - //TODO: using this instead of the terminal command bc by definition the inline terminal chat is focused when this dialog is invoked. - const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel(); + const startChatKeybinding = keybindingService.lookupKeybinding(TerminalChatCommandId.Start)?.getAriaLabel(); content.push(localize('inlineChat.overview', "Inline chat occurs within a terminal. It is useful for suggesting terminal commands. Keep in mind that AI generated code may be incorrect.")); content.push(localize('inlineChat.access', "It can be activated using the command: Terminal: Start Chat ({0}), which will focus the input box.", startChatKeybinding)); content.push(makeRequestKeybinding ? localize('inlineChat.input', "The input box is where the user can type a request and can make the request ({0}). The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.", makeRequestKeybinding) : localize('inlineChat.inputNoKb', "The input box is where the user can type a request and can make the request by tabbing to the Make Request button, which is not currently triggerable via keybindings. The widget will be closed and all content will be discarded when the Escape key is pressed and the terminal will regain focus.")); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 72eac948274..04ff3b3a472 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -21,7 +21,7 @@ registerActiveXtermAction({ title: localize2('startChat', 'Start Chat'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, - when: ContextKeyExpr.and(TerminalChatContextKeys.focused.negate(), TerminalContextKeys.focusInAny), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 0653f403de0..4e9bed0f354 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -114,7 +114,7 @@ export class TerminalChatWidget extends Disposable { } const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; const height = font.charHeight * font.lineHeight; - const top = (cursorY + .5) * height; + const top = cursorY * height; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From 27d1fc77f4fee8fdf442f9ae3f1175c1105760aa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 16:52:57 -0600 Subject: [PATCH 120/753] Fix position --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 4e9bed0f354..180d51c1549 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -114,7 +114,7 @@ export class TerminalChatWidget extends Disposable { } const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; const height = font.charHeight * font.lineHeight; - const top = cursorY * height; + const top = cursorY * height + 10; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From a389ff1c805a8cb4b8044a5d88a24323ae268ecd Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:28:28 +0100 Subject: [PATCH 121/753] Use compact hover by default for workbench (#206224) Use a compact hover by default for workbench --- src/vs/platform/hover/browser/hover.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 82d9574ca06..fea45187b43 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -285,6 +285,9 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate hideOnHover: true, hideOnKeyDown: true, }, + appearance: { + compact: true, + }, ...overrideOptions }, focus); } From 2c045aee0a449ff5f400f893e8e97fa9522a09f9 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:33:40 +0100 Subject: [PATCH 122/753] Hoverservice has it's own contextview instance (#206151) Hoverservice with it's own context view --- .../services/hoverService/hoverService.ts | 20 +++++++++++-------- .../contextview/browser/contextViewService.ts | 20 +++++++++++-------- .../parts/editor/breadcrumbsControl.ts | 4 ++-- .../browser/parts/editor/breadcrumbsPicker.ts | 7 ++----- .../browser/outline/documentSymbolsTree.ts | 11 ++-------- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index f7338ae7b52..47fddf11c86 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -7,11 +7,11 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { IHoverService, IHoverOptions } from 'vs/platform/hover/browser/hover'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { HoverWidget } from 'vs/editor/browser/services/hoverService/hoverWidget'; import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -20,10 +20,12 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ContextViewHandler } from 'vs/platform/contextview/browser/contextViewService'; -export class HoverService implements IHoverService { +export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; + private _contextViewHandler: IContextViewProvider; private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; private _lastHoverOptions: IHoverOptions | undefined; @@ -32,13 +34,15 @@ export class HoverService implements IHoverService { constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IContextViewService private readonly _contextViewService: IContextViewService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { + super(); + contextMenuService.onDidShowContextMenu(() => this.hideHover()); + this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService)); } showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean): IHoverWidget | undefined { @@ -84,12 +88,12 @@ export class HoverService implements IHoverService { const targetElement = options.target instanceof HTMLElement ? options.target : options.target.targetElements[0]; options.container = this._layoutService.getContainer(getWindow(targetElement)); } - const provider = this._contextViewService as IContextViewProvider; - provider.showContextView( + + this._contextViewHandler.showContextView( new HoverContextViewDelegate(hover, focus), options.container ); - hover.onRequestLayout(() => provider.layout()); + hover.onRequestLayout(() => this._contextViewHandler.layout()); if (options.persistence?.sticky) { hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => { if (!isAncestor(e.target as HTMLElement, hover.domNode)) { @@ -136,7 +140,7 @@ export class HoverService implements IHoverService { private doHideHover(): void { this._currentHover = undefined; this._currentHoverOptions = undefined; - this._contextViewService.hideContextView(); + this._contextViewHandler.hideContextView(); } private _intersectionChange(entries: IntersectionObserverEntry[], hover: IDisposable): void { diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index f47285746fe..929cb32d5a8 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextView, ContextViewDOMPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { ContextView, ContextViewDOMPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IContextViewDelegate, IContextViewService } from './contextView'; import { getWindow } from 'vs/base/browser/dom'; -export class ContextViewService extends Disposable implements IContextViewService { - declare readonly _serviceBrand: undefined; +export class ContextViewHandler extends Disposable implements IContextViewProvider { private currentViewDisposable: IDisposable = Disposable.None; - private readonly contextView = this._register(new ContextView(this.layoutService.mainContainer, ContextViewDOMPosition.ABSOLUTE)); + protected readonly contextView = this._register(new ContextView(this.layoutService.mainContainer, ContextViewDOMPosition.ABSOLUTE)); constructor( @ILayoutService private readonly layoutService: ILayoutService @@ -55,10 +54,6 @@ export class ContextViewService extends Disposable implements IContextViewServic return disposable; } - getContextViewElement(): HTMLElement { - return this.contextView.getViewElement(); - } - layout(): void { this.contextView.layout(); } @@ -74,3 +69,12 @@ export class ContextViewService extends Disposable implements IContextViewServic this.currentViewDisposable = Disposable.None; } } + +export class ContextViewService extends ContextViewHandler implements IContextViewService { + + declare readonly _serviceBrand: undefined; + + getContextViewElement(): HTMLElement { + return this.contextView.getViewElement(); + } +} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 2a9d829b3ff..bf46167248f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -41,7 +41,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; class OutlineItem extends BreadcrumbsItem { @@ -229,7 +229,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = nativeHoverDelegate; + this._hoverDelegate = getDefaultHoverDelegate('mouse'); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 26f77202d53..9ca0b4310a5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -31,8 +31,6 @@ import { IOutline, IOutlineComparator } from 'vs/workbench/services/outline/brow import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; interface ILayoutInfo { maxHeight: number; @@ -215,13 +213,12 @@ class FileRenderer implements ITreeRenderer, index: number, templateData: IResourceLabel): void { @@ -377,7 +374,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), - [this._instantiationService.createInstance(FileRenderer, labels, nativeHoverDelegate)], + [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { multipleSelectionSupport: false, diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 4e8761ffb1d..c4f2a2def5a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -24,9 +24,6 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { ThemeIcon } from 'vs/base/common/themables'; import { mainWindow } from 'vs/base/browser/window'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -118,20 +115,16 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Mon, 26 Feb 2024 20:11:47 +0900 Subject: [PATCH 123/753] chore: update to electron 28 (#203956) * chore: update electron@28.1.4 * ci: use latest Node.js v18 release 18.18.2 has npm version that has removed the node-gyp script which will cause native modules fail to build that rely on something like `install: node-gyp rebuild` Refs https://github.com/npm/cli/commit/c93edb55f52532e666a9ba2719bee0da725fe6f0 * chore: update rpm dependencies * chore: bump electron@28.2.1 * chore: bump nodejs@18.18.2 * chore: bump electron@28.2.2 * chore: bump distro --- .nvmrc | 2 +- .yarnrc | 4 +- build/azure-pipelines/linux/install.sh | 8 +- build/checksums/electron.txt | 150 ++++++++++++------------- build/checksums/nodejs.txt | 12 +- build/linux/dependencies-generator.js | 2 +- build/linux/dependencies-generator.ts | 2 +- build/linux/rpm/dep-lists.js | 3 - build/linux/rpm/dep-lists.ts | 3 - cgmanifest.json | 14 +-- package.json | 4 +- remote/.yarnrc | 4 +- yarn.lock | 8 +- 13 files changed, 105 insertions(+), 111 deletions(-) diff --git a/.nvmrc b/.nvmrc index 4a1f488b6c3..a9d087399d7 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17.1 +18.19.0 diff --git a/.yarnrc b/.yarnrc index 19c5cb1eb8f..8675f7ab83c 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.3.2" -ms_build_id "26836302" +target "28.2.2" +ms_build_id "26836304" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/linux/install.sh b/build/azure-pipelines/linux/install.sh index 57f58763cca..c75100cac49 100755 --- a/build/azure-pipelines/linux/install.sh +++ b/build/azure-pipelines/linux/install.sh @@ -15,7 +15,7 @@ SYSROOT_ARCH="$SYSROOT_ARCH" node -e '(async () => { const { getVSCodeSysroot } if [ "$npm_config_arch" == "x64" ]; then # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/118.0.5993.159/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + curl -s https://raw.githubusercontent.com/chromium/chromium/120.0.6099.268/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux # Download libcxx headers and objects from upstream electron releases DEBUG=libcxx-fetcher \ @@ -27,9 +27,9 @@ if [ "$npm_config_arch" == "x64" ]; then # Set compiler toolchain # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:build/config/c++/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:build/config/c++/BUILD.gn export CC="$PWD/.build/CR_Clang/bin/clang --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr --sysroot=$VSCODE_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index a774dffc830..35feee9b876 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -032e54843700736bf3566518ff88717b2dc70be41bdc43840993fcb4cd9c82e8 *chromedriver-v27.3.2-darwin-arm64.zip -7d693267bacc510b724b97db23e21e22983e9f500605a132ab519303ec2e4d94 *chromedriver-v27.3.2-darwin-x64.zip -5f3f417986667e4c82c492b30c14892b0fef3a6dcf07860e74f7d7ba29f0ca41 *chromedriver-v27.3.2-linux-arm64.zip -84364d9c1fc53ce6f29e41d08d12351a2a4a208646acf02551c6f9aa6029c163 *chromedriver-v27.3.2-linux-armv7l.zip -7d3965a5ca3217e16739153d2817fc292e7cb16f55034fde76f26bdc916e60d1 *chromedriver-v27.3.2-linux-x64.zip -068adc1ea9e1d21dcfef1468b2b789714c93465c1874dbd3bf2872a695a1279f *chromedriver-v27.3.2-mas-arm64.zip -0d4d4bb8971260cbc0058cab2a7972e556b83a19d6ea062ea226e7a8555bc369 *chromedriver-v27.3.2-mas-x64.zip -83ffc61b6b524ee0caa0e5cd02dcd00adcd166ba1e03e7fc50206a299a6fca11 *chromedriver-v27.3.2-win32-arm64.zip -df4e9f20681b3e7b65c41dd1df3aa8cb9bc0a061a24ddcffbe44a9191aa01e0c *chromedriver-v27.3.2-win32-ia32.zip -1ef67b7c06061e691176df5e3463f4d5f5f258946dac24ae62e3cc250b8b95d1 *chromedriver-v27.3.2-win32-x64.zip -f3c52d205572da71a23f436b4708dc89c721a74f0e0c5c51093e3e331b1dff67 *electron-api.json -1489dca88c89f6fef05bdc2c08b9623bb46eb8d0f43020985776daef08642061 *electron-v27.3.2-darwin-arm64-dsym-snapshot.zip -7ee895e81d695c1ed65378ff4514d4fc9c4015a1c3c67691765f92c08c8e0855 *electron-v27.3.2-darwin-arm64-dsym.zip -cbc1c9973b2a895aa2ebecdbd92b3fe8964590b12141a658a6d03ed97339fae6 *electron-v27.3.2-darwin-arm64-symbols.zip -0d4efeff14ac16744eef3d461b95fb59abd2c3affbf638af169698135db73e1f *electron-v27.3.2-darwin-arm64.zip -a77b52509213e67ae1e24172256479831ecbff55d1f49dc0e8bfd4818a5f393e *electron-v27.3.2-darwin-x64-dsym-snapshot.zip -9006386321c50aa7e0e02cd9bd9daef4b8c3ec0e9735912524802f31d02399ef *electron-v27.3.2-darwin-x64-dsym.zip -14fa8e76e519e1fb9e166e134d03f3df1ae1951c14dfd76db8a033a9627c0f13 *electron-v27.3.2-darwin-x64-symbols.zip -5105acce7d832a606fd11b0551d1ef00e0c49fc8b4cff4b53712c9efdddc27a2 *electron-v27.3.2-darwin-x64.zip -3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-arm64-debug.zip -0d5d97a93938fa62d2659e2053dcc8d1cabc967878992b248bfec4dcc7763b8c *electron-v27.3.2-linux-arm64-symbols.zip -db9320d9ec6309145347fbba369ab7634139e80f15fff452be9b0171b2bd1823 *electron-v27.3.2-linux-arm64.zip -3bc20fb4f1d5effb2d882e7b587a337f910026aa50c22e7bc92522daa13f389c *electron-v27.3.2-linux-armv7l-debug.zip -6b9117419568c72542ab671301df05d46a662deab0bc37787b3dc9a907e68f8c *electron-v27.3.2-linux-armv7l-symbols.zip -72fd10c666dd810e9f961c2727ae44f5f6cf964cedb6860c1f09da7152e29a29 *electron-v27.3.2-linux-armv7l.zip -354209d48be01785d286eb80d691cdff476479db2d8cdbc6b6bd30652f5539fa *electron-v27.3.2-linux-x64-debug.zip -5f45a4b42f3b35ecea8a623338a6add35bb5220cb0ed02e3489b6d77fbe102ef *electron-v27.3.2-linux-x64-symbols.zip -2261aa0a5a293cf963487c050e9f6d05124da1f946f99bd1115f616f8730f286 *electron-v27.3.2-linux-x64.zip -54a4ad6e75e5a0001c32de18dbfec17f5edc17693663078076456ded525d65da *electron-v27.3.2-mas-arm64-dsym-snapshot.zip -5a5c85833ad7a6ef04337ed8acd131e5cf383a49638789dfd84e07c855b33ccc *electron-v27.3.2-mas-arm64-dsym.zip -16da4cc5a19a953c839093698f0532854e4d3fc839496a5c2b2405fd63c707f4 *electron-v27.3.2-mas-arm64-symbols.zip -8455b79826fe195124bee3f0661e08c14ca50858d376b09d03c79aace0082ea5 *electron-v27.3.2-mas-arm64.zip -00731db08a1bb66e51af0d26d03f8510221f4f6f92282c7baa0cd1c130e0cce6 *electron-v27.3.2-mas-x64-dsym-snapshot.zip -446f98f2d957e4ae487a6307b18be7b11edff35187b71143def4d00325943e42 *electron-v27.3.2-mas-x64-dsym.zip -d3455394eff02d463fdf89aabeee9c05d4980207ecf75a5eac27b35fb2aef874 *electron-v27.3.2-mas-x64-symbols.zip -dae434f52ff9b1055703aaf74b17ff3d93351646e9271a3b10e14b49969d4218 *electron-v27.3.2-mas-x64.zip -a598fcd1e20dcef9e7dccf7676ba276cd95ec7ff6799834fd090800fb15a6507 *electron-v27.3.2-win32-arm64-pdb.zip -7ba64940321ddff307910cc49077aa36c430d4b0797097975cb797cc0ab2b39d *electron-v27.3.2-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-arm64-toolchain-profile.zip -692f264e9d13478ad9a42d06e2eead0ed67ab1b52fc3693ba536a6a441fd9010 *electron-v27.3.2-win32-arm64.zip -a74eee739ff26681f6696f7959ab8e8603bb57f8fcb7ddab305220f71d2c69f3 *electron-v27.3.2-win32-ia32-pdb.zip -c10b90b51d0292129dc5bba5e012c7e07c78d6c70b0980c36676d6abf8eef12f *electron-v27.3.2-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-ia32-toolchain-profile.zip -63e477332608d31afb965a4054b5d78165df1da65d57477ac1dbddf8ede0f1b9 *electron-v27.3.2-win32-ia32.zip -3d795150c0afd48f585c7d32685f726618825262cb76f4014567be9e3de88732 *electron-v27.3.2-win32-x64-pdb.zip -d5463f797d1eb9a57ac9b20caa6419c15c5f3b378a3cb2b45d338040d7124411 *electron-v27.3.2-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.2-win32-x64-toolchain-profile.zip -e701b3023d4929f86736ae8a7ff6409134455da99b3fbdcea8d58555acbd9d46 *electron-v27.3.2-win32-x64.zip -3383cd44951cf763ddd36ba3ec91c930c9e8d33a175adfcb6dce4f667d60bc34 *electron.d.ts -db6df7bd0264c859009247276b35eda4ef20f22a7b2f41c2335a4609f5653cb7 *ffmpeg-v27.3.2-darwin-arm64.zip -3c0bb9740d6b95ff476ff7a5d4b442ccef7ec98e0fa3f2bad8d0e6a51329b511 *ffmpeg-v27.3.2-darwin-x64.zip -6fea38ce22bae4d23fb6b143e946c1c3d214ccecabf841883a2cb1b621161113 *ffmpeg-v27.3.2-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.2-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.2-linux-x64.zip -c75f62fc08d6c5e49fd1a805ca00b4191d5f04d26469448e3d4af48fb409b3a7 *ffmpeg-v27.3.2-mas-arm64.zip -acb8154c113ecbafb91aef5a294dc2c2bce61cbc4a261696681b723d292a5cb3 *ffmpeg-v27.3.2-mas-x64.zip -1665bdac6aa7264a6eb5f00a93110b718c7231010389bdda5ec7bf8275aab953 *ffmpeg-v27.3.2-win32-arm64.zip -3972d89c60a77f7955d7e8520adeae0c9f449a5ae3730cacf202f2baf2bae079 *ffmpeg-v27.3.2-win32-ia32.zip -37d2da723c2f2148c1c8f2ccf354b6dd933148c49dfc7f32aa57ecbd7063ffaf *ffmpeg-v27.3.2-win32-x64.zip -8828099c931c56981865fb9ff6fca85012dd05702a125858d6377c793760db1f *hunspell_dictionaries.zip -9e2126db472f66d3dde2d77eec63364e7071358f5591fc3c4dfb53d191ab5da8 *libcxx-objects-v27.3.2-linux-arm64.zip -530c3a92c4cd721e49e62d4fd97090c4e4d1b00c3ba821fd4f42c5f9186c98e7 *libcxx-objects-v27.3.2-linux-armv7l.zip -5b67f5e2a268bd1980a13b794013d4ac96e7ee40c4878d96f7c27da2c3f94923 *libcxx-objects-v27.3.2-linux-x64.zip -0d3086ccf9a050a88251a4382349f436f99d3d2b1842d87d854ea80667f6c423 *libcxx_headers.zip -ac02262548cb396051c683ad35fcbbed61b9a6f935c2a2bd3d568b209ce9e5a4 *libcxxabi_headers.zip -ba3b63a297b8be954a0ca1b8b83c3c856abaae85d17e6337d2b34e1c14f0d4b2 *mksnapshot-v27.3.2-darwin-arm64.zip -cb09a9e9e1fee567bf9e697eef30d143bd30627c0b189d0271cf84a72a03042e *mksnapshot-v27.3.2-darwin-x64.zip -014c5b621bbbc497bdc40dac47fac20143013fa1e905c0570b5cf92a51826354 *mksnapshot-v27.3.2-linux-arm64-x64.zip -f71407b9cc5c727de243a9e9e7fb56d2a0880e02187fa79982478853432ed5b7 *mksnapshot-v27.3.2-linux-armv7l-x64.zip -e5caa81f467d071756a4209f05f360055be7625a71a0dd9b2a8c95296c8415b5 *mksnapshot-v27.3.2-linux-x64.zip -fc33ec02a17fb58d48625c7b68517705dcd95b5a12e731d0072711a084dc65bd *mksnapshot-v27.3.2-mas-arm64.zip -961af5fc0ef80243d0e94036fb31b90f7e8458e392dd0e49613c11be89cb723f *mksnapshot-v27.3.2-mas-x64.zip -844a70ccef160921e0baeaefe9038d564db9a9476d98fab1eebb5c122ba9c22c *mksnapshot-v27.3.2-win32-arm64-x64.zip -3e723ca42794d43e16656599fbfec73880b964264f5057e38b865688c83ac905 *mksnapshot-v27.3.2-win32-ia32.zip -3e6fc056fa8cfb9940b26c4f066a9c9343056f053bcc53e1eada464bf5bc0d42 *mksnapshot-v27.3.2-win32-x64.zip +b1478a79a80e453e9ee39ad4a0b0e0d871f3e369ec519e3ac5a1da20bb7bdbf3 *chromedriver-v28.2.2-darwin-arm64.zip +4e7c4651d610c70de883b9ceef633f1a2bf90e0f3a732eae7a6d7bcad11bb2df *chromedriver-v28.2.2-darwin-x64.zip +7cee31da7d90c2a24338a10046386517bb93c69c79bd44cfcc9372a551fc7d01 *chromedriver-v28.2.2-linux-arm64.zip +2056e41f713d1a6c83d1f0260c0f2b8addc3c49887ae85ca7e92267eb53951e8 *chromedriver-v28.2.2-linux-armv7l.zip +19503257092605e21bd3798f5ffd0049d8420a504ececef7b1e95d3733846874 *chromedriver-v28.2.2-linux-x64.zip +ec09eeb8a7040c7402a8a5f54491b33e5dc95ea0535b55381a3ec405014f08db *chromedriver-v28.2.2-mas-arm64.zip +1dd5cb2a113c74ae84f2ac98f6f40da2c367014381a547788fea9ae220e6fc9f *chromedriver-v28.2.2-mas-x64.zip +fd505e1f1c2f72266c48914690a48918fc7920877215a508ea5325cf0353f72c *chromedriver-v28.2.2-win32-arm64.zip +c0226c0fb260d6812185eeea718c8c0054d0fcac995bb1ccb333f852206372c8 *chromedriver-v28.2.2-win32-ia32.zip +b6daccad5bcd3046d0678c927f6b97ed91f2242f716deb0de95a0ee2303af818 *chromedriver-v28.2.2-win32-x64.zip +76a88da92b950c882d90c3dcb26e0c2ca5e5a52ad7a066ec0b3cbf9cc4d04563 *electron-api.json +6ad08d733c95de3c30560e8289d0e657ed5ee03bc8ba9d1f11d528851e5b7fba *electron-v28.2.2-darwin-arm64-dsym-snapshot.zip +8f0d450f3d2392cbe7a6cb274ec0f3bf63da66c98fa0baaa2355e69f1c93b151 *electron-v28.2.2-darwin-arm64-dsym.zip +262036eb86b767db0d199df022b8b432aa3714e451b9ac656af7ef031581b44a *electron-v28.2.2-darwin-arm64-symbols.zip +23119b333c47a5ea9e36e04cdc3b8c5955cfccfeb90994f1fecea4722bfb8dcc *electron-v28.2.2-darwin-arm64.zip +384015a3e49a6846ebefc78f9f01ce6d47c2ec109e6223907298aa6382b0d072 *electron-v28.2.2-darwin-x64-dsym-snapshot.zip +434838821de746ff71baafdf9e0df07cb3766dd73eb7fcd253aee0571bd0cd59 *electron-v28.2.2-darwin-x64-dsym.zip +470087b5d631dc0032611048d5fc23faed9a71ec2c36a528c5a50c2e357d1716 *electron-v28.2.2-darwin-x64-symbols.zip +48f3424b3cbdf602a13f451361ade2f7f2896a354a51f78da4239dbdf2d1218b *electron-v28.2.2-darwin-x64.zip +d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-arm64-debug.zip +90550f29b1f032ebcf467dc81f4915c322f93855a4658cf74261f68a3ccdc21e *electron-v28.2.2-linux-arm64-symbols.zip +746284eb1d8029b0f6b02281543ab2ecf45f071da21407f45b2b32d1ff268310 *electron-v28.2.2-linux-arm64.zip +d5bf835ba4b2eaa4435946f97ad7ac3e7243564037423cfaadaf5cb03af4ddbc *electron-v28.2.2-linux-armv7l-debug.zip +80cc8d9333156caaee59c7ddf3bd77712be8379b51f4218063e6c176a4ec2c26 *electron-v28.2.2-linux-armv7l-symbols.zip +f4580e8877481c0526110feaa78372ed3045bfbf5a6ba4b14e8cd155b9965f5e *electron-v28.2.2-linux-armv7l.zip +da33d92871768e4cf95b143c6022830d97b0ec2d4120463ab71b48597f940f07 *electron-v28.2.2-linux-x64-debug.zip +18dce5283513abd94b79a1636d25e3453f5c33d335425a234b9967dd4e5ce942 *electron-v28.2.2-linux-x64-symbols.zip +1eeb6ebc3b0699cae1fb171bbf7c9105e716db833f6e73a90f4ca161f17ffb15 *electron-v28.2.2-linux-x64.zip +e74da15d90e52cddf0f0f14663f6313df585b486b002966f6016c1b148cdd70d *electron-v28.2.2-mas-arm64-dsym-snapshot.zip +498357eb2e784bff54c5ac59fd3eada814d130f12a5e77d47c468f2305377717 *electron-v28.2.2-mas-arm64-dsym.zip +849fa891d072d06b1e929eb1acfbe7ac83f0238483039f8e1102e01e5223c3f5 *electron-v28.2.2-mas-arm64-symbols.zip +621fd91d70cb33ec58543fc57762e692dfa0e272a53f3316fd215ffa88bd075b *electron-v28.2.2-mas-arm64.zip +0f9e2ab79bca99f44c1e9a140929fad6d2cd37def60303974f5a82ca95dd9a69 *electron-v28.2.2-mas-x64-dsym-snapshot.zip +51c29e047ba7d8669030cc9615f70ecaa5c9519cd04ab5e62822c0d4f21f5fbb *electron-v28.2.2-mas-x64-dsym.zip +25da93f45b095a3669475416832647a01f2a02a95dcc064dfabdf9c621045106 *electron-v28.2.2-mas-x64-symbols.zip +3b6931362f1b7f377624ea7c6ccf069f291e4e675a28f12a56e3e75355c13fbd *electron-v28.2.2-mas-x64.zip +cece93c232c65bf4e1b918b9645f5a2e247bd3f8bb2dd9e6e889a402060a103b *electron-v28.2.2-win32-arm64-pdb.zip +4a46e1ead0de7b6f757c1194add6467b3375a8dcfb02d903e481c0d8db5c7e5d *electron-v28.2.2-win32-arm64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-arm64-toolchain-profile.zip +083f95abbce97cab70e77b86e39cff01ff1df121f36b9da581ead960ae329f69 *electron-v28.2.2-win32-arm64.zip +f9b4633bc03fe7c77db4b335121e7e3e05f6788c6752ccb3f68267e664d4323a *electron-v28.2.2-win32-ia32-pdb.zip +d20f70ea8dc86477f723d104d42fe78e2508577ef3b1eb6ec812366f18ad80d8 *electron-v28.2.2-win32-ia32-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-ia32-toolchain-profile.zip +c57691b73592632829cef136be6dd356a82331450920fd024ac3589654d23550 *electron-v28.2.2-win32-ia32.zip +b8a14fc75b9205a4b82aa008e23a020e9fac694399b47390a163c3100ac7946d *electron-v28.2.2-win32-x64-pdb.zip +780089dde95ce1ab5da176ad53d9c7cd122085809622852a3132b80b93faac9b *electron-v28.2.2-win32-x64-symbols.zip +c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v28.2.2-win32-x64-toolchain-profile.zip +5fbc76585891b0d7b09c938f7be25b7ab36b3768491021b053dc99bc70a8aa29 *electron-v28.2.2-win32-x64.zip +225268475350fa71d9fdea966160fc91379ced2f2353a902addf65d5f9b0dbf1 *electron.d.ts +59a8d6b81d93bc99ecf099fac6492eb67ba601386cce07261a009a5b99e75479 *ffmpeg-v28.2.2-darwin-arm64.zip +15386f238dce9ba40714336265422cc41a1ef0608041f562a8fd42e3813ddc64 *ffmpeg-v28.2.2-darwin-x64.zip +8e108e533811febcc51f377ac8604d506663453e41c02dc818517e1ea9a4e8d5 *ffmpeg-v28.2.2-linux-arm64.zip +51ecd03435f56a2ced31b1c9dbf281955ba82a814ca0214a4292bdc711e5a45c *ffmpeg-v28.2.2-linux-armv7l.zip +acc9dc3765f68b7563045e2d0df11bbef6b41be0a1c34bbf9fa778f36eefb42f *ffmpeg-v28.2.2-linux-x64.zip +e71aac5c02f67bd5ba5d650160ff4edb122f697ab6bd8e686eae78426c439733 *ffmpeg-v28.2.2-mas-arm64.zip +3d0bb26cc9b751dad883750972fddec72aa936ecaa0d9bd198ba9b47203410e8 *ffmpeg-v28.2.2-mas-x64.zip +035b24a44f09587092e7db4e28400139901cec6378b3c828ce9f90a60f4f3a9a *ffmpeg-v28.2.2-win32-arm64.zip +38b25e225fd028f1f3f2c551f3b42d62d9e5c4ef388e0b0e019e9c8d93a85b07 *ffmpeg-v28.2.2-win32-ia32.zip +41849e779371dc0c35899341ae658b883ef0124296787ad96b7d5e4d9b69f1b9 *ffmpeg-v28.2.2-win32-x64.zip +8830364f8050164b1736246c30e96ae7ac876bcec5af1bf6344edbd66ed45353 *hunspell_dictionaries.zip +fc417873289fa9c947598ed73a27a28c4b5a07ce90ef998bb56550c4e10a034b *libcxx-objects-v28.2.2-linux-arm64.zip +06e9cdb2e8785a0835f66d34e9518c47ef220e32646e5b43e599339836e9e7b1 *libcxx-objects-v28.2.2-linux-armv7l.zip +ac098a006a8f84d0bb19088b2dec3ee3068b19208c5611194e831b1e5878fb2d *libcxx-objects-v28.2.2-linux-x64.zip +56414a1e809874949c1a1111b8e68b8d4f40d55cb481ad4869e920e47fe1b71b *libcxx_headers.zip +36e46cbed397cc1fe34d8dc477d3a87613acb9936f811535c1300e138e1a7008 *libcxxabi_headers.zip +94b01f4dd6bd56dec39a0be9ac14bb8c9a73db22cb579d6093f4f4c95a4a8896 *mksnapshot-v28.2.2-darwin-arm64.zip +ea768087b4fedf09c38eb093beb744c1a3b5b2a54025a83f1e2301ea03539500 *mksnapshot-v28.2.2-darwin-x64.zip +b9a01ba90abb69877838515d8273532e4aeea6d66c49b8aac3267e26546fc8b3 *mksnapshot-v28.2.2-linux-arm64-x64.zip +60005160b5e9db4a3847c63893f44e18ca86657a3ec97b6c13a90e43291bdb65 *mksnapshot-v28.2.2-linux-armv7l-x64.zip +81bf5ec59e7c33c642b79582fc5b775ec635ce0c52f3f5c30315cb45fdbffd12 *mksnapshot-v28.2.2-linux-x64.zip +7bfbe3cf02713b1a09aa19b75b876e158ed167b0d4345ec3b429061b53fc4b8f *mksnapshot-v28.2.2-mas-arm64.zip +91f7d34a05fa9c7cda4f36a44309f51e7defea2134d5bcc818a3eb4537979870 *mksnapshot-v28.2.2-mas-x64.zip +3f7163a34aae864cd44ebec086d4fab30132924680f20136cf19348811bace50 *mksnapshot-v28.2.2-win32-arm64-x64.zip +ac64fbfb78a1f6f389dac96ad7c655e2ea6fb2289e38a8fd516dbbda6bea42a3 *mksnapshot-v28.2.2-win32-ia32.zip +1bcd03747ce3eee6dd94b0608a0812268dacf77bac5541c581c22b92f700b303 *mksnapshot-v28.2.2-win32-x64.zip diff --git a/build/checksums/nodejs.txt b/build/checksums/nodejs.txt index 9ed8af5842a..13aa4c7e87b 100644 --- a/build/checksums/nodejs.txt +++ b/build/checksums/nodejs.txt @@ -1,6 +1,6 @@ -18ca716ea57522b90473777cb9f878467f77fdf826d37beb15a0889fdd74533e node-v18.17.1-darwin-arm64.tar.gz -b3e083d2715f07ec3f00438401fb58faa1e0bdf3c7bde9f38b75ed17809d92fa node-v18.17.1-darwin-x64.tar.gz -8f5203f5c6dc44ea50ac918b7ecbdb1c418e4f3d9376d8232a1ef9ff38f9c480 node-v18.17.1-linux-arm64.tar.gz -1ab79868859b2d37148c6d8ecee3abb5ee55b88731ab5df01928ed4f6f9bfbad node-v18.17.1-linux-armv7l.tar.gz -2cb75f2bc04b0a3498733fbee779b2f76fe3f655188b4ac69ef2887b6721da2d node-v18.17.1-linux-x64.tar.gz -afb45186ad4f4217c2fc1dfc2239ff5ab016ef0ba5fc329bc6aa8fd10c7ecc88 win-x64/node.exe +9f982cc91b28778dd8638e4f94563b0c2a1da7aba62beb72bd427721035ab553 node-v18.18.2-darwin-arm64.tar.gz +5bb8da908ed590e256a69bf2862238c8a67bc4600119f2f7721ca18a7c810c0f node-v18.18.2-darwin-x64.tar.gz +0c9a6502b66310cb26e12615b57304e91d92ac03d4adcb91c1906351d7928f0d node-v18.18.2-linux-arm64.tar.gz +7a3b34a6fdb9514bc2374114ec6df3c36113dc5075c38b22763aa8f106783737 node-v18.18.2-linux-armv7l.tar.gz +a44c3e7f8bf91e852c928e5d8bd67ca316b35e27eec1d8acbe3b9dbe03688dab node-v18.18.2-linux-x64.tar.gz +54884183ff5108874c091746465e8156ae0acc68af589cc10bc41b3927db0f4a win-x64/node.exe diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js index e40ed70901c..58db0f4af51 100644 --- a/build/linux/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -23,7 +23,7 @@ const product = require("../../product.json"); // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 12bc3c08a64..9f1a068b8d7 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -25,7 +25,7 @@ import product = require('../../product.json'); // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/118.0.5993.159:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/120.0.6099.268:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index b9a6e80d5f3..bd84fc146dc 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -81,7 +81,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', @@ -173,7 +172,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)', 'libnss3.so(NSS_3.12)', 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.13)', 'libnss3.so(NSS_3.2)', 'libnss3.so(NSS_3.22)', 'libnss3.so(NSS_3.22)(64bit)', @@ -269,7 +267,6 @@ exports.referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 275d88b95a8..82a4fe7698d 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -80,7 +80,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', @@ -172,7 +171,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)', 'libnss3.so(NSS_3.12)', 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.13)', 'libnss3.so(NSS_3.2)', 'libnss3.so(NSS_3.22)', 'libnss3.so(NSS_3.22)(64bit)', @@ -268,7 +266,6 @@ export const referenceGeneratedDepsByArch = { 'libnss3.so(NSS_3.11)(64bit)', 'libnss3.so(NSS_3.12)(64bit)', 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.13)(64bit)', 'libnss3.so(NSS_3.2)(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libnss3.so(NSS_3.3)(64bit)', diff --git a/cgmanifest.json b/cgmanifest.json index 2673931fdb6..4b4d49573e4 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "b1f5594cf472956192e71c38ebfc22472d44a03d" + "commitHash": "01303e423c41f9fefe7ff777744a4c549c0c6d8c" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "118.0.5993.159" + "version": "120.0.6099.276" }, { "component": { @@ -48,7 +48,7 @@ "git": { "name": "ffmpeg", "repositoryUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", - "commitHash": "0ba37733400593b162e5ae9ff26b384cff49c250" + "commitHash": "e1ca3f06adec15150a171bc38f550058b4bbb23b" } }, "isOnlyProductionDependency": true, @@ -516,11 +516,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "2e414d5d1082233c3516fca923fe351d5186c80e" + "commitHash": "8a01b3dcb7d08a48bfd3e6bf85ef49faa1454839" } }, "isOnlyProductionDependency": true, - "version": "18.17.1" + "version": "18.18.2" }, { "component": { @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "077c4addd5faa3ad1d1c9e598284368394a97fdd" + "commitHash": "16adf2a26358e3fc2297832e867c942b6df35844" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.3.2" + "version": "28.2.2" }, { "component": { diff --git a/package.json b/package.json index 1c3d3fc095f..2f127741d55 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.88.0", - "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", + "distro": "de07f23454d5352cc3711ca34d51278767e6eb0a", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.3.2", + "electron": "28.2.2", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/remote/.yarnrc b/remote/.yarnrc index cac528fd2dd..4e7208cdf69 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,5 +1,5 @@ disturl "https://nodejs.org/dist" -target "18.17.1" -ms_build_id "255375" +target "18.18.2" +ms_build_id "256117" runtime "node" build_from_source "true" diff --git a/yarn.lock b/yarn.lock index 7dcbe6fe030..98368474549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3556,10 +3556,10 @@ electron-to-chromium@^1.4.648: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== -electron@27.3.2: - version "27.3.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.2.tgz#ff2caa6d09e013ec32bae3185c790d712cd02f54" - integrity sha512-FoLdHj2ON0jE8S0YntgNT4ABaHgK4oR4dqXixPQXnTK0JvXgrrrW5W7ls+c7oiFBGN/f9bm0Mabq8iKH+7wMYQ== +electron@28.2.2: + version "28.2.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-28.2.2.tgz#d5aa4a33c00927d83ca893f8726f7c62aad98c41" + integrity sha512-8UcvIGFcjplHdjPFNAHVFg5bS0atDyT3Zx21WwuE4iLfxcAMsyMEOgrQX3im5LibA8srwsUZs7Cx0JAUfcQRpw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From 51772af23ced693d721de52b0b3ed90ed0635bc6 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 26 Feb 2024 11:41:18 +0100 Subject: [PATCH 124/753] rename suggestions: increase approximate font width --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 1b3728c30c6..6cb9f8a8c5a 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -612,7 +612,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - const APPROXIMATE_CHAR_WIDTH = 7.2; // approximate # of pixes taken by a single character + const APPROXIMATE_CHAR_WIDTH = 8; // approximate # of pixes taken by a single character const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * APPROXIMATE_CHAR_WIDTH); // TODO@ulugbekna: use editor#typicalCharacterWidth or something const width = Math.max( this._minimumWidth, From aa8ec9703cab7b592e0adce2f4d2bc6927b84127 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 26 Feb 2024 12:32:17 +0100 Subject: [PATCH 125/753] Fix some bogus uses of new CancellationTokenSource().token (#205994) Part of #205966 --- src/vs/workbench/api/browser/mainThreadQuickDiff.ts | 4 ++-- src/vs/workbench/api/common/extHostTunnelService.ts | 4 ++-- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 6 +++--- src/vs/workbench/services/remote/common/tunnelModel.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts index 48f53faea39..d5312097ee9 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostContext, ExtHostQuickDiffShape, IDocumentFilterDto, MainContext, MainThreadQuickDiffShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -30,7 +30,7 @@ export class MainThreadQuickDiff implements MainThreadQuickDiffShape { selector, isSCM: false, getOriginalResource: async (uri: URI) => { - return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, new CancellationTokenSource().token)); + return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, CancellationToken.None)); } }; const disposable = this.quickDiffService.addQuickDiffProvider(provider); diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index ccf5700c83b..7200372acca 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; @@ -149,7 +149,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe throw new Error('A tunnel provider has already been registered. Only the first tunnel provider to be registered will be used.'); } this._forwardPortProvider = async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { - const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, new CancellationTokenSource().token); + const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, CancellationToken.None); return result ?? undefined; }; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 0eb478a3544..3b40a248a1d 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -767,7 +767,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { let command = element?.command; if (element && !command) { if ((element instanceof ResolvableTreeItem) && element.hasResolve) { - await element.resolve(new CancellationTokenSource().token); + await element.resolve(CancellationToken.None); command = element.command; } } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 8b0318487b6..35ba3ee8c5d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -44,7 +44,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, labelPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { isMacintosh } from 'vs/base/common/platform'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableMouseEvent, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { WorkbenchTable } from 'vs/platform/list/browser/listService'; @@ -1372,9 +1372,9 @@ export namespace OpenPortInPreviewAction { if (tunnel) { const remoteHost = tunnel.remoteHost.includes(':') ? `[${tunnel.remoteHost}]` : tunnel.remoteHost; const sourceUri = URI.parse(`http://${remoteHost}:${tunnel.remotePort}`); - const opener = await externalOpenerService.getOpener(tunnel.localUri, { sourceUri }, new CancellationTokenSource().token); + const opener = await externalOpenerService.getOpener(tunnel.localUri, { sourceUri }, CancellationToken.None); if (opener) { - return opener.openExternalUri(tunnel.localUri, { sourceUri }, new CancellationTokenSource().token); + return opener.openExternalUri(tunnel.localUri, { sourceUri }, CancellationToken.None); } return openerService.open(tunnel.localUri); } diff --git a/src/vs/workbench/services/remote/common/tunnelModel.ts b/src/vs/workbench/services/remote/common/tunnelModel.ts index 5394416d717..f0ceaf1950c 100644 --- a/src/vs/workbench/services/remote/common/tunnelModel.ts +++ b/src/vs/workbench/services/remote/common/tunnelModel.ts @@ -20,7 +20,7 @@ import { RemoteTunnel, ITunnelService, TunnelProtocol, TunnelPrivacyId, LOCALHOS import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { isNumber, isObject, isString } from 'vs/base/common/types'; import { deepClone } from 'vs/base/common/objects'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -993,7 +993,7 @@ export class TunnelModel extends Disposable { const portGroup = entry[1]; const matchingCandidate = matchingCandidates.get(portGroup[0]); return provider.providePortAttributes(portGroup, - matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token); + matchingCandidate?.pid, matchingCandidate?.detail, CancellationToken.None); }); }))); const providedAttributes: Map = new Map(); From 90ab3213c32e5dfc9eff78af7b7b4783f78d855b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:44:32 +0100 Subject: [PATCH 126/753] Move diff editor internal commands to their own file --- .../browser/widget/diffEditor/commands.ts | 242 ++++++++++++++++++ .../diffEditor/diffEditor.contribution.ts | 240 +---------------- 2 files changed, 245 insertions(+), 237 deletions(-) create mode 100644 src/vs/editor/browser/widget/diffEditor/commands.ts diff --git a/src/vs/editor/browser/widget/diffEditor/commands.ts b/src/vs/editor/browser/widget/diffEditor/commands.ts new file mode 100644 index 00000000000..5297e77c086 --- /dev/null +++ b/src/vs/editor/browser/widget/diffEditor/commands.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getActiveElement } from 'vs/base/browser/dom'; +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { localize2 } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import './registrations.contribution'; + +export class ToggleCollapseUnchangedRegions extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleCollapseUnchangedRegions', + title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'), + icon: Codicon.map, + toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + menu: { + when: ContextKeyExpr.has('isInDiffEditor'), + id: MenuId.EditorTitle, + order: 22, + group: 'navigation', + }, + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); + configurationService.updateValue('diffEditor.hideUnchangedRegions.enabled', newValue); + } +} + +export class ToggleShowMovedCodeBlocks extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleShowMovedCodeBlocks', + title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.experimental.showMoves'); + configurationService.updateValue('diffEditor.experimental.showMoves', newValue); + } +} + +export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { + constructor() { + super({ + id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', + title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'), + precondition: ContextKeyExpr.has('isInDiffEditor'), + }); + } + + run(accessor: ServicesAccessor, ...args: unknown[]): void { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); + configurationService.updateValue('diffEditor.useInlineViewWhenSpaceIsLimited', newValue); + } +} + +const diffEditorCategory: ILocalizedString = localize2('diffEditor', "Diff Editor"); + +export class SwitchSide extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.switchSide', + title: localize2('switchSide', 'Switch Side'), + icon: Codicon.arrowSwap, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg?: { dryRun: boolean }): unknown { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + if (arg && arg.dryRun) { + return { destinationSelection: diffEditor.mapToOtherSide().destinationSelection }; + } else { + diffEditor.switchSide(); + } + } + return undefined; + } +} +export class ExitCompareMove extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.exitCompareMove', + title: localize2('exitCompareMove', 'Exit Compare Move'), + icon: Codicon.close, + precondition: EditorContextKeys.comparingMovedCode, + f1: false, + category: diffEditorCategory, + keybinding: { + weight: 10000, + primary: KeyCode.Escape, + } + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.exitCompareMove(); + } + } +} + +export class CollapseAllUnchangedRegions extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.collapseAllUnchangedRegions', + title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'), + icon: Codicon.fold, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.collapseAllUnchangedRegions(); + } + } +} + +export class ShowAllUnchangedRegions extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.showAllUnchangedRegions', + title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'), + icon: Codicon.unfold, + precondition: ContextKeyExpr.has('isInDiffEditor'), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget) { + diffEditor.showAllUnchangedRegions(); + } + } +} + +const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer"); + +export class AccessibleDiffViewerNext extends Action2 { + public static id = 'editor.action.accessibleDiffViewer.next'; + + constructor() { + super({ + id: AccessibleDiffViewerNext.id, + title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'), + category: accessibleDiffViewerCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), + keybinding: { + primary: KeyCode.F7, + weight: KeybindingWeight.EditorContrib + }, + f1: true, + }); + } + + public override run(accessor: ServicesAccessor): void { + const diffEditor = findFocusedDiffEditor(accessor); + diffEditor?.accessibleDiffViewerNext(); + } +} + +export class AccessibleDiffViewerPrev extends Action2 { + public static id = 'editor.action.accessibleDiffViewer.prev'; + + constructor() { + super({ + id: AccessibleDiffViewerPrev.id, + title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'), + category: accessibleDiffViewerCategory, + precondition: ContextKeyExpr.has('isInDiffEditor'), + keybinding: { + primary: KeyMod.Shift | KeyCode.F7, + weight: KeybindingWeight.EditorContrib + }, + f1: true, + }); + } + + public override run(accessor: ServicesAccessor): void { + const diffEditor = findFocusedDiffEditor(accessor); + diffEditor?.accessibleDiffViewerPrev(); + } +} + +export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { + const codeEditorService = accessor.get(ICodeEditorService); + const diffEditors = codeEditorService.listDiffEditors(); + + const activeElement = getActiveElement(); + if (activeElement) { + for (const d of diffEditors) { + const container = d.getContainerDomNode(); + if (isElementOrParentOf(container, activeElement)) { + return d; + } + } + } + + return null; +} + +function isElementOrParentOf(elementOrParent: Element, element: Element): boolean { + let e: Element | null = element; + while (e) { + if (e === elementOrParent) { + return true; + } + e = e.parentElement; + } + return false; +} diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 2cc7ffacdc4..bb8ab9ccca7 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -3,83 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getActiveElement } from 'vs/base/browser/dom'; import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev, CollapseAllUnchangedRegions, ExitCompareMove, ShowAllUnchangedRegions, SwitchSide, ToggleCollapseUnchangedRegions, ToggleShowMovedCodeBlocks, ToggleUseInlineViewWhenSpaceIsLimited } from 'vs/editor/browser/widget/diffEditor/commands'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { localize, localize2 } from 'vs/nls'; -import { ILocalizedString } from 'vs/platform/action/common/action'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import './registrations.contribution'; -export class ToggleCollapseUnchangedRegions extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleCollapseUnchangedRegions', - title: localize2('toggleCollapseUnchangedRegions', 'Toggle Collapse Unchanged Regions'), - icon: Codicon.map, - toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - menu: { - when: ContextKeyExpr.has('isInDiffEditor'), - id: MenuId.EditorTitle, - order: 22, - group: 'navigation', - }, - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.hideUnchangedRegions.enabled'); - configurationService.updateValue('diffEditor.hideUnchangedRegions.enabled', newValue); - } -} - registerAction2(ToggleCollapseUnchangedRegions); - -export class ToggleShowMovedCodeBlocks extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleShowMovedCodeBlocks', - title: localize2('toggleShowMovedCodeBlocks', 'Toggle Show Moved Code Blocks'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.experimental.showMoves'); - configurationService.updateValue('diffEditor.experimental.showMoves', newValue); - } -} - registerAction2(ToggleShowMovedCodeBlocks); - -export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { - constructor() { - super({ - id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', - title: localize2('toggleUseInlineViewWhenSpaceIsLimited', 'Toggle Use Inline View When Space Is Limited'), - precondition: ContextKeyExpr.has('isInDiffEditor'), - }); - } - - run(accessor: ServicesAccessor, ...args: unknown[]): void { - const configurationService = accessor.get(IConfigurationService); - const newValue = !configurationService.getValue('diffEditor.useInlineViewWhenSpaceIsLimited'); - configurationService.updateValue('diffEditor.useInlineViewWhenSpaceIsLimited', newValue); - } -} - registerAction2(ToggleUseInlineViewWhenSpaceIsLimited); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -110,130 +44,12 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { when: ContextKeyExpr.has('isInDiffEditor'), }); -const diffEditorCategory: ILocalizedString = localize2('diffEditor', "Diff Editor"); - -export class SwitchSide extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.switchSide', - title: localize2('switchSide', 'Switch Side'), - icon: Codicon.arrowSwap, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg?: { dryRun: boolean }): unknown { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - if (arg && arg.dryRun) { - return { destinationSelection: diffEditor.mapToOtherSide().destinationSelection }; - } else { - diffEditor.switchSide(); - } - } - return undefined; - } -} registerAction2(SwitchSide); - -export class ExitCompareMove extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.exitCompareMove', - title: localize2('exitCompareMove', 'Exit Compare Move'), - icon: Codicon.close, - precondition: EditorContextKeys.comparingMovedCode, - f1: false, - category: diffEditorCategory, - keybinding: { - weight: 10000, - primary: KeyCode.Escape, - } - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.exitCompareMove(); - } - } -} - registerAction2(ExitCompareMove); - -export class CollapseAllUnchangedRegions extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.collapseAllUnchangedRegions', - title: localize2('collapseAllUnchangedRegions', 'Collapse All Unchanged Regions'), - icon: Codicon.fold, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.collapseAllUnchangedRegions(); - } - } -} - registerAction2(CollapseAllUnchangedRegions); - -export class ShowAllUnchangedRegions extends EditorAction2 { - constructor() { - super({ - id: 'diffEditor.showAllUnchangedRegions', - title: localize2('showAllUnchangedRegions', 'Show All Unchanged Regions'), - icon: Codicon.unfold, - precondition: ContextKeyExpr.has('isInDiffEditor'), - f1: true, - category: diffEditorCategory, - }); - } - - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { - const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor instanceof DiffEditorWidget) { - diffEditor.showAllUnchangedRegions(); - } - } -} - registerAction2(ShowAllUnchangedRegions); -const accessibleDiffViewerCategory: ILocalizedString = localize2('accessibleDiffViewer', "Accessible Diff Viewer"); - -export class AccessibleDiffViewerNext extends Action2 { - public static id = 'editor.action.accessibleDiffViewer.next'; - - constructor() { - super({ - id: AccessibleDiffViewerNext.id, - title: localize2('editor.action.accessibleDiffViewer.next', 'Go to Next Difference'), - category: accessibleDiffViewerCategory, - precondition: ContextKeyExpr.has('isInDiffEditor'), - keybinding: { - primary: KeyCode.F7, - weight: KeybindingWeight.EditorContrib - }, - f1: true, - }); - } - - public override run(accessor: ServicesAccessor): void { - const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.accessibleDiffViewerNext(); - } -} - MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: AccessibleDiffViewerNext.id, @@ -248,56 +64,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { ), }); -export class AccessibleDiffViewerPrev extends Action2 { - public static id = 'editor.action.accessibleDiffViewer.prev'; - - constructor() { - super({ - id: AccessibleDiffViewerPrev.id, - title: localize2('editor.action.accessibleDiffViewer.prev', 'Go to Previous Difference'), - category: accessibleDiffViewerCategory, - precondition: ContextKeyExpr.has('isInDiffEditor'), - keybinding: { - primary: KeyMod.Shift | KeyCode.F7, - weight: KeybindingWeight.EditorContrib - }, - f1: true, - }); - } - - public override run(accessor: ServicesAccessor): void { - const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.accessibleDiffViewerPrev(); - } -} - -export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { - const codeEditorService = accessor.get(ICodeEditorService); - const diffEditors = codeEditorService.listDiffEditors(); - - const activeElement = getActiveElement(); - if (activeElement) { - for (const d of diffEditors) { - const container = d.getContainerDomNode(); - if (isElementOrParentOf(container, activeElement)) { - return d; - } - } - } - - return null; -} - -function isElementOrParentOf(elementOrParent: Element, element: Element): boolean { - let e: Element | null = element; - while (e) { - if (e === elementOrParent) { - return true; - } - e = e.parentElement; - } - return false; -} CommandsRegistry.registerCommandAlias('editor.action.diffReview.next', AccessibleDiffViewerNext.id); registerAction2(AccessibleDiffViewerNext); From f63ffa61419924642f0d5722452f16eb14984fa9 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:57:23 +0100 Subject: [PATCH 127/753] Move diff editor commands into their own files --- .../parts/editor/diffEditorCommands.ts | 231 ++++++++++++++++++ .../parts/editor/editor.contribution.ts | 14 +- .../browser/parts/editor/editorCommands.ts | 221 +---------------- 3 files changed, 241 insertions(+), 225 deletions(-) create mode 100644 src/vs/workbench/browser/parts/editor/diffEditorCommands.ts diff --git a/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts new file mode 100644 index 00000000000..550ed81628f --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/diffEditorCommands.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { localize2, localize } from 'vs/nls'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; +import { TextCompareEditorVisibleContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide'; +export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange'; +export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange'; +export const DIFF_FOCUS_PRIMARY_SIDE = 'workbench.action.compareEditor.focusPrimarySide'; +export const DIFF_FOCUS_SECONDARY_SIDE = 'workbench.action.compareEditor.focusSecondarySide'; +export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherSide'; +export const DIFF_OPEN_SIDE = 'workbench.action.compareEditor.openSide'; +export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace'; +export const DIFF_SWAP_SIDES = 'workbench.action.compareEditor.swapSides'; + +export function registerDiffEditorCommands(): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: GOTO_NEXT_CHANGE, + weight: KeybindingWeight.WorkbenchContrib, + when: TextCompareEditorVisibleContext, + primary: KeyMod.Alt | KeyCode.F5, + handler: accessor => navigateInDiffEditor(accessor, true) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: GOTO_NEXT_CHANGE, + title: localize2('compare.nextChange', 'Go to Next Change'), + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: GOTO_PREVIOUS_CHANGE, + weight: KeybindingWeight.WorkbenchContrib, + when: TextCompareEditorVisibleContext, + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5, + handler: accessor => navigateInDiffEditor(accessor, false) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: GOTO_PREVIOUS_CHANGE, + title: localize2('compare.previousChange', 'Go to Previous Change'), + } + }); + + function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined { + const editorService = accessor.get(IEditorService); + + for (const editor of [editorService.activeEditorPane, ...editorService.visibleEditorPanes]) { + if (editor instanceof TextDiffEditor) { + return editor; + } + } + + return undefined; + } + + function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + activeTextDiffEditor.getControl()?.goToDiff(next ? 'next' : 'previous'); + } + } + + enum FocusTextDiffEditorMode { + Original, + Modified, + Toggle + } + + function focusInDiffEditor(accessor: ServicesAccessor, mode: FocusTextDiffEditorMode): void { + const activeTextDiffEditor = getActiveTextDiffEditor(accessor); + + if (activeTextDiffEditor) { + switch (mode) { + case FocusTextDiffEditorMode.Original: + activeTextDiffEditor.getControl()?.getOriginalEditor().focus(); + break; + case FocusTextDiffEditorMode.Modified: + activeTextDiffEditor.getControl()?.getModifiedEditor().focus(); + break; + case FocusTextDiffEditorMode.Toggle: + if (activeTextDiffEditor.getControl()?.getModifiedEditor().hasWidgetFocus()) { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original); + } else { + return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified); + } + } + } + } + + function toggleDiffSideBySide(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + + const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); + configurationService.updateValue('diffEditor.renderSideBySide', newValue); + } + + function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + + const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); + configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); + } + + async function swapDiffSides(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + + const diffEditor = getActiveTextDiffEditor(accessor); + const activeGroup = diffEditor?.group; + const diffInput = diffEditor?.input; + if (!diffEditor || typeof activeGroup === 'undefined' || !(diffInput instanceof DiffEditorInput) || !diffInput.modified.resource) { + return; + } + + const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id, preserveResource: true }); + if (!untypedDiffInput) { + return; + } + + // Since we are about to replace the diff editor, make + // sure to first open the modified side if it is not + // yet opened. This ensures that the swapping is not + // bringing up a confirmation dialog to save. + if (diffInput.modified.isModified() && editorService.findEditors({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId }).length === 0) { + await editorService.openEditor({ + ...untypedDiffInput.modified, + options: { + ...untypedDiffInput.modified.options, + pinned: true, + inactive: true + } + }, activeGroup); + } + + // Replace the input with the swapped variant + await editorService.replaceEditors([ + { + editor: diffInput, + replacement: { + ...untypedDiffInput, + original: untypedDiffInput.modified, + modified: untypedDiffInput.original, + options: { + ...untypedDiffInput.options, + pinned: true + } + } + } + ], activeGroup); + } + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TOGGLE_DIFF_SIDE_BY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => toggleDiffSideBySide(accessor) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_PRIMARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_SECONDARY_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_FOCUS_OTHER_SIDE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Toggle) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor) + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: DIFF_SWAP_SIDES, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: undefined, + handler: accessor => swapDiffSides(accessor) + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: TOGGLE_DIFF_SIDE_BY_SIDE, + title: localize2('toggleInlineView', "Toggle Inline View"), + category: localize('compare', "Compare") + }, + when: TextCompareEditorActiveContext + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: DIFF_SWAP_SIDES, + title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), + category: localize('compare', "Compare") + }, + when: TextCompareEditorActiveContext + }); +} diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index ca21ad13864..a60e8e59d62 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -47,12 +47,13 @@ import { } from 'vs/workbench/browser/parts/editor/editorActions'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, - CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, - SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, + CLOSE_PINNED_EDITOR_COMMAND_ID, CLOSE_SAVED_EDITORS_COMMAND_ID, KEEP_EDITOR_COMMAND_ID, PIN_EDITOR_COMMAND_ID, SHOW_EDITORS_IN_GROUP, SPLIT_EDITOR_DOWN, SPLIT_EDITOR_LEFT, + SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, TOGGLE_KEEP_EDITORS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID, setup as registerEditorCommands, REOPEN_WITH_COMMAND_ID, TOGGLE_LOCK_GROUP_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID, SPLIT_EDITOR_IN_GROUP, JOIN_EDITOR_IN_GROUP, FOCUS_FIRST_SIDE_EDITOR, FOCUS_SECOND_SIDE_EDITOR, TOGGLE_SPLIT_EDITOR_IN_GROUP_LAYOUT, LOCK_GROUP_COMMAND_ID, SPLIT_EDITOR, TOGGLE_MAXIMIZE_EDITOR_GROUP, MOVE_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_INTO_NEW_WINDOW_COMMAND_ID, MOVE_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, COPY_EDITOR_GROUP_INTO_NEW_WINDOW_COMMAND_ID, - NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID, DIFF_SWAP_SIDES + NEW_EMPTY_EDITOR_WINDOW_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { GOTO_NEXT_CHANGE, GOTO_PREVIOUS_CHANGE, TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, TOGGLE_DIFF_SIDE_BY_SIDE, DIFF_SWAP_SIDES } from './diffEditorCommands'; import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; @@ -570,11 +571,8 @@ appendEditorToolItem( CLOSE_ORDER - 1, // immediately to the left of close action ); -const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); -const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); -const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); - // Diff Editor Title Menu: Previous Change +const previousChangeIcon = registerIcon('diff-editor-previous-change', Codicon.arrowUp, localize('previousChangeIcon', 'Icon for the previous change action in the diff editor.')); appendEditorToolItem( { id: GOTO_PREVIOUS_CHANGE, @@ -588,6 +586,7 @@ appendEditorToolItem( ); // Diff Editor Title Menu: Next Change +const nextChangeIcon = registerIcon('diff-editor-next-change', Codicon.arrowDown, localize('nextChangeIcon', 'Icon for the next change action in the diff editor.')); appendEditorToolItem( { id: GOTO_NEXT_CHANGE, @@ -613,6 +612,7 @@ appendEditorToolItem( undefined ); +const toggleWhitespace = registerIcon('diff-editor-toggle-whitespace', Codicon.whitespace, localize('toggleWhitespace', 'Icon for the toggle whitespace action in the diff editor.')); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 68cd3a6b328..89795c71057 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -16,7 +16,7 @@ import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize, localize2 } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -30,7 +30,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; -import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext, TextCompareEditorVisibleContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorCanSplitInGroupContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupLockedContext, ActiveEditorStickyContext, MultipleEditorGroupsContext, SideBySideEditorActiveContext, TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'; import { CloseDirection, EditorInputCapabilities, EditorsOrder, IEditorCommandsContext, IEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IVisibleEditorPane, isEditorIdentifier, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -41,6 +41,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { DIFF_FOCUS_OTHER_SIDE, DIFF_FOCUS_PRIMARY_SIDE, DIFF_FOCUS_SECONDARY_SIDE, DIFF_OPEN_SIDE, registerDiffEditorCommands } from './diffEditorCommands'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -65,16 +66,6 @@ export const REOPEN_WITH_COMMAND_ID = 'workbench.action.reopenWithEditor'; export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor'; export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor'; -export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide'; -export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange'; -export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange'; -export const DIFF_FOCUS_PRIMARY_SIDE = 'workbench.action.compareEditor.focusPrimarySide'; -export const DIFF_FOCUS_SECONDARY_SIDE = 'workbench.action.compareEditor.focusSecondarySide'; -export const DIFF_FOCUS_OTHER_SIDE = 'workbench.action.compareEditor.focusOtherSide'; -export const DIFF_OPEN_SIDE = 'workbench.action.compareEditor.openSide'; -export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace'; -export const DIFF_SWAP_SIDES = 'workbench.action.compareEditor.swapSides'; - export const SPLIT_EDITOR = 'workbench.action.splitEditor'; export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp'; export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; @@ -373,212 +364,6 @@ function registerEditorGroupsLayoutCommands(): void { }); } -function registerDiffEditorCommands(): void { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: GOTO_NEXT_CHANGE, - weight: KeybindingWeight.WorkbenchContrib, - when: TextCompareEditorVisibleContext, - primary: KeyMod.Alt | KeyCode.F5, - handler: accessor => navigateInDiffEditor(accessor, true) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: GOTO_NEXT_CHANGE, - title: localize2('compare.nextChange', 'Go to Next Change'), - } - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: GOTO_PREVIOUS_CHANGE, - weight: KeybindingWeight.WorkbenchContrib, - when: TextCompareEditorVisibleContext, - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5, - handler: accessor => navigateInDiffEditor(accessor, false) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: GOTO_PREVIOUS_CHANGE, - title: localize2('compare.previousChange', 'Go to Previous Change'), - } - }); - - function getActiveTextDiffEditor(accessor: ServicesAccessor): TextDiffEditor | undefined { - const editorService = accessor.get(IEditorService); - - for (const editor of [editorService.activeEditorPane, ...editorService.visibleEditorPanes]) { - if (editor instanceof TextDiffEditor) { - return editor; - } - } - - return undefined; - } - - function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void { - const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - - if (activeTextDiffEditor) { - activeTextDiffEditor.getControl()?.goToDiff(next ? 'next' : 'previous'); - } - } - - enum FocusTextDiffEditorMode { - Original, - Modified, - Toggle - } - - function focusInDiffEditor(accessor: ServicesAccessor, mode: FocusTextDiffEditorMode): void { - const activeTextDiffEditor = getActiveTextDiffEditor(accessor); - - if (activeTextDiffEditor) { - switch (mode) { - case FocusTextDiffEditorMode.Original: - activeTextDiffEditor.getControl()?.getOriginalEditor().focus(); - break; - case FocusTextDiffEditorMode.Modified: - activeTextDiffEditor.getControl()?.getModifiedEditor().focus(); - break; - case FocusTextDiffEditorMode.Toggle: - if (activeTextDiffEditor.getControl()?.getModifiedEditor().hasWidgetFocus()) { - return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original); - } else { - return focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified); - } - } - } - } - - function toggleDiffSideBySide(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); - - const newValue = !configurationService.getValue('diffEditor.renderSideBySide'); - configurationService.updateValue('diffEditor.renderSideBySide', newValue); - } - - function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void { - const configurationService = accessor.get(IConfigurationService); - - const newValue = !configurationService.getValue('diffEditor.ignoreTrimWhitespace'); - configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue); - } - - async function swapDiffSides(accessor: ServicesAccessor): Promise { - const editorService = accessor.get(IEditorService); - - const diffEditor = getActiveTextDiffEditor(accessor); - const activeGroup = diffEditor?.group; - const diffInput = diffEditor?.input; - if (!diffEditor || typeof activeGroup === 'undefined' || !(diffInput instanceof DiffEditorInput) || !diffInput.modified.resource) { - return; - } - - const untypedDiffInput = diffInput.toUntyped({ preserveViewState: activeGroup.id, preserveResource: true }); - if (!untypedDiffInput) { - return; - } - - // Since we are about to replace the diff editor, make - // sure to first open the modified side if it is not - // yet opened. This ensures that the swapping is not - // bringing up a confirmation dialog to save. - if (diffInput.modified.isModified() && editorService.findEditors({ resource: diffInput.modified.resource, typeId: diffInput.modified.typeId, editorId: diffInput.modified.editorId }).length === 0) { - await editorService.openEditor({ - ...untypedDiffInput.modified, - options: { - ...untypedDiffInput.modified.options, - pinned: true, - inactive: true - } - }, activeGroup); - } - - // Replace the input with the swapped variant - await editorService.replaceEditors([ - { - editor: diffInput, - replacement: { - ...untypedDiffInput, - original: untypedDiffInput.modified, - modified: untypedDiffInput.original, - options: { - ...untypedDiffInput.options, - pinned: true - } - } - } - ], activeGroup); - } - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: TOGGLE_DIFF_SIDE_BY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => toggleDiffSideBySide(accessor) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_PRIMARY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Modified) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_SECONDARY_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Original) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_FOCUS_OTHER_SIDE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => focusInDiffEditor(accessor, FocusTextDiffEditorMode.Toggle) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor) - }); - - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: DIFF_SWAP_SIDES, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: undefined, - handler: accessor => swapDiffSides(accessor) - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: TOGGLE_DIFF_SIDE_BY_SIDE, - title: localize2('toggleInlineView', "Toggle Inline View"), - category: localize('compare', "Compare") - }, - when: TextCompareEditorActiveContext - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: DIFF_SWAP_SIDES, - title: localize2('swapDiffSides', "Swap Left and Right Editor Side"), - category: localize('compare', "Compare") - }, - when: TextCompareEditorActiveContext - }); -} - function registerOpenEditorAPICommands(): void { function mixinContext(context: IOpenEvent | undefined, options: ITextEditorOptions | undefined, column: EditorGroupColumn | undefined): [ITextEditorOptions | undefined, EditorGroupColumn | undefined] { From 6176d9642094a0140f4318659a683bb3baf0915c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 19:22:57 +0100 Subject: [PATCH 128/753] Fixes CI --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9fd67d69b88..92b430ff162 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -12,7 +12,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; +import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/commands'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index a19d88cca33..902b335ba4f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -8,7 +8,7 @@ import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; +import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/commands'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; From 9e5982f512e32739e6ffe92e8c9b123531430b5a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 26 Feb 2024 12:51:14 +0100 Subject: [PATCH 129/753] Some :lipstick: around the code (#206222) --- .../browser/parts/editor/editorStatus.ts | 43 +++++++++--------- src/vs/workbench/common/contributions.ts | 2 +- src/vs/workbench/common/editor.ts | 6 +-- .../common/editor/sideBySideEditorInput.ts | 6 +-- .../browser/multiDiffEditorInput.ts | 4 +- .../contrib/remote/browser/remoteIndicator.ts | 45 +++++++++++-------- .../editor/browser/editorResolverService.ts | 4 +- .../editor/common/editorResolverService.ts | 4 +- 8 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index e1ee2bc0d94..d34a58122cd 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -293,8 +293,6 @@ class TabFocusMode extends Disposable { const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true ? true : false; TabFocus.setTabFocusMode(tabFocusModeConfig); - - this._onDidChange.fire(tabFocusModeConfig); } private registerListeners(): void { @@ -328,14 +326,16 @@ class EditorStatus extends Disposable { private readonly eolElement = this._register(new MutableDisposable()); private readonly languageElement = this._register(new MutableDisposable()); private readonly metadataElement = this._register(new MutableDisposable()); - private readonly currentProblemStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); - private readonly state = new State(); - private readonly activeEditorListeners = this._register(new DisposableStore()); - private readonly delayedRender = this._register(new MutableDisposable()); + + private readonly currentMarkerStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); private readonly tabFocusMode = this.instantiationService.createInstance(TabFocusMode); + private readonly state = new State(); private toRender: StateChange | undefined = undefined; + private readonly activeEditorListeners = this._register(new DisposableStore()); + private readonly delayedRender = this._register(new MutableDisposable()); + constructor( private readonly targetWindowId: number, @IEditorService private readonly editorService: IEditorService, @@ -634,7 +634,7 @@ class EditorStatus extends Disposable { this.onEncodingChange(activeEditorPane, activeCodeEditor); this.onIndentationChange(activeCodeEditor); this.onMetadataChange(activeEditorPane); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); // Dispose old active editor listeners this.activeEditorListeners.clear(); @@ -662,7 +662,7 @@ class EditorStatus extends Disposable { // Hook Listener for Selection changes this.activeEditorListeners.add(Event.defer(activeCodeEditor.onDidChangeCursorPosition)(() => { this.onSelectionChange(activeCodeEditor); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); })); // Hook Listener for language changes @@ -673,7 +673,7 @@ class EditorStatus extends Disposable { // Hook Listener for content changes this.activeEditorListeners.add(Event.accumulate(activeCodeEditor.onDidChangeModelContent)(e => { this.onEOLChange(activeCodeEditor); - this.currentProblemStatus.update(activeCodeEditor); + this.currentMarkerStatus.update(activeCodeEditor); const selections = activeCodeEditor.getSelections(); if (selections) { @@ -918,13 +918,16 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); + this.statusBarEntryAccessor = this._register(new MutableDisposable()); + this._register(markerService.onMarkerChanged(changedResources => this.onMarkerChanged(changedResources))); this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('problems.showCurrentInStatus'))(() => this.updateStatus())); } update(editor: ICodeEditor | undefined): void { this.editor = editor; + this.updateMarkers(); this.updateStatus(); } @@ -1022,26 +1025,26 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { resource: model.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); - this.markers.sort(compareMarker); + this.markers.sort(this.compareMarker); } else { this.markers = []; } this.updateStatus(); } -} -function compareMarker(a: IMarker, b: IMarker): number { - let res = compare(a.resource.toString(), b.resource.toString()); - if (res === 0) { - res = MarkerSeverity.compare(a.severity, b.severity); - } + private compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = MarkerSeverity.compare(a.severity, b.severity); + } - if (res === 0) { - res = Range.compareRangesUsingStarts(a, b); - } + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } - return res; + return res; + } } export class ShowLanguageExtensionsAction extends Action { diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index b96df678196..aaf1452c25a 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -385,7 +385,7 @@ export class WorkbenchContributionsRegistry extends Disposable implements IWorkb } } - if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names (TODO@bpasero remove when adopted IDs) */) { + if (typeof contribution.id === 'string' || !environmentService.isBuilt /* only log out of sources where we have good ctor names */) { const time = Date.now() - now; if (time > (phase < LifecyclePhase.Restored ? WorkbenchContributionsRegistry.BLOCK_BEFORE_RESTORE_WARN_THRESHOLD : WorkbenchContributionsRegistry.BLOCK_AFTER_RESTORE_WARN_THRESHOLD)) { logService.warn(`Creation of workbench contribution '${contribution.id ?? contribution.ctor.name}' took ${time}ms.`); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c6986bd0f57..9652c2dfe00 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -564,7 +564,7 @@ export function isResourceDiffEditorInput(editor: unknown): editor is IResourceD return candidate?.original !== undefined && candidate.modified !== undefined; } -export function isResourceDiffListEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput { +export function isResourceMultiDiffEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput { if (isEditorInput(editor)) { return false; // make sure to not accidentally match on typed editor inputs } @@ -1310,7 +1310,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } @@ -1379,7 +1379,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 7228b47ae71..0c6e63d43ce 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; import { EditorInput, IUntypedEditorOptions } from 'vs/workbench/common/editor/editorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -210,7 +210,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi return new SideBySideEditorInput(this.preferredName, this.preferredDescription, primarySaveResult, primarySaveResult, this.editorService); } - if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceDiffListEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { + if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceMultiDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { return { primary: primarySaveResult, secondary: primarySaveResult, @@ -279,7 +279,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi if ( primaryResourceEditorInput && secondaryResourceEditorInput && !isResourceDiffEditorInput(primaryResourceEditorInput) && !isResourceDiffEditorInput(secondaryResourceEditorInput) && - !isResourceDiffListEditorInput(primaryResourceEditorInput) && !isResourceDiffListEditorInput(secondaryResourceEditorInput) && + !isResourceMultiDiffEditorInput(primaryResourceEditorInput) && !isResourceMultiDiffEditorInput(secondaryResourceEditorInput) && !isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput) && !isResourceMergeEditorInput(primaryResourceEditorInput) && !isResourceMergeEditorInput(secondaryResourceEditorInput) ) { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 4344d015873..ab81ed790df 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -343,9 +343,9 @@ export class MultiDiffEditorResolverContribution extends Disposable { }, {}, { - createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { + createMultiDiffEditorInput: (multiDiffEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { return { - editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(diffListEditor, instantiationService), + editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(multiDiffEditor, instantiationService), }; }, } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index f362b61aa1b..f5d98f3fd90 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -97,7 +97,32 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private measureNetworkConnectionLatencyScheduler: RunOnceScheduler | undefined = undefined; private loggedInvalidGroupNames: { [group: string]: boolean } = Object.create(null); - private readonly remoteExtensionMetadata: RemoteExtensionMetadata[]; + + private _remoteExtensionMetadata: RemoteExtensionMetadata[] | undefined = undefined; + private get remoteExtensionMetadata(): RemoteExtensionMetadata[] { + if (!this._remoteExtensionMetadata) { + const remoteExtensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips }; + this._remoteExtensionMetadata = Object.values(remoteExtensionTips).filter(value => value.startEntry !== undefined).map(value => { + return { + id: value.extensionId, + installed: false, + friendlyName: value.friendlyName, + isPlatformCompatible: false, + dependencies: [], + helpLink: value.startEntry?.helpLink ?? '', + startConnectLabel: value.startEntry?.startConnectLabel ?? '', + startCommand: value.startEntry?.startCommand ?? '', + priority: value.startEntry?.priority ?? 10, + supportedPlatforms: value.supportedPlatforms + }; + }); + + this.remoteExtensionMetadata.sort((ext1, ext2) => ext1.priority - ext2.priority); + } + + return this._remoteExtensionMetadata; + } + private remoteMetadataInitialized: boolean = false; private readonly _onDidChangeEntries = this._register(new Emitter()); private readonly onDidChangeEntries: Event = this._onDidChangeEntries.event; @@ -124,24 +149,6 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr ) { super(); - const remoteExtensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips }; - this.remoteExtensionMetadata = Object.values(remoteExtensionTips).filter(value => value.startEntry !== undefined).map(value => { - return { - id: value.extensionId, - installed: false, - friendlyName: value.friendlyName, - isPlatformCompatible: false, - dependencies: [], - helpLink: value.startEntry?.helpLink ?? '', - startConnectLabel: value.startEntry?.startConnectLabel ?? '', - startCommand: value.startEntry?.startCommand ?? '', - priority: value.startEntry?.priority ?? 10, - supportedPlatforms: value.supportedPlatforms - }; - }); - - this.remoteExtensionMetadata.sort((ext1, ext2) => ext1.priority - ext2.priority); - // Set initial connection state if (this.remoteAuthority) { this.connectionState = 'initializing'; diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 91d1b7f6da9..d6c3375262a 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -10,7 +10,7 @@ import { basename, extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorActivation, EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor, isResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Schemas } from 'vs/base/common/network'; @@ -476,7 +476,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver } // If it's a diff list editor we trigger the create diff list editor input - if (isResourceDiffListEditorInput(editor)) { + if (isResourceMultiDiffEditorInput(editor)) { if (!selectedEditor.editorFactoryObject.createMultiDiffEditorInput) { return; } diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index cd571b4d9e2..bc3a26d311b 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -108,7 +108,7 @@ export type UntitledEditorInputFactoryFunction = (untitledEditorInput: IUntitled export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; -export type DiffListEditorInputFactoryFunction = (diffEditorInput: IResourceMultiDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; +export type MultiDiffEditorInputFactoryFunction = (multiDiffEditorInput: IResourceMultiDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult; export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult; @@ -116,7 +116,7 @@ type EditorInputFactories = { createEditorInput?: EditorInputFactoryFunction; createUntitledEditorInput?: UntitledEditorInputFactoryFunction; createDiffEditorInput?: DiffEditorInputFactoryFunction; - createMultiDiffEditorInput?: DiffListEditorInputFactoryFunction; + createMultiDiffEditorInput?: MultiDiffEditorInputFactoryFunction; createMergeEditorInput?: MergeEditorInputFactoryFunction; }; From 9f4bcdde684bc54e311ea49cb469771796094e7d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 26 Feb 2024 13:08:31 +0100 Subject: [PATCH 130/753] voice - tweak mic animation further (#206064) --- .../electron-sandbox/actions/voiceChatActions.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b508d24ff6b..36ff7b6c4b0 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -788,7 +788,18 @@ registerThemingParticipant((theme, collector) => { .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { border-radius: 50%; outline: 2px solid ${activeRecordingColor}; - animation: pulseAnimation 1500ms ease-in-out infinite !important; + outline-offset: -1px; + animation: pulseAnimation 1500ms cubic-bezier(0.75, 0, 0.25, 1) infinite; + } + + .monaco-workbench:not(.reduce-motion) .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, + .monaco-workbench:not(.reduce-motion) .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + position: absolute; + outline: 1px solid ${activeRecordingColor}; + outline-offset: 2px; + border-radius: 50%; + width: 16px; + height: 16px; } @keyframes pulseAnimation { From 10e94ec363bf41ca09098a44186bc915b710cb61 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 26 Feb 2024 14:03:25 +0100 Subject: [PATCH 131/753] rename suggestions: use editor#typicalHalfwidthCharacterWidth --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 6cb9f8a8c5a..3bb00ecdb05 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -435,6 +435,7 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; private _minimumWidth: number; + private _typicalHalfwidthCharacterWidth: number; private _disposables: DisposableStore; @@ -446,6 +447,7 @@ class CandidatesView { this._minimumWidth = 0; this._lineHeight = opts.fontInfo.lineHeight; + this._typicalHalfwidthCharacterWidth = opts.fontInfo.typicalHalfwidthCharacterWidth; this._listContainer = document.createElement('div'); this._listContainer.style.fontFamily = opts.fontInfo.fontFamily; @@ -612,8 +614,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - const APPROXIMATE_CHAR_WIDTH = 8; // approximate # of pixes taken by a single character - const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * APPROXIMATE_CHAR_WIDTH); // TODO@ulugbekna: use editor#typicalCharacterWidth or something + const longestCandidateWidth = Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * this._typicalHalfwidthCharacterWidth); const width = Math.max( this._minimumWidth, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + longestCandidateWidth + 10 /* (possibly visible) scrollbar width */ // TODO@ulugbekna: approximate calc - clean this up From 20f1afb29131d150028318dfe8afe33efc288693 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 26 Feb 2024 14:40:58 +0100 Subject: [PATCH 132/753] api - update todos, rename makeChatRequest (#206240) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vscode-dts/vscode.proposed.languageModels.d.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3bf78d8ed24..65b718f3f05 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1437,7 +1437,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); }, - makeChatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { + chatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); let options: Record; if (CancellationToken.isCancellationToken(optionsOrToken)) { diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index bde1bd4aad4..258a5bf18e9 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -190,12 +190,19 @@ declare module 'vscode' { */ // TODO@API refine doc // TODO@API define specific error types? - export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; + // TODO@API NAME: chatRequest + // TODO@API NAME: ChatRequestXYZMessage + // TODO@API NAME: ChatRequestResponse + export function chatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; /** - * @see {@link makeChatRequest} + * @see {@link chatRequest} */ - export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + export function chatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + + // TODO@API probe on having access + // TODO@API, BETTER?: ExtensionContext.permissions.languageModels: Record; + // export function canMakeChatRequest(languageModel: string): Thenable; /** * The identifiers of all language models that are currently available. From b1a49d4cdb0ae53e19a93a27cb276061e34d9d8b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:19:48 +0100 Subject: [PATCH 133/753] Fix hover pointer and override options (#206246) fix hover pointer and override options --- src/vs/platform/hover/browser/hover.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index fea45187b43..0a593c04ec8 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -281,14 +281,17 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this.hoverService.showHover({ ...options, + ...overrideOptions, persistence: { hideOnHover: true, hideOnKeyDown: true, + ...overrideOptions.persistence }, appearance: { + ...options.appearance, compact: true, - }, - ...overrideOptions + ...overrideOptions.appearance + } }, focus); } From 845c8ed65c5daecf5395d6216db63cf2bb0eaa9b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 26 Feb 2024 15:35:06 +0100 Subject: [PATCH 134/753] Indentation doesn't work in .yml files (#205979) --- extensions/docker/language-configuration.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/docker/language-configuration.json b/extensions/docker/language-configuration.json index cab4f6602ff..08a483ad5ce 100644 --- a/extensions/docker/language-configuration.json +++ b/extensions/docker/language-configuration.json @@ -20,5 +20,9 @@ ["(", ")"], ["\"", "\""], ["'", "'"] - ] -} \ No newline at end of file + ], + "indentationRules": { + "increaseIndentPattern": "^\\s*.*(:|-) ?(&\\w+)?(\\{[^}\"']*|\\([^)\"']*)?$", + "decreaseIndentPattern": "^\\s+\\}$" + } +} From 69db64290797e02b48dc481e4be0928b5cf02d39 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:06:57 -0800 Subject: [PATCH 135/753] Add warning to suggestEnabled via deprecation message I get the occasional report of people bricking their terminal because of this setting. I'd prefer not to remove it, the deprecation message gives the best of both worlds (still in code, loud warning when used). Fixes #206172 --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 0df9dc1171c..3b3cafe11ab 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -601,7 +601,8 @@ const terminalConfiguration: IConfigurationNode = { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.suggestEnabled', "Enables experimental terminal Intellisense suggestions for supported shells when {0} is set to {1}. If shell integration is installed manually, {2} needs to be set to {3} before calling the script.", '`#terminal.integrated.shellIntegration.enabled#`', '`true`', '`VSCODE_SUGGEST`', '`1`'), type: 'boolean', - default: false + default: false, + markdownDeprecationMessage: localize('suggestEnabled.deprecated', 'This is an experimental setting and may break the terminal! Use at your own risk.') }, [TerminalSettingId.SmoothScrolling]: { markdownDescription: localize('terminal.integrated.smoothScrolling', "Controls whether the terminal will scroll using an animation."), From 5443fa0bd8d08629f69e3ed075cb358537867bb7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:55:29 -0800 Subject: [PATCH 136/753] De-dupe links and add URI-based presentation Fixes #206260 Fixes #206124 --- .../links/browser/terminalLinkManager.ts | 2 +- .../links/browser/terminalLinkQuickpick.ts | 55 +++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts index 8d981fb62d2..5d8f803de51 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager.ts @@ -441,6 +441,6 @@ export interface ILineColumnInfo { export interface IDetectedLinks { wordLinks?: ILink[]; webLinks?: ILink[]; - fileLinks?: ILink[]; + fileLinks?: (ILink | TerminalLink)[]; folderLinks?: ILink[]; } diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 0cea7b3f514..2c042949a9d 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -22,6 +22,8 @@ import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browse import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; export class TerminalLinkQuickpick extends DisposableStore { @@ -35,6 +37,7 @@ export class TerminalLinkQuickpick extends DisposableStore { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IHistoryService private readonly _historyService: IHistoryService, + @ILabelService private readonly _labelService: ILabelService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { @@ -148,17 +151,57 @@ export class TerminalLinkQuickpick extends DisposableStore { /** * @param ignoreLinks Links with labels to not include in the picks. */ - private async _generatePicks(links: ILink[], ignoreLinks?: ILink[]): Promise { + private async _generatePicks(links: (ILink | TerminalLink)[], ignoreLinks?: ILink[]): Promise { if (!links) { return; } - const linkKeys: Set = new Set(); + const linkTextKeys: Set = new Set(); + const linkUriKeys: Set = new Set(); const picks: ITerminalLinkQuickPickItem[] = []; for (const link of links) { - const label = link.text; - if (!linkKeys.has(label) && (!ignoreLinks || !ignoreLinks.some(e => e.text === label))) { - linkKeys.add(label); - picks.push({ label, link }); + let label = link.text; + if (!linkTextKeys.has(label) && (!ignoreLinks || !ignoreLinks.some(e => e.text === label))) { + linkTextKeys.add(label); + + // Add a consistently formatted resolved URI label to the description if applicable + let description: string | undefined; + if ('uri' in link && link.uri) { + // For local files and folders, mimic the presentation of go to file + if ( + link.type === TerminalBuiltinLinkType.LocalFile || + link.type === TerminalBuiltinLinkType.LocalFolderInWorkspace || + link.type === TerminalBuiltinLinkType.LocalFolderOutsideWorkspace + ) { + label = basenameOrAuthority(link.uri); + description = this._labelService.getUriLabel(dirname(link.uri), { relative: true }); + } + + // Add line and column numbers to the label if applicable + if (link.type === TerminalBuiltinLinkType.LocalFile) { + if (link.parsedLink?.suffix?.row !== undefined) { + label += `:${link.parsedLink.suffix.row}`; + if (link.parsedLink?.suffix?.rowEnd !== undefined) { + label += `-${link.parsedLink.suffix.rowEnd}`; + } + if (link.parsedLink?.suffix?.col !== undefined) { + label += `:${link.parsedLink.suffix.col}`; + if (link.parsedLink?.suffix?.colEnd !== undefined) { + label += `-${link.parsedLink.suffix.colEnd}`; + } + } + } + } + + // Skip the link if it's a duplicate URI + line/col + if (description) { + if (linkUriKeys.has(label + '|' + description)) { + continue; + } + linkUriKeys.add(label + '|' + description); + } + } + + picks.push({ label, link, description }); } } return picks.length > 0 ? picks : undefined; From a984153d6a98fba04f0a39a5ab9918c7b6e47511 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:02:42 +0100 Subject: [PATCH 137/753] Hover debt. Mainly import changes (#206247) * hover debt * hover import * less hover imports --- .../browser/ui/actionbar/actionViewItems.ts | 13 ++- src/vs/base/browser/ui/actionbar/actionbar.ts | 6 +- src/vs/base/browser/ui/button/button.ts | 10 +-- src/vs/base/browser/ui/dropdown/dropdown.ts | 8 +- .../ui/dropdown/dropdownActionViewItem.ts | 4 +- src/vs/base/browser/ui/findinput/findInput.ts | 4 +- .../browser/ui/findinput/findInputToggles.ts | 4 +- .../base/browser/ui/findinput/replaceInput.ts | 2 +- src/vs/base/browser/ui/hover/hoverDelegate.ts | 83 +++++++++++++------ .../browser/ui/hover/hoverDelegateFactory.ts | 35 ++++++++ .../ui/hover/{hover.css => hoverWidget.css} | 0 src/vs/base/browser/ui/hover/hoverWidget.ts | 2 +- .../updatableHoverWidget.ts} | 5 +- .../browser/ui/iconLabel/iconHoverDelegate.ts | 72 ---------------- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 6 +- .../browser/ui/selectBox/selectBoxCustom.ts | 24 ++++-- src/vs/base/browser/ui/table/tableWidget.ts | 9 +- src/vs/base/browser/ui/toggle/toggle.ts | 6 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 6 +- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- .../services/hoverService/hoverService.ts | 2 +- .../services/hoverService/hoverWidget.ts | 2 +- .../contrib/find/browser/findOptionsWidget.ts | 4 +- .../editor/contrib/find/browser/findWidget.ts | 10 +-- .../browser/standaloneCodeEditor.ts | 2 +- src/vs/platform/actions/browser/buttonbar.ts | 6 +- .../dropdownWithPrimaryActionViewItem.ts | 2 +- .../browser/menuEntryActionViewItem.ts | 2 +- src/vs/platform/hover/browser/hover.ts | 3 +- .../platform/quickinput/browser/quickInput.ts | 2 +- .../quickinput/browser/quickInputList.ts | 4 +- .../browser/parts/compositeBarActions.ts | 2 +- .../workbench/browser/parts/compositePart.ts | 8 +- .../parts/editor/breadcrumbsControl.ts | 4 +- .../browser/parts/editor/editorTabsControl.ts | 4 +- .../notifications/notificationsViewer.ts | 4 +- .../browser/parts/statusbar/statusbarItem.ts | 4 +- .../parts/titlebar/commandCenterControl.ts | 6 +- .../browser/parts/titlebar/titlebarPart.ts | 6 +- .../workbench/browser/parts/views/checkbox.ts | 4 +- .../workbench/browser/parts/views/treeView.ts | 4 +- .../workbench/browser/parts/views/viewPane.ts | 4 +- src/vs/workbench/browser/workbench.ts | 2 +- .../contrib/comments/browser/commentReply.ts | 4 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/timestamp.ts | 4 +- .../contrib/debug/browser/baseDebugView.ts | 4 +- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 8 +- .../debug/browser/debugActionViewItems.ts | 4 +- .../contrib/debug/browser/replViewer.ts | 4 +- .../abstractRuntimeExtensionsEditor.ts | 4 +- .../extensions/browser/extensionEditor.ts | 4 +- .../extensions/browser/extensionsWidgets.ts | 4 +- .../files/browser/views/explorerViewer.ts | 3 +- .../inlineChat/browser/inlineChatWidget.ts | 4 +- .../browser/view/cellParts/cellActionView.ts | 4 +- .../browser/view/cellParts/cellStatusPart.ts | 4 +- .../browser/view/cellParts/cellToolbars.ts | 6 +- .../settingsEditorSettingIndicators.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 4 +- .../search/browser/patternInputWidget.ts | 2 +- .../search/browser/searchResultsView.ts | 4 +- .../contrib/search/browser/searchView.ts | 4 +- .../contrib/search/browser/searchWidget.ts | 2 +- .../searchEditor/browser/searchEditor.ts | 4 +- .../contrib/terminal/browser/terminalView.ts | 2 +- .../testing/browser/testCoverageBars.ts | 4 +- .../testing/browser/testingExplorerView.ts | 4 +- .../contrib/timeline/browser/timelinePane.ts | 4 +- .../userDataProfileImportExportService.ts | 2 +- 71 files changed, 256 insertions(+), 247 deletions(-) create mode 100644 src/vs/base/browser/ui/hover/hoverDelegateFactory.ts rename src/vs/base/browser/ui/hover/{hover.css => hoverWidget.css} (100%) rename src/vs/base/browser/ui/{iconLabel/iconLabelHover.ts => hover/updatableHoverWidget.ts} (98%) delete mode 100644 src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index fd7424b0f72..698d711f4dc 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -9,9 +9,9 @@ import { addDisposableListener, EventHelper, EventLike, EventType } from 'vs/bas import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Action, ActionRunner, IAction, IActionChangeEvent, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -230,11 +230,10 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { /* While custom hover is not supported with context view */ this.element.title = title; } else { - if (!this.customHover) { + if (!this.customHover && title !== '') { const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this.customHover = setupCustomHover(hoverDelegate, this.element, title); - this._store.add(this.customHover); - } else { + this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.element, title)); + } else if (this.customHover) { this.customHover.update(title); } } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 5f5ad352842..05505b768a5 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -6,8 +6,8 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -118,7 +118,7 @@ export class ActionBar extends Disposable implements IActionRunner { keys: this.options.triggerKeys?.keys ?? [KeyCode.Enter, KeyCode.Space] }; - this._hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); + this._hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); if (this.options.actionRunner) { this._actionRunner = this.options.actionRunner; diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index e7ca41dc8b3..1d2a3364d0b 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,9 +9,9 @@ import { sanitize } from 'vs/base/browser/dompurify/dompurify'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; @@ -303,9 +303,9 @@ export class Button extends Disposable implements IButton { } private setTitle(title: string) { - if (!this._hover) { + if (!this._hover && title !== '') { this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); - } else { + } else if (this._hover) { this._hover.update(title); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 88dfaf2c5b1..dfc2329510f 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -8,8 +8,8 @@ import { $, addDisposableListener, append, EventHelper, EventType, isMouseEvent import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -105,9 +105,9 @@ class BaseDropdown extends ActionRunner { set tooltip(tooltip: string) { if (this._label) { - if (!this.hover) { + if (!this.hover && tooltip !== '') { this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); - } else { + } else if (this.hover) { this.hover.update(tooltip); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 995b8f40817..75333985372 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -19,8 +19,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./dropdown'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index c119ad43779..9ecfcbce827 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IFindInputOptions { @@ -114,7 +114,7 @@ export class FindInput extends Widget { inputBoxStyles: options.inputBoxStyles, })); - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); if (this.showCommonFindToggles) { this.regex = this._register(new RegexToggle({ diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 8b3ea12580f..adce009430b 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import * as nls from 'vs/nls'; diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 8e20025d757..4dfdf549a3b 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,7 +16,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IReplaceInputOptions { diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 6d2dfef371a..57f6962ac0e 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -3,33 +3,66 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { Lazy } from 'vs/base/common/lazy'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IHoverWidget, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IDisposable } from 'vs/base/common/lifecycle'; -const nullHoverDelegateFactory = () => ({ - get delay(): number { return -1; }, - dispose: () => { }, - showHover: () => { return undefined; }, -}); - -let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory; -const defaultHoverDelegateMouse = new Lazy(() => hoverDelegateFactory('mouse', false)); -const defaultHoverDelegateElement = new Lazy(() => hoverDelegateFactory('element', false)); - -export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void { - hoverDelegateFactory = hoverDelegateProvider; +export interface IHoverDelegateTarget extends IDisposable { + readonly targetElements: readonly HTMLElement[]; + x?: number; } -export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate; -export function getDefaultHoverDelegate(placement: 'element', enableInstantHover: true): IScopedHoverDelegate; -export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover?: boolean): IHoverDelegate | IScopedHoverDelegate { - if (enableInstantHover) { - // If instant hover is enabled, the consumer is responsible for disposing the hover delegate - return hoverDelegateFactory(placement, true); - } +export interface IHoverDelegateOptions extends IUpdatableHoverOptions { + /** + * The content to display in the primary section of the hover. The type of text determines the + * default `hideOnHover` behavior. + */ + content: IMarkdownString | string | HTMLElement; + /** + * The target for the hover. This determines the position of the hover and it will only be + * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for + * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a + * dispose method is required. + */ + target: IHoverDelegateTarget | HTMLElement; + /** + * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover + * in. This is particularly useful for more natural tab focusing behavior, where the hover is + * created as the next tab index after the element being hovered and/or to workaround the + * element's container hiding on `focusout`. + */ + container?: HTMLElement; + /** + * Options that defines where the hover is positioned. + */ + position?: { + /** + * Position of the hover. The default is to show above the target. This option will be ignored + * if there is not enough room to layout the hover in the specified position, unless the + * forcePosition option is set. + */ + hoverPosition?: HoverPosition; + }; + appearance?: { + /** + * Whether to show the hover pointer + */ + showPointer?: boolean; + /** + * Whether to skip the fade in animation, this should be used when hovering from one hover to + * another in the same group so it looks like the hover is moving from one element to the other. + */ + skipFadeInAnimation?: boolean; + }; +} - if (placement === 'element') { - return defaultHoverDelegateElement.value; - } - return defaultHoverDelegateMouse.value; +export interface IHoverDelegate { + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; + onDidHideHover?: () => void; + delay: number; + placement?: 'mouse' | 'element'; + showNativeHover?: boolean; // TODO@benibenj remove this, only temp fix for contextviews } + +export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } diff --git a/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts b/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts new file mode 100644 index 00000000000..44628261333 --- /dev/null +++ b/src/vs/base/browser/ui/hover/hoverDelegateFactory.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { Lazy } from 'vs/base/common/lazy'; + +const nullHoverDelegateFactory = () => ({ + get delay(): number { return -1; }, + dispose: () => { }, + showHover: () => { return undefined; }, +}); + +let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory; +const defaultHoverDelegateMouse = new Lazy(() => hoverDelegateFactory('mouse', false)); +const defaultHoverDelegateElement = new Lazy(() => hoverDelegateFactory('element', false)); + +export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void { + hoverDelegateFactory = hoverDelegateProvider; +} + +export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate { + if (placement === 'element') { + return defaultHoverDelegateElement.value; + } + return defaultHoverDelegateMouse.value; +} + +export function createInstantHoverDelegate(): IScopedHoverDelegate { + // Creates a hover delegate with instant hover enabled. + // This hover belongs to the consumer and requires the them to dispose it. + // Instant hover only makes sense for 'element' placement. + return hoverDelegateFactory('element', true); +} diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hoverWidget.css similarity index 100% rename from src/vs/base/browser/ui/hover/hover.css rename to src/vs/base/browser/ui/hover/hoverWidget.css diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index dc0af66ff5a..bff397303be 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; -import 'vs/css!./hover'; +import 'vs/css!./hoverWidget'; import { localize } from 'vs/nls'; const $ = dom.$; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts similarity index 98% rename from src/vs/base/browser/ui/iconLabel/iconLabelHover.ts rename to src/vs/base/browser/ui/hover/updatableHoverWidget.ts index bdcdfa7c7da..c3c505cef39 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/hover/updatableHoverWidget.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/hover/hoverDelegate'; import { TimeoutTimer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; @@ -68,6 +68,9 @@ export interface ICustomHover extends IDisposable { update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void; } +export interface IHoverWidget extends IDisposable { + readonly isDisposed: boolean; +} class UpdatableHoverWidget implements IDisposable { diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts deleted file mode 100644 index f0209856dc3..00000000000 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export interface IHoverDelegateTarget extends IDisposable { - readonly targetElements: readonly HTMLElement[]; - x?: number; -} - -export interface IHoverDelegateOptions extends IUpdatableHoverOptions { - /** - * The content to display in the primary section of the hover. The type of text determines the - * default `hideOnHover` behavior. - */ - content: IMarkdownString | string | HTMLElement; - /** - * The target for the hover. This determines the position of the hover and it will only be - * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for - * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a - * dispose method is required. - */ - target: IHoverDelegateTarget | HTMLElement; - /** - * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover - * in. This is particularly useful for more natural tab focusing behavior, where the hover is - * created as the next tab index after the element being hovered and/or to workaround the - * element's container hiding on `focusout`. - */ - container?: HTMLElement; - /** - * Options that defines where the hover is positioned. - */ - position?: { - /** - * Position of the hover. The default is to show above the target. This option will be ignored - * if there is not enough room to layout the hover in the specified position, unless the - * forcePosition option is set. - */ - hoverPosition?: HoverPosition; - }; - appearance?: { - /** - * Whether to show the hover pointer - */ - showPointer?: boolean; - /** - * Whether to skip the fade in animation, this should be used when hovering from one hover to - * another in the same group so it looks like the hover is moving from one element to the other. - */ - skipFadeInAnimation?: boolean; - }; -} - -export interface IHoverDelegate { - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined; - onDidHideHover?: () => void; - delay: number; - placement?: 'mouse' | 'element'; - showNativeHover?: boolean; // TODO@benibenj remove this, only temp fix for contextviews -} - -export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { } - -export interface IHoverWidget extends IDisposable { - readonly isDisposed: boolean; -} diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index c9d62a9bc4e..5bf35b8ffc2 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -6,13 +6,13 @@ import 'vs/css!./iconlabel'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IMatch } from 'vs/base/common/filters'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { Range } from 'vs/base/common/range'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IIconLabelCreationOptions { readonly supportHighlights?: boolean; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ca6aaa33505..c813532f496 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -9,8 +9,8 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { AnchorPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IListEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -103,7 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; - private _hover: ICustomHover; + private _hover?: ICustomHover; private _sticky: boolean = false; // for dev purposes only @@ -134,8 +134,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription); } - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, '')); - this._onDidSelect = new Emitter(); this._register(this._onDidSelect); @@ -152,6 +150,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } + private setTitle(title: string): void { + if (!this._hover && title) { + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, title)); + } else if (this._hover) { + this._hover.update(title); + } + } + // IDelegate - List renderer getHeight(): number { @@ -204,7 +210,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: e.target.value }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } })); @@ -314,7 +320,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.selectedIndex = this.selected; if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } @@ -842,7 +848,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } @@ -941,7 +947,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: this.options[this.selected].text }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this._hover.update(this.options[this.selected].text); + this.setTitle(this.options[this.selected].text); } } diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 536fb25608e..38192d39413 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; @@ -132,7 +132,10 @@ class ColumnHeader extends Disposable implements IView { super(); this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label); - this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); + + if (column.tooltip) { + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); + } } layout(size: number): void { diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 540bb17db2f..53e0e2b9a30 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -13,9 +13,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./toggle'; import { isActiveElement, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index f92369cddfb..ebb6bc264d1 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -15,8 +15,8 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; @@ -60,7 +60,7 @@ export class ToolBar extends Disposable { constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); - options.hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true)); + options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c42cd0ba336..87e8f52fbc6 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,8 +33,8 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./media/tree'; import { localize } from 'vs/nls'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -807,7 +807,7 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - const toggleHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const toggleHoverDelegate = this._register(createInstantHoverDelegate()); this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter, hoverDelegate: toggleHoverDelegate })); this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy, hoverDelegate: toggleHoverDelegate })); this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 47fddf11c86..f3d6b5c9f16 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -19,7 +19,7 @@ import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ContextViewHandler } from 'vs/platform/contextview/browser/contextViewService'; export class HoverService extends Disposable implements IHoverService { diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index 8b4814a92c9..4ec59ed09bd 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; import { isMacintosh } from 'vs/base/common/platform'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { status } from 'vs/base/browser/ui/aria/aria'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = dom.$; type TargetRect = { diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 7b693a1f28c..007723f6986 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -13,7 +13,7 @@ import { FIND_IDS } from 'vs/editor/contrib/find/browser/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { asCssVariable, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -53,7 +53,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), }; - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 76189e64741..424375e8cae 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,9 +43,9 @@ import { isHighContrast } from 'vs/platform/theme/common/theme'; import { assertIsDefined } from 'vs/base/common/types'; import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Selection } from 'vs/editor/common/core/selection'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.')); const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); @@ -1014,7 +1014,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._updateMatchesCount(); // Create a scoped hover delegate for all find related buttons - const hoverDelegate = getDefaultHoverDelegate('element', true); + const hoverDelegate = this._register(createInstantHoverDelegate()); // Previous button this._prevBtn = this._register(new SimpleButton({ @@ -1148,7 +1148,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL })); // Create scoped hover delegate for replace actions - const replaceHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const replaceHoverDelegate = this._register(createInstantHoverDelegate()); // Replace one button this._replaceBtn = this._register(new SimpleButton({ diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 3e903d5bab9..05305694e57 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -39,7 +39,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { mainWindow } from 'vs/base/browser/window'; -import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; /** diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 94720d5b557..79cbe6faa37 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -73,7 +73,7 @@ export class WorkbenchButtonBar extends ButtonBar { this.clear(); // Support instamt hover between buttons - const hoverDelegate = this._updateStore.add(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._updateStore.add(createInstantHoverDelegate()); for (let i = 0; i < actions.length; i++) { diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index cfcc0e2c73b..97aac13854a 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -19,7 +19,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface IDropdownWithPrimaryActionViewItemOptions { actionRunner?: IActionRunner; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c1edd287bea..da596a8fc6c 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -26,7 +26,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDark } from 'vs/platform/theme/common/theme'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { assertType } from 'vs/base/common/types'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 0a593c04ec8..3a44ead957b 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -7,10 +7,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { addStandardDisposableListener } from 'vs/base/browser/dom'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export const IHoverService = createDecorator('hoverService'); diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 96dfeee7ea8..73c3a23a305 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { CountBadge, ICountBadgeStyles } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index de219a800bb..26e32890647 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -8,7 +8,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverDelegate, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -35,7 +35,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { URI } from 'vs/base/common/uri'; import { isDark } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverWidget, ITooltipMarkdownString } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = dom.$; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index cfa2d348aa0..6ae11c51361 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -26,7 +26,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { URI } from 'vs/base/common/uri'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export interface ICompositeBar { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 81a034ffa91..f4a0bb4fe28 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -33,9 +33,9 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate, getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; export interface ICompositeTitleLabel { @@ -97,7 +97,7 @@ export abstract class CompositePart extends Part { super(id, options, themeService, storageService, layoutService); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); - this.toolbarHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.toolbarHoverDelegate = this._register(createInstantHoverDelegate()); } protected openComposite(id: string, focus?: boolean): Composite | undefined { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index bf46167248f..00e7153c46f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -40,8 +40,8 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Emitter } from 'vs/base/common/event'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; class OutlineItem extends BreadcrumbsItem { diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 218c8808205..d7a82ed5c16 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -44,8 +44,8 @@ import { IAuxiliaryEditorPart, MergeGroupMode } from 'vs/workbench/services/edit import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class EditorCommandsContextActionRunner extends ActionRunner { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index e66dc53ad61..246532ddd02 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -29,8 +29,8 @@ import { Event } from 'vs/base/common/event'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class NotificationsListDelegate implements IListVirtualDelegate { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index e18622523c5..13b5fde792b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -21,9 +21,9 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; export class StatusbarEntryItem extends Disposable { diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 72eeea0b590..54eded7aab3 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -5,9 +5,9 @@ import { isActiveDocument, reset } from 'vs/base/browser/dom'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 176ac265172..548f4d36caf 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -52,9 +52,9 @@ import { IEditorCommandsContext, IEditorPartOptionsChangeEvent, IToolbarActions import { mainWindow } from 'vs/base/browser/window'; import { ACCOUNTS_ACTIVITY_TILE_ACTION, GLOBAL_ACTIVITY_TITLE_ACTION } from 'vs/workbench/browser/parts/titlebar/titlebarActions'; import { IView } from 'vs/base/browser/ui/grid/grid'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export interface ITitleVariable { readonly name: string; @@ -282,7 +282,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, targetWindow, editorGroupsContainer)); - this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.hoverDelegate = this._register(createInstantHoverDelegate()); this.registerListeners(getWindowId(targetWindow)); } diff --git a/src/vs/workbench/browser/parts/views/checkbox.ts b/src/vs/workbench/browser/parts/views/checkbox.ts index 849966bbe33..d79f2ac7930 100644 --- a/src/vs/workbench/browser/parts/views/checkbox.ts +++ b/src/vs/workbench/browser/parts/views/checkbox.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 3b40a248a1d..2af2a8be2e5 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -8,8 +8,8 @@ import * as DOM from 'vs/base/browser/dom'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode, ITreeRenderer, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree'; diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 9cc1b35c354..06b591f477a 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -47,8 +47,8 @@ import { FilterWidget, IFilterWidgetOptions } from 'vs/workbench/browser/parts/v import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultButtonStyles, defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export enum ViewPaneShowActions { /** Show the actions when the view is hovered. This is the default behavior. */ diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 8a311d4bb0e..0315e95a2d4 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -44,7 +44,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mainWindow } from 'vs/base/browser/window'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; -import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setHoverDelegateFactory } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IWorkbenchOptions { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 4d2a9e4b887..55ea0d4e8f9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -30,8 +30,8 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 585d253442c..451bc210789 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -32,8 +32,8 @@ import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; diff --git a/src/vs/workbench/contrib/comments/browser/timestamp.ts b/src/vs/workbench/contrib/comments/browser/timestamp.ts index 2d1fcf15b48..16b3969d2c3 100644 --- a/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { fromNow } from 'vs/base/common/date'; import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 90aba19a7f2..5a365a2cb9f 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -7,8 +7,8 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5de52259df4..13b72018035 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -8,9 +8,9 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IListContextMenuEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 43b69137af3..139928ec9fe 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -48,8 +48,8 @@ import { createDisconnectMenuItemAction } from 'vs/workbench/contrib/debug/brows import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, getStateLabel, IDebugModel, IDebugService, IDebugSession, IRawStoppedDetails, IStackFrame, IThread, State } from 'vs/workbench/contrib/debug/common/debug'; import { StackFrame, Thread, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = dom.$; @@ -556,9 +556,9 @@ class SessionsRenderer implements ICompressibleTreeRenderer { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 084e20c5898..04d8fe1ff4f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -66,7 +66,7 @@ import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -375,7 +375,7 @@ export class InlineChatWidget { this._store.add(this._slashCommandContentWidget); // Share hover delegates between toolbars to support instant hover between both - const hoverDelegate = this._store.add(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._store.add(createInstantHoverDelegate()); // toolbars diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index 35007bedeb8..bcfd1900226 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -13,8 +13,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/base/common/themables'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export class CodiconActionViewItem extends MenuEntryActionViewItem { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts index 52e87ad8086..b5c306d0b41 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellStatusPart.ts @@ -27,8 +27,8 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell import { ClickTargetType, IClickTarget } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { CellStatusbarAlignment, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index f10530616f1..250cb9824ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -22,8 +22,8 @@ import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/vie import { CellOverlayPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { registerCellToolbarStickyScroll } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class BetweenCellToolbar extends CellOverlayPart { private _betweenCellToolbar: ToolBar | undefined; @@ -167,7 +167,7 @@ export class CellTitleToolbarPart extends CellOverlayPart { if (this._view) { return this._view; } - const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + const hoverDelegate = this._register(createInstantHoverDelegate()); const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { actionViewItemProvider: (action, options) => { return createActionViewItem(this.instantiationService, action, options); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 2f32b9c5932..cf98a39a26e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -22,7 +22,7 @@ import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/bro import { POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; const $ = DOM.$; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 35ba3ee8c5d..49a702fbbfe 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -51,12 +51,12 @@ import { WorkbenchTable } from 'vs/platform/list/browser/listService'; import { Button } from 'vs/base/browser/ui/button/button'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { STATUS_BAR_REMOTE_ITEM_BACKGROUND } from 'vs/workbench/common/theme'; import { Codicon } from 'vs/base/common/codicons'; import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Attributes, CandidatePort, Tunnel, TunnelCloseReason, TunnelModel, TunnelSource, forwardedPortsViewEnabled, makeAddress, mapHasAddressLocalhostOrAllInterfaces, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index c7dfc7eae60..d12c8269212 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IOptions { placeholder?: string; diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 38a4f536403..e22115c2a8f 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -30,8 +30,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; interface IFolderMatchTemplate { label: IResourceLabel; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 4a0a156fcf6..6ea0cb4542e 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -81,8 +81,8 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 731653a4828..332c290246a 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -42,7 +42,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { GroupModelChangeKind } from 'vs/workbench/common/editor'; import { SearchFindInput } from 'vs/workbench/contrib/search/browser/searchFindInput'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 02b833d6737..449b0d60787 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -62,8 +62,8 @@ import { UnusualLineTerminatorsDetector } from 'vs/editor/contrib/unusualLineTer import { defaultToggleStyles, getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; import { ILogService } from 'vs/platform/log/common/log'; import { SearchContext } from 'vs/workbench/contrib/search/common/constants'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 31ed968488e..7bf4f427bd0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -44,7 +44,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Event } from 'vs/base/common/event'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { InstanceContext, TerminalContextActionRunner } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 7261e5e1021..56d486699a6 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { h } from 'vs/base/browser/dom'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { assertNever } from 'vs/base/common/assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 8eb58e118a3..e5e8e34abfa 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -8,8 +8,8 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; -import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DefaultKeyboardNavigationDelegate, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 06476f564c8..87503bb990c 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -49,13 +49,13 @@ import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { isString } from 'vs/base/common/types'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { ILocalizedString } from 'vs/platform/action/common/action'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; const ItemHeight = 22; diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 45ea476ec9a..987d9f119b9 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -77,7 +77,7 @@ import { IHoverService } from 'vs/platform/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { DEFAULT_ICON, ICONS } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; import { WorkbenchIconSelectBox } from 'vs/workbench/services/userDataProfile/browser/iconSelectBox'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverWidget } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; From 0bdc2f09bb775f72e72f5ca178a9f918e9dc575a Mon Sep 17 00:00:00 2001 From: Mahmoud Salah Date: Mon, 26 Feb 2024 18:09:44 +0200 Subject: [PATCH 138/753] =?UTF-8?q?for=20diff=20editors,=20resolve=20the?= =?UTF-8?q?=20modified=20editor=20to=20allow=20run=20tests=20in=20c?= =?UTF-8?q?=E2=80=A6=20(#206026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * for diff editors, resolve the modified editor to allow run tests in current file or run test at cursor to work * Check for active code editor when finding tests. * Handle compilation issues with possible null. * update import path. * Missing null check. * Remove cast. --------- Co-authored-by: Mahmoud Khalil --- .../testing/browser/testExplorerActions.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index f6f451bcd0d..103efb04a9c 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -9,7 +9,8 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -981,15 +982,20 @@ abstract class ExecuteTestAtCursor extends Action2 { * @override */ public async run(accessor: ServicesAccessor) { + const codeEditorService = accessor.get(ICodeEditorService); const editorService = accessor.get(IEditorService); const activeEditorPane = editorService.activeEditorPane; - const activeControl = editorService.activeTextEditorControl; - if (!activeEditorPane || !activeControl) { + let editor = codeEditorService.getActiveCodeEditor(); + if (!activeEditorPane || !editor) { return; } - const position = activeControl?.getPosition(); - const model = activeControl?.getModel(); + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + + const position = editor?.getPosition(); + const model = editor?.getModel(); if (!position || !model || !('uri' in model)) { return; } @@ -1053,8 +1059,8 @@ abstract class ExecuteTestAtCursor extends Action2 { group: this.group, tests: bestNodes.length ? bestNodes : bestNodesBefore, }); - } else if (isCodeEditor(activeControl)) { - MessageController.get(activeControl)?.showMessage(localize('noTestsAtCursor', "No tests found here"), position); + } else if (editor) { + MessageController.get(editor)?.showMessage(localize('noTestsAtCursor', "No tests found here"), position); } } } @@ -1186,9 +1192,15 @@ abstract class ExecuteTestsInCurrentFile extends Action2 { * @override */ public run(accessor: ServicesAccessor) { - const control = accessor.get(IEditorService).activeTextEditorControl; - const position = control?.getPosition(); - const model = control?.getModel(); + let editor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + if (!editor) { + return; + } + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + const position = editor?.getPosition(); + const model = editor?.getModel(); if (!position || !model || !('uri' in model)) { return; } @@ -1218,8 +1230,8 @@ abstract class ExecuteTestsInCurrentFile extends Action2 { }); } - if (isCodeEditor(control)) { - MessageController.get(control)?.showMessage(localize('noTestsInFile', "No tests found in this file"), position); + if (editor) { + MessageController.get(editor)?.showMessage(localize('noTestsInFile', "No tests found in this file"), position); } return undefined; From 2ba3fae68d15d0a83a00cc62e2d84962180c359a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 08:45:12 -0800 Subject: [PATCH 139/753] Pick up TS 5.4 rc for bundling (#206263) --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 4365c20acc1..7066412c3f8 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.3.2" + "typescript": "5.4.1-rc" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f4543f6a1c9..4b22ef50a82 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -234,10 +234,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +typescript@5.4.1-rc: + version "5.4.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.1-rc.tgz#1ecdd897df1d9ef5bd1f844bad64691ecc23314d" + integrity sha512-gInURzaO0bbfzfQAc3mfcHxh8qev+No4QOFUZHajo9vBgOLaljELJ3wuzyoGo/zHIzMSezdhtrsRdqL6E9SvNA== vscode-grammar-updater@^1.1.0: version "1.1.0" From 3d880720c822671cd4aa38fa9529567388b3b191 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 09:01:43 -0800 Subject: [PATCH 140/753] fix #205066 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 50a7fc56a29..9e6c751c89c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1654,7 +1654,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StartVoice, - title: localize2('workbench.action.terminal.startVoice', "Start Terminal Voice"), + title: localize2('workbench.action.terminal.startDictation', "Start Dictation in Terminal"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { @@ -1665,7 +1665,7 @@ export function registerTerminalActions() { registerActiveInstanceAction({ id: TerminalCommandId.StopVoice, - title: localize2('workbench.action.terminal.stopVoice', "Stop Terminal Voice"), + title: localize2('workbench.action.terminal.stopDictation', "Stop Dictation in Terminal"), precondition: ContextKeyExpr.and(HasSpeechProvider, sharedWhenClause.terminalAvailable), f1: true, run: (activeInstance, c, accessor) => { From 0a0f196666c687e0dd0642411a70ba0e7f9cb1c1 Mon Sep 17 00:00:00 2001 From: Luis Sousa Date: Mon, 26 Feb 2024 14:19:56 -0300 Subject: [PATCH 141/753] Feat: Add PascalCase to CaseActions (#206259) feat: Add PascalCase to CaseActions --- .../browser/linesOperations.ts | 32 +++++++++ .../test/browser/linesOperations.test.ts | 70 ++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 74d7849587e..dd6b3158491 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1187,6 +1187,35 @@ export class CamelCaseAction extends AbstractCaseAction { } } +export class PascalCaseAction extends AbstractCaseAction { + public static wordBoundary = new BackwardsCompatibleRegExp('[_\\s-]', 'gm'); + public static wordBoundaryToMaintain = new BackwardsCompatibleRegExp('(?<=\\.)', 'gm'); + + constructor() { + super({ + id: 'editor.action.transformToPascalcase', + label: nls.localize('editor.transformToPascalcase', "Transform to Pascal Case"), + alias: 'Transform to Pascal Case', + precondition: EditorContextKeys.writable + }); + } + + protected _modifyText(text: string, wordSeparators: string): string { + const wordBoundary = PascalCaseAction.wordBoundary.get(); + const wordBoundaryToMaintain = PascalCaseAction.wordBoundaryToMaintain.get(); + + if (!wordBoundary || !wordBoundaryToMaintain) { + // cannot support this + return text; + } + + const wordsWithMaintainBoundaries = text.split(wordBoundaryToMaintain); + const words = wordsWithMaintainBoundaries.map((word: string) => word.split(wordBoundary)).flat(); + return words.map((word: string) => word.substring(0, 1).toLocaleUpperCase() + word.substring(1)) + .join(''); + } +} + export class KebabCaseAction extends AbstractCaseAction { public static isSupported(): boolean { @@ -1257,6 +1286,9 @@ if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters. if (CamelCaseAction.wordBoundary.isSupported()) { registerEditorAction(CamelCaseAction); } +if (PascalCaseAction.wordBoundary.isSupported()) { + registerEditorAction(PascalCaseAction); +} if (TitleCaseAction.titleBoundary.isSupported()) { registerEditorAction(TitleCaseAction); } diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 3df2a1f682c..795bf69bec5 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -12,7 +12,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { CamelCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; +import { CamelCaseAction, PascalCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -935,6 +935,74 @@ suite('Editor Contrib - Line Operations', () => { assertSelection(editor, new Selection(11, 1, 11, 11)); } ); + + withTestCodeEditor( + [ + 'hello world', + 'öçşğü', + 'parseHTMLString', + 'getElementById', + 'PascalCase', + 'öçşÖÇŞğüĞÜ', + 'audioConverter.convertM4AToMP3();', + 'Capital_Snake_Case', + 'parseHTML4String', + 'Kebab-Case', + ], {}, (editor) => { + const model = editor.getModel()!; + const pascalCaseAction = new PascalCaseAction(); + + editor.setSelection(new Selection(1, 1, 1, 12)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(1), 'HelloWorld'); + assertSelection(editor, new Selection(1, 1, 1, 11)); + + editor.setSelection(new Selection(2, 1, 2, 6)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(2), 'Öçşğü'); + assertSelection(editor, new Selection(2, 1, 2, 6)); + + editor.setSelection(new Selection(3, 1, 3, 16)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(3), 'ParseHTMLString'); + assertSelection(editor, new Selection(3, 1, 3, 16)); + + editor.setSelection(new Selection(4, 1, 4, 15)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(4), 'GetElementById'); + assertSelection(editor, new Selection(4, 1, 4, 15)); + + editor.setSelection(new Selection(5, 1, 5, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(5), 'PascalCase'); + assertSelection(editor, new Selection(5, 1, 5, 11)); + + editor.setSelection(new Selection(6, 1, 6, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(6), 'ÖçşÖÇŞğüĞÜ'); + assertSelection(editor, new Selection(6, 1, 6, 11)); + + editor.setSelection(new Selection(7, 1, 7, 34)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(7), 'AudioConverter.ConvertM4AToMP3();'); + assertSelection(editor, new Selection(7, 1, 7, 34)); + + editor.setSelection(new Selection(8, 1, 8, 19)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(8), 'CapitalSnakeCase'); + assertSelection(editor, new Selection(8, 1, 8, 17)); + + editor.setSelection(new Selection(9, 1, 9, 17)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(9), 'ParseHTML4String'); + assertSelection(editor, new Selection(9, 1, 9, 17)); + + editor.setSelection(new Selection(10, 1, 10, 11)); + executeAction(pascalCaseAction, editor); + assert.strictEqual(model.getLineContent(10), 'KebabCase'); + assertSelection(editor, new Selection(10, 1, 10, 10)); + } + ); }); suite('DeleteAllRightAction', () => { From 6350f21dfe5614d722def7e259103e846ef5bf2c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 10:07:28 -0800 Subject: [PATCH 142/753] Reveal link source while navigating detected links Part of #206127 --- .../terminal/browser/media/terminal.css | 5 + .../contrib/terminal/browser/terminal.ts | 6 +- .../browser/xterm/markNavigationAddon.ts | 97 +++++++++++++++++-- .../browser/terminal.links.contribution.ts | 2 +- .../links/browser/terminalLinkQuickpick.ts | 46 ++++++++- 5 files changed, 145 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 488815cf258..e30f5dd92d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -474,6 +474,11 @@ pointer-events: none; } +.terminal-range-highlight { + outline: 1px solid var(--vscode-focusBorder); + pointer-events: none; +} + .terminal-command-guide { left: 0; border: 1.5px solid #ffffff; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 9a1bd1ab1b7..d8d7ebeb111 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -23,7 +23,7 @@ import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/termi import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfiguration, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import type { IMarker, ITheme, Terminal as RawXtermTerminal, IBufferRange } from '@xterm/xterm'; import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -118,8 +118,12 @@ export interface IMarkTracker { scrollToLine(line: number, position: ScrollPosition): void; revealCommand(command: ITerminalCommand, position?: ScrollPosition): void; + revealRange(range: IBufferRange): void; registerTemporaryDecoration(marker: IMarker, endMarker: IMarker | undefined, showOutline: boolean): void; showCommandGuide(command: ITerminalCommand | undefined): void; + + saveScrollState(): void; + restoreScrollState(): void; } export interface ITerminalGroup { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index 499e1d2f57d..bb6907c4e78 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { Disposable, DisposableStore, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMarkTracker } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import type { Terminal, IMarker, ITerminalAddon, IDecoration } from '@xterm/xterm'; +import type { Terminal, IMarker, ITerminalAddon, IDecoration, IBufferRange } from '@xterm/xterm'; import { timeout } from 'vs/base/common/async'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -24,6 +24,11 @@ export const enum ScrollPosition { Middle } +interface IScrollToMarkerOptions { + hideDecoration?: boolean; + bufferRange?: IBufferRange; +} + export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITerminalAddon { private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; @@ -219,7 +224,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } } - private _scrollToMarker(start: IMarker | number, position: ScrollPosition, end?: IMarker | number, hideDecoration?: boolean): void { + private _scrollToMarker(start: IMarker | number, position: ScrollPosition, end?: IMarker | number, options?: IScrollToMarkerOptions): void { if (!this._terminal) { return; } @@ -227,8 +232,12 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe const line = this.getTargetScrollLine(toLineIndex(start), position); this._terminal.scrollToLine(line); } - if (!hideDecoration) { - this.registerTemporaryDecoration(start, end, true); + if (!options?.hideDecoration) { + if (options?.bufferRange) { + this._highlightBufferRange(options.bufferRange); + } else { + this.registerTemporaryDecoration(start, end, true); + } } } @@ -260,6 +269,16 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe ); } + revealRange(range: IBufferRange): void { + // TODO: Allow room for sticky scroll + this._scrollToMarker( + range.start.y - 1, + ScrollPosition.Middle, + range.end.y - 1, + { bufferRange: range } + ); + } + showCommandGuide(command: ITerminalCommand | undefined): void { if (!this._terminal) { return; @@ -314,6 +333,72 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } } + + private _scrollState: { viewportY: number } | undefined; + + saveScrollState(): void { + this._scrollState = { viewportY: this._terminal?.buffer.active.viewportY ?? 0 }; + } + + restoreScrollState(): void { + if (this._scrollState && this._terminal) { + this._terminal.scrollToLine(this._scrollState.viewportY); + this._scrollState = undefined; + } + } + + private _highlightBufferRange(range: IBufferRange): void { + if (!this._terminal) { + return; + } + + // TODO: Save original scroll point + + this._resetNavigationDecorations(); + const startLine = range.start.y; + const decorationCount = range.end.y - range.start.y + 1; + for (let i = 0; i < decorationCount; i++) { + const decoration = this._terminal.registerDecoration({ + marker: this._createMarkerForOffset(startLine - 1, i), + x: range.start.x - 1, + width: (range.end.x - 1) - (range.start.x - 1) + 1, + overviewRulerOptions: undefined + }); + if (decoration) { + this._navigationDecorations?.push(decoration); + let renderedElement: HTMLElement | undefined; + + decoration.onRender(element => { + if (!renderedElement) { + renderedElement = element; + // if (i === 0) { + // element.classList.add('top'); + // } + // if (i === decorationCount - 1) { + // element.classList.add('bottom'); + // } + element.classList.add('terminal-range-highlight'); + } + if (this._terminal?.element) { + // element.style.marginLeft = `-${getWindow(this._terminal.element).getComputedStyle(this._terminal.element).paddingLeft}`; + } + }); + // TODO: Scroll may be under sticky scroll + + // TODO: This is not efficient for a large decorationCount + decoration.onDispose(() => { this._navigationDecorations = this._navigationDecorations?.filter(d => d !== decoration); }); + // Number picked to align with symbol highlight in the editor + // if (showOutline) { + // timeout(350).then(() => { + // if (renderedElement) { + // renderedElement.classList.remove('terminal-scroll-highlight-outline'); + // } + // }); + // } + } + } + } + registerTemporaryDecoration(marker: IMarker | number, endMarker: IMarker | number | undefined, showOutline: boolean): void { if (!this._terminal) { return; @@ -373,7 +458,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } getTargetScrollLine(line: number, position: ScrollPosition): number { - // Middle is treated at 1/4 of the viewport's size because context below is almost always + // Middle is treated as 1/4 of the viewport's size because context below is almost always // more important than context above in the terminal. if (this._terminal && position === ScrollPosition.Middle) { return Math.max(line - Math.floor(this._terminal.rows / 4), 0); @@ -397,7 +482,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return; } const endMarker = endMarkerId ? detectionCapability.getMark(endMarkerId) : startMarker; - this._scrollToMarker(startMarker, ScrollPosition.Top, endMarker, !highlight); + this._scrollToMarker(startMarker, ScrollPosition.Top, endMarker, { hideDecoration: !highlight }); } selectToPreviousMark(): void { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 0327aa48168..2a0269486a3 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -83,7 +83,7 @@ class TerminalLinkContribution extends DisposableStore implements ITerminalContr }); } const links = await this._getLinks(); - return await this._terminalLinkQuickpick.show(links); + return await this._terminalLinkQuickpick.show(this._instance, links); } private async _getLinks(): Promise<{ viewport: IDetectedLinks; all: Promise }> { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 2c042949a9d..f496f89cbfc 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { QuickPickItem, IQuickInputService, IQuickPickItem, QuickInputHideReason } from 'vs/platform/quickinput/common/quickInput'; import { IDetectedLinks } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkManager'; -import { TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalLinkQuickPickEvent, type IDetachedTerminalInstance, type ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { ILink } from '@xterm/xterm'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; @@ -30,6 +30,8 @@ export class TerminalLinkQuickpick extends DisposableStore { private readonly _editorSequencer = new Sequencer(); private readonly _editorViewState: EditorViewState; + private _instance: ITerminalInstance | IDetachedTerminalInstance | undefined; + private readonly _onDidRequestMoreLinks = this.add(new Emitter()); readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; @@ -45,7 +47,9 @@ export class TerminalLinkQuickpick extends DisposableStore { this._editorViewState = new EditorViewState(_editorService); } - async show(links: { viewport: IDetectedLinks; all: Promise }): Promise { + async show(instance: ITerminalInstance | IDetachedTerminalInstance, links: { viewport: IDetectedLinks; all: Promise }): Promise { + this._instance = instance; + // Get raw link picks const wordPicks = links.viewport.wordLinks ? await this._generatePicks(links.viewport.wordLinks) : undefined; const filePicks = links.viewport.fileLinks ? await this._generatePicks(links.viewport.fileLinks) : undefined; @@ -122,6 +126,18 @@ export class TerminalLinkQuickpick extends DisposableStore { return new Promise(r => { disposables.add(pick.onDidHide(({ reason }) => { + + // Restore terminal scroll state + if (this._terminalScrollStateSaved) { + const markTracker = this._instance?.xterm?.markTracker; + if (markTracker) { + markTracker.restoreScrollState(); + // TODO: This name isn't great + markTracker.clearMarker(); + this._terminalScrollStateSaved = false; + } + } + // Restore view state upon cancellation if we changed it // but only when the picker was closed via explicit user // gesture and not e.g. when focus was lost because that @@ -208,11 +224,18 @@ export class TerminalLinkQuickpick extends DisposableStore { } private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { - if (!item || !('link' in item) || !item.link || !('uri' in item.link) || !item.link.uri) { + if (!item || !('link' in item) || !item.link) { return; } + // Any link can be previewed in the termninal const link = item.link; + this._previewItemInTerminal(link); + + if (!('uri' in link) || !link.uri) { + return; + } + if (link.type !== TerminalBuiltinLinkType.LocalFile) { return; } @@ -223,6 +246,10 @@ export class TerminalLinkQuickpick extends DisposableStore { return; } + this._previewItemInEditor(link); + } + + private _previewItemInEditor(link: TerminalLink) { const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); const selection = linkSuffix?.row === undefined ? undefined : { startLineNumber: linkSuffix.row ?? 1, @@ -245,6 +272,19 @@ export class TerminalLinkQuickpick extends DisposableStore { } }); } + + private _terminalScrollStateSaved: boolean = false; + private _previewItemInTerminal(link: ILink) { + const xterm = this._instance?.xterm; + if (!xterm) { + return; + } + if (!this._terminalScrollStateSaved) { + xterm.markTracker.saveScrollState(); + this._terminalScrollStateSaved = true; + } + xterm.markTracker.revealRange(link.range); + } } export interface ITerminalLinkQuickPickItem extends IQuickPickItem { From 920a3a701eeb5830d94f00813d444873599e44ea Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 10:56:20 -0800 Subject: [PATCH 143/753] Pick up latest TS for building VS Code (#206264) --- build/azure-pipelines/common/publish.js | 2 +- build/lib/compilation.js | 2 +- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index e6b24921ac1..b690ae5c792 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -595,7 +595,7 @@ async function main() { operations.push({ name: artifact.name, operation }); resultPromise = Promise.allSettled(operations.map(o => o.operation)); } - await new Promise(c => setTimeout(c, 10000)); + await new Promise(c => setTimeout(c, 10_000)); } console.log(`Found all ${done.size + processing.size} artifacts, waiting for ${processing.size} artifacts to finish publishing...`); const artifactsInProgress = operations.filter(o => processing.has(o.name)); diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 35bc464d34a..e7a460de7d0 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -99,7 +99,7 @@ function transpileTask(src, out, swc) { exports.transpileTask = transpileTask; function compileTask(src, out, build, options = {}) { const task = () => { - if (os.totalmem() < 4000000000) { + if (os.totalmem() < 4_000_000_000) { throw new Error('compilation requires 4GB of RAM'); } const compile = createCompile(src, build, true, false); diff --git a/package.json b/package.json index 2f127741d55..30ed28ac516 100644 --- a/package.json +++ b/package.json @@ -209,7 +209,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.4.0-dev.20240206", + "typescript": "^5.5.0-dev.20240226", "typescript-formatter": "7.1.0", "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 98368474549..056c1e1bce0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9636,10 +9636,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.4.0-dev.20240206: - version "5.4.0-dev.20240206" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.0-dev.20240206.tgz#75755acb115e1176958d511d11eb018694e74987" - integrity sha512-8P1XYxDbG/AyGE5tB8+JpeiQfS5ye1BTvIVDZaHhoK9nJuCn4nkB0L66lvfwYB+46hA4rLo3vE3WkIToSYtqQA== +typescript@^5.5.0-dev.20240226: + version "5.5.0-dev.20240226" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.0-dev.20240226.tgz#b571688666f07e4d7db4c9863f3ee1401e161a7a" + integrity sha512-mLY9/pjzSCr7JLkMKHS3KQUKX+LPO9WWjiR+mRcWKcskSdMBZ0j1TPhk/zUyuBklOf3YX4orkvamNiZWZEK0CQ== typical@^4.0.0: version "4.0.0" From a2030c81feeec0d803dfe60e5b4a113c3c7b47e1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:10:01 -0800 Subject: [PATCH 144/753] Update issue notebook milestones (#206278) --- .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/endgame.github-issues | 2 +- .vscode/notebooks/my-endgame.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 2a6f3ec1bc5..6b8a385ec42 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"February 2024\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"March 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index ee1084be56d..750e53e4b26 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"March 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index a286082c738..ab59f23283f 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"February 2024\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"March 2024\"\n\n$MINE=assignee:@me" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 2a3f9159703..b23dacf87e4 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"February 2024\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"March 2024\"\n" }, { "kind": 1, From ead787f6f01630a1261fe80abe87242c9170ebc6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 11:13:02 -0800 Subject: [PATCH 145/753] fix issue --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 180d51c1549..aa43972ef64 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -9,7 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/terminalChatWidget'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; From c9e61431bae75f54303d2c00b83c6ed6b8ff2f3c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:17:28 -0800 Subject: [PATCH 146/753] Show all links if resolved within 500ms Fixes #206280 --- .../links/browser/terminalLinkQuickpick.ts | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index f496f89cbfc..544612bcb53 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -14,7 +14,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import type { TerminalLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLink'; -import { Sequencer } from 'vs/base/common/async'; +import { Sequencer, timeout } from 'vs/base/common/async'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -50,11 +50,17 @@ export class TerminalLinkQuickpick extends DisposableStore { async show(instance: ITerminalInstance | IDetachedTerminalInstance, links: { viewport: IDetectedLinks; all: Promise }): Promise { this._instance = instance; + // Allow all links a small amount of time to elapse to finish, if this is not done in this + // time they will be loaded upon the first filter. + const result = await Promise.race([links.all, timeout(500)]); + const usingAllLinks = typeof result === 'object'; + const resolvedLinks = usingAllLinks ? result : links.viewport; + // Get raw link picks - const wordPicks = links.viewport.wordLinks ? await this._generatePicks(links.viewport.wordLinks) : undefined; - const filePicks = links.viewport.fileLinks ? await this._generatePicks(links.viewport.fileLinks) : undefined; - const folderPicks = links.viewport.folderLinks ? await this._generatePicks(links.viewport.folderLinks) : undefined; - const webPicks = links.viewport.webLinks ? await this._generatePicks(links.viewport.webLinks) : undefined; + const wordPicks = resolvedLinks.wordLinks ? await this._generatePicks(resolvedLinks.wordLinks) : undefined; + const filePicks = resolvedLinks.fileLinks ? await this._generatePicks(resolvedLinks.fileLinks) : undefined; + const folderPicks = resolvedLinks.folderLinks ? await this._generatePicks(resolvedLinks.folderLinks) : undefined; + const webPicks = resolvedLinks.webLinks ? await this._generatePicks(resolvedLinks.webLinks) : undefined; const picks: LinkQuickPickItem[] = []; if (webPicks) { @@ -88,36 +94,38 @@ export class TerminalLinkQuickpick extends DisposableStore { // ASAP with only the viewport entries. let accepted = false; const disposables = new DisposableStore(); - disposables.add(Event.once(pick.onDidChangeValue)(async () => { - const allLinks = await links.all; - if (accepted) { - return; - } - const wordIgnoreLinks = [...(allLinks.fileLinks ?? []), ...(allLinks.folderLinks ?? []), ...(allLinks.webLinks ?? [])]; - - const wordPicks = allLinks.wordLinks ? await this._generatePicks(allLinks.wordLinks, wordIgnoreLinks) : undefined; - const filePicks = allLinks.fileLinks ? await this._generatePicks(allLinks.fileLinks) : undefined; - const folderPicks = allLinks.folderLinks ? await this._generatePicks(allLinks.folderLinks) : undefined; - const webPicks = allLinks.webLinks ? await this._generatePicks(allLinks.webLinks) : undefined; - const picks: LinkQuickPickItem[] = []; - if (webPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.urlLinks', "Url") }); - picks.push(...webPicks); - } - if (filePicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.localFileLinks', "File") }); - picks.push(...filePicks); - } - if (folderPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.localFolderLinks', "Folder") }); - picks.push(...folderPicks); - } - if (wordPicks) { - picks.push({ type: 'separator', label: localize('terminal.integrated.searchLinks', "Workspace Search") }); - picks.push(...wordPicks); - } - pick.items = picks; - })); + if (!usingAllLinks) { + disposables.add(Event.once(pick.onDidChangeValue)(async () => { + const allLinks = await links.all; + if (accepted) { + return; + } + const wordIgnoreLinks = [...(allLinks.fileLinks ?? []), ...(allLinks.folderLinks ?? []), ...(allLinks.webLinks ?? [])]; + + const wordPicks = allLinks.wordLinks ? await this._generatePicks(allLinks.wordLinks, wordIgnoreLinks) : undefined; + const filePicks = allLinks.fileLinks ? await this._generatePicks(allLinks.fileLinks) : undefined; + const folderPicks = allLinks.folderLinks ? await this._generatePicks(allLinks.folderLinks) : undefined; + const webPicks = allLinks.webLinks ? await this._generatePicks(allLinks.webLinks) : undefined; + const picks: LinkQuickPickItem[] = []; + if (webPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.urlLinks', "Url") }); + picks.push(...webPicks); + } + if (filePicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.localFileLinks', "File") }); + picks.push(...filePicks); + } + if (folderPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.localFolderLinks', "Folder") }); + picks.push(...folderPicks); + } + if (wordPicks) { + picks.push({ type: 'separator', label: localize('terminal.integrated.searchLinks', "Workspace Search") }); + picks.push(...wordPicks); + } + pick.items = picks; + })); + } disposables.add(pick.onDidChangeActive(async () => { const [item] = pick.activeItems; From 754dc0c68a7f1137695f1774425ed8bcb873c2b8 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 26 Feb 2024 11:25:01 -0800 Subject: [PATCH 147/753] Run in Section for notebook sticky scroll context menu (#205307) * breadcrumbs in nb sticky context menu * run section nb sticky scroll context menu * implement actionRunner for run in section ctx menu * use context for run in section args * nit + toggle verbage fix --- .../parts/editor/breadcrumbsControl.ts | 5 +- .../viewParts/notebookEditorStickyScroll.ts | 74 ++++++++++++++++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 00e7153c46f..b97793d6ddd 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -612,13 +612,14 @@ registerAction2(class ToggleBreadcrumb extends Action2 { toggled: { condition: ContextKeyExpr.equals('config.breadcrumbs.enabled', true), title: localize('cmd.toggle2', "Breadcrumbs"), - mnemonicTitle: localize({ key: 'miBreadcrumbs2', comment: ['&& denotes a mnemonic'] }, "&&Breadcrumbs") + mnemonicTitle: localize({ key: 'miBreadcrumbs2', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs") }, menu: [ { id: MenuId.CommandPalette }, { id: MenuId.MenubarAppearanceMenu, group: '4_editor', order: 2 }, { id: MenuId.NotebookToolbar, group: 'notebookLayout', order: 2 }, - { id: MenuId.StickyScrollContext } + { id: MenuId.StickyScrollContext }, + { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 } ] }); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index a02e1bb2010..1fb8e60d3db 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -34,17 +34,21 @@ export class ToggleNotebookStickyScroll extends Action2 { id: 'notebook.action.toggleNotebookStickyScroll', title: { ...localize2('toggleStickyScroll', "Toggle Notebook Sticky Scroll"), - mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), }, category: Categories.View, toggled: { condition: ContextKeyExpr.equals('config.notebook.stickyScroll.enabled', true), title: localize('notebookStickyScroll', "Notebook Sticky Scroll"), - mnemonicTitle: localize({ key: 'miNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), }, menu: [ { id: MenuId.CommandPalette }, - { id: MenuId.NotebookStickyScrollContext } + { + id: MenuId.NotebookStickyScrollContext, + group: 'notebookView', + order: 2 + } ] }); } @@ -56,6 +60,51 @@ export class ToggleNotebookStickyScroll extends Action2 { } } +type RunInSectionContext = { + target: HTMLElement; + currentStickyLines: Map; + notebookEditor: INotebookEditor; +}; + +export class RunInSectionStickyScroll extends Action2 { + constructor() { + super({ + id: 'notebook.action.runInSection', + title: { + ...localize2('runInSectionStickyScroll', "Run Section"), + mnemonicTitle: localize({ key: 'mirunInSectionStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Run Section"), + }, + menu: [ + { + id: MenuId.NotebookStickyScrollContext, + group: 'notebookExecution', + order: 1 + } + ] + }); + } + + override async run(accessor: ServicesAccessor, context: RunInSectionContext, ...args: any[]): Promise { + const selectedElement = context.target.parentElement; + const stickyLines: Map = context.currentStickyLines; + + const selectedOutlineEntry = Array.from(stickyLines.values()).find(entry => entry.line.element.contains(selectedElement))?.line.entry; + if (!selectedOutlineEntry) { + return; + } + + const flatList: OutlineEntry[] = []; + selectedOutlineEntry.asFlatList(flatList); + + const cellViewModels = flatList.map(entry => entry.cell); + const notebookEditor: INotebookEditor = context.notebookEditor; + notebookEditor.executeNotebookCells(cellViewModels); + } +} + export class NotebookStickyLine extends Disposable { constructor( public readonly element: HTMLElement, @@ -78,14 +127,6 @@ export class NotebookStickyLine extends Disposable { } })); - // folding icon hovers - // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OVER, () => { - // this.foldingIcon.setVisible(true); - // })); - // this._register(DOM.addDisposableListener(this.element, DOM.EventType.MOUSE_OUT, () => { - // this.foldingIcon.setVisible(false); - // })); - } private toggleFoldRange(currentState: CellFoldingState) { @@ -145,7 +186,6 @@ export class NotebookStickyScroll extends Disposable { private readonly _onDidChangeNotebookStickyScroll = this._register(new Emitter()); readonly onDidChangeNotebookStickyScroll: Event = this._onDidChangeNotebookStickyScroll.event; - getDomNode(): HTMLElement { return this.domNode; } @@ -205,9 +245,17 @@ export class NotebookStickyScroll extends Disposable { private onContextMenu(e: MouseEvent) { const event = new StandardMouseEvent(DOM.getWindow(this.domNode), e); + + const context: RunInSectionContext = { + target: event.target, + currentStickyLines: this.currentStickyLines, + notebookEditor: this.notebookEditor, + }; + this._contextMenuService.showContextMenu({ menuId: MenuId.NotebookStickyScrollContext, getAnchor: () => event, + menuActionOptions: { shouldForwardArgs: true, arg: context }, }); } @@ -384,6 +432,7 @@ export class NotebookStickyScroll extends Disposable { stickyHeader.innerText = entry.label; stickyElement.append(stickyFoldingIcon.domNode, stickyHeader); + return new NotebookStickyLine(stickyElement, stickyFoldingIcon, stickyHeader, entry, notebookEditor); } @@ -490,3 +539,4 @@ export function computeContent(notebookEditor: INotebookEditor, notebookCellList } registerAction2(ToggleNotebookStickyScroll); +registerAction2(RunInSectionStickyScroll); From d4b102e34470574cf8c6eedb7bb6895268674ba5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:28:42 -0800 Subject: [PATCH 148/753] Always scroll when sticky scroll is enabled --- .../browser/xterm/markNavigationAddon.ts | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index bb6907c4e78..aae4ac6f275 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -13,6 +13,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { getWindow } from 'vs/base/browser/dom'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; enum Boundary { Top, @@ -26,6 +28,8 @@ export const enum ScrollPosition { interface IScrollToMarkerOptions { hideDecoration?: boolean; + /** Scroll even if the line is within the viewport */ + forceScroll?: boolean; bufferRange?: IBufferRange; } @@ -48,6 +52,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe constructor( private readonly _capabilities: ITerminalCapabilityStore, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService ) { super(); @@ -228,7 +233,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe if (!this._terminal) { return; } - if (!this._isMarkerInViewport(this._terminal, start)) { + if (!this._isMarkerInViewport(this._terminal, start) || options?.forceScroll) { const line = this.getTargetScrollLine(toLineIndex(start), position); this._terminal.scrollToLine(line); } @@ -270,12 +275,15 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } revealRange(range: IBufferRange): void { - // TODO: Allow room for sticky scroll this._scrollToMarker( range.start.y - 1, ScrollPosition.Middle, range.end.y - 1, - { bufferRange: range } + { + bufferRange: range, + // Ensure scroll shows the line when sticky scroll is enabled + forceScroll: !!this._configurationService.getValue(TerminalSettingId.StickyScrollEnabled) + } ); } @@ -352,8 +360,6 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return; } - // TODO: Save original scroll point - this._resetNavigationDecorations(); const startLine = range.start.y; const decorationCount = range.end.y - range.start.y + 1; @@ -371,30 +377,10 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe decoration.onRender(element => { if (!renderedElement) { renderedElement = element; - // if (i === 0) { - // element.classList.add('top'); - // } - // if (i === decorationCount - 1) { - // element.classList.add('bottom'); - // } element.classList.add('terminal-range-highlight'); } - if (this._terminal?.element) { - // element.style.marginLeft = `-${getWindow(this._terminal.element).getComputedStyle(this._terminal.element).paddingLeft}`; - } }); - // TODO: Scroll may be under sticky scroll - - // TODO: This is not efficient for a large decorationCount decoration.onDispose(() => { this._navigationDecorations = this._navigationDecorations?.filter(d => d !== decoration); }); - // Number picked to align with symbol highlight in the editor - // if (showOutline) { - // timeout(350).then(() => { - // if (renderedElement) { - // renderedElement.classList.remove('terminal-scroll-highlight-outline'); - // } - // }); - // } } } } From d1c62c90be88d326d1b8ea73c7bb7a9b6fb550ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:29:19 -0800 Subject: [PATCH 149/753] Add inline code for a few special character in docs (#206277) --- src/vscode-dts/vscode.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index a8fef891e89..614dcc68cf1 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1356,7 +1356,7 @@ declare module 'vscode' { export interface TextEditorEdit { /** * Replace a certain text region with a new value. - * You can use \r\n or \n in `value` and they will be normalized to the current {@link TextDocument document}. + * You can use `\r\n` or `\n` in `value` and they will be normalized to the current {@link TextDocument document}. * * @param location The range this operation should remove. * @param value The new text this operation should insert after removing `location`. @@ -1365,7 +1365,7 @@ declare module 'vscode' { /** * Insert text at a location. - * You can use \r\n or \n in `value` and they will be normalized to the current {@link TextDocument document}. + * You can use `\r\n` or `\n` in `value` and they will be normalized to the current {@link TextDocument document}. * Although the equivalent text edit can be made with {@link TextEditorEdit.replace replace}, `insert` will produce a different resulting selection (it will get moved). * * @param location The position where the new text should be inserted. @@ -7335,7 +7335,7 @@ declare module 'vscode' { * * @param text The text to send. * @param shouldExecute Indicates that the text being sent should be executed rather than just inserted in the terminal. - * The character(s) added are \n or \r\n, depending on the platform. This defaults to `true`. + * The character(s) added are `\n` or `\r\n`, depending on the platform. This defaults to `true`. */ sendText(text: string, shouldExecute?: boolean): void; From de7da9f4af0c7c440bddd46a6fb0a47781fa6371 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 26 Feb 2024 11:29:37 -0800 Subject: [PATCH 150/753] Improve creation of text models for chat code blocks (#205943) * Improve creation of text models for chat code blocks Refactors the chat code block logic to better support cross code blocks IntelliSense. Previously we only created text models for the visible editors in chat. With this new approach, we instead create a unique text model for each code block in the conversation. This allows us our IntelliSense features to work even if a code block is not visible in chat Also uses this as a change to remove some duplicate I introduced to support local file editors in chat Still a draft as the text model creation should be moved out of the chat list renderer * Move model updating logic into view model * Small cleanup --- .../contrib/chat/browser/chatListRenderer.ts | 115 +++--- .../contrib/chat/browser/chatWidget.ts | 65 +++- .../contrib/chat/browser/codeBlockPart.ts | 337 +++++------------- .../contrib/chat/common/chatViewModel.ts | 99 ++++- .../chat/common/codeBlockModelCollection.ts | 53 +++ .../inlineChat/browser/inlineChatWidget.ts | 7 +- 6 files changed, 360 insertions(+), 316 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index c0f1827014a..93b651276e5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -21,7 +21,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; @@ -30,10 +30,9 @@ import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; import { Range } from 'vs/editor/common/core/range'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -41,7 +40,6 @@ import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { FileKind, FileType } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -56,9 +54,9 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { ChatMarkdownDecorationsRenderer, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { ChatMarkdownDecorationsRenderer, IMarkdownVulnerability, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ChatCodeBlockContentProvider, ICodeBlockData, ICodeBlockPart, LocalFileCodeBlockPart, SimpleCodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { ChatCodeBlockContentProvider, CodeBlockPart, ICodeBlockData, ICodeBlockPart, localFileLanguageId, parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -68,6 +66,7 @@ import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownR import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { CodeBlockModelCollection } from '../common/codeBlockModelCollection'; const $ = dom.$; @@ -133,47 +132,30 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer => { - if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { - return null; - } - const block = this._editorPool.find(input.resource); - if (!block) { - return null; - } - if (input.options?.selection) { - block.editor.setSelection({ - startLineNumber: input.options.selection.startLineNumber, - startColumn: input.options.selection.startColumn, - endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, - endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn - }); - } - return block.editor; - })); - this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? true; this._register(configService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('chat.experimental.usedReferences')) { @@ -186,6 +168,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - let data: ICodeBlockData; + const index = codeBlockIndex++; + let textModel: Promise>; + let range: Range | undefined; + let vulns: readonly IMarkdownVulnerability[] | undefined; if (equalsIgnoreCase(languageId, localFileLanguageId)) { try { const parsedBody = parseLocalFileData(text); - data = { type: 'localFile', uri: parsedBody.uri, range: parsedBody.range && Range.lift(parsedBody.range), codeBlockIndex: codeBlockIndex++, element, hideToolbar: false, parentContextKeyService: templateData.contextKeyService }; + range = parsedBody.range && Range.lift(parsedBody.range); + textModel = this.textModelService.createModelReference(parsedBody.uri); } catch (e) { - console.error(e); return $('div'); } } else { - const vulns = extractVulnerabilitiesFromText(text); - const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; - data = { type: 'code', languageId, text: vulns.newText, codeBlockIndex: codeBlockIndex++, element, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns: vulns.vulnerabilities }; + const blockModel = this.codeBlockModelCollection.get(element.id, index); + if (!blockModel) { + console.error('Trying to render code block without model', element.id, index); + return $('div'); + } + + textModel = blockModel; + const extractedVulns = extractVulnerabilitiesFromText(text); + vulns = extractedVulns.vulnerabilities; + textModel.then(ref => ref.object.textEditorModel.setValue(extractedVulns.newText)); } - const ref = this.renderCodeBlock(data); + const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; + const ref = this.renderCodeBlock({ languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: templateData.contextKeyService, vulns }); // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) // not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render) @@ -899,15 +896,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByEditorUri.delete(ref.object.uri))); + if (ref.object.uri) { + const uri = ref.object.uri; + this.codeBlocksByEditorUri.set(uri, info); + disposables.add(toDisposable(() => this.codeBlocksByEditorUri.delete(uri))); + } } orderedDisposablesList.push(ref); return ref.object.element; @@ -933,7 +933,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const ref = this._editorPool.get(data); + const ref = this._editorPool.get(); const editorInfo = ref.object; editorInfo.render(data, this._currentLayoutWidth); @@ -1068,41 +1068,28 @@ interface IDisposableReference extends IDisposable { isStale: () => boolean; } -class EditorPool extends Disposable { +export class EditorPool extends Disposable { - private readonly _simpleEditorPool: ResourcePool; - private readonly _localFileEditorPool: ResourcePool; + private readonly _pool: ResourcePool; - public *inUse(): Iterable { - yield* this._simpleEditorPool.inUse; - yield* this._localFileEditorPool.inUse; + public inUse(): Iterable { + return this._pool.inUse; } constructor( - private readonly options: ChatEditorOptions, + options: ChatEditorOptions, delegate: IChatRendererDelegate, overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this._simpleEditorPool = this._register(new ResourcePool(() => { - return this.instantiationService.createInstance(SimpleCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); + this._pool = this._register(new ResourcePool(() => { + return instantiationService.createInstance(CodeBlockPart, options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); })); - this._localFileEditorPool = this._register(new ResourcePool(() => { - return this.instantiationService.createInstance(LocalFileCodeBlockPart, this.options, MenuId.ChatCodeBlock, delegate, overflowWidgetsDomNode); - })); - } - - get(data: ICodeBlockData): IDisposableReference { - return this.getFromPool(data.type === 'localFile' ? this._localFileEditorPool : this._simpleEditorPool); - } - - find(resource: URI): SimpleCodeBlockPart | undefined { - return Array.from(this._simpleEditorPool.inUse).find(part => part.uri?.toString() === resource.toString()); } - private getFromPool(pool: ResourcePool): IDisposableReference { - const codeBlock = pool.get(); + get(): IDisposableReference { + const codeBlock = this._pool.get(); let stale = false; return { object: codeBlock, @@ -1110,7 +1097,7 @@ class EditorPool extends Disposable { dispose: () => { codeBlock.reset(); stale = true; - pool.release(codeBlock); + this._pool.release(codeBlock); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index ab61b6bde4f..b2e1680a28c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -5,37 +5,41 @@ import * as dom from 'vs/base/browser/dom'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { disposableTimeout } from 'vs/base/common/async'; +import { disposableTimeout, timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/chat'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; 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 { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const $ = dom.$; @@ -98,6 +102,7 @@ export class ChatWidget extends Disposable implements IChatWidget { private tree!: WorkbenchObjectTree; private renderer!: ChatListItemRenderer; + private readonly _codeBlockModelCollection: CodeBlockModelCollection; private inputPart!: ChatInputPart; private editorOptions!: ChatEditorOptions; @@ -149,6 +154,7 @@ export class ChatWidget extends Disposable implements IChatWidget { readonly viewContext: IChatWidgetViewContext, private readonly viewOptions: IChatWidgetViewOptions, private readonly styles: IChatWidgetStyles, + @ICodeEditorService codeEditorService: ICodeEditorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IChatService private readonly chatService: IChatService, @@ -158,13 +164,51 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, - @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService, ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); this._register((chatWidgetService as ChatWidgetService).register(this)); + + this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); + + this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise => { + if (input.resource.scheme !== Schemas.vscodeChatCodeBlock) { + return null; + } + + const responseId = input.resource.path.split('/').at(1); + if (!responseId) { + return null; + } + + const item = this.viewModel?.getItems().find(item => item.id === responseId); + if (!item) { + return null; + } + + this.reveal(item); + + await timeout(0); // wait for list to actually render + + for (const editor of this.renderer.editorsInUse() ?? []) { + if (editor.uri?.toString() === input.resource.toString()) { + const inner = editor.editor; + if (input.options?.selection) { + inner.setSelection({ + startLineNumber: input.options.selection.startLineNumber, + startColumn: input.options.selection.startColumn, + endLineNumber: input.options.selection.startLineNumber ?? input.options.selection.endLineNumber, + endColumn: input.options.selection.startColumn ?? input.options.selection.endColumn + }); + } + return inner; + } + } + return null; + })); } get supportsFileReferences(): boolean { @@ -340,6 +384,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.editorOptions, options, rendererDelegate, + this._codeBlockModelCollection, overflowWidgetsContainer, )); this._register(this.renderer.onDidClickFollowup(item => { @@ -490,8 +535,10 @@ export class ChatWidget extends Disposable implements IChatWidget { throw new Error('Call render() before setModel()'); } + this._codeBlockModelCollection.clear(); + this.container.setAttribute('data-session-id', model.sessionId); - this.viewModel = this.instantiationService.createInstance(ChatViewModel, model); + this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection); this.viewModelDisposables.add(this.viewModel.onDidChange(e => { this.requestInProgress.set(this.viewModel!.requestInProgress); this.onDidChangeItems(); @@ -757,6 +804,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.saveState(); return { inputValue: this.getInput(), inputState: this.collectInputState() }; } + + } export class ChatWidgetService implements IChatWidgetService { diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 8669cadcce7..b6d4500e448 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -9,18 +9,15 @@ import * as dom from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -50,27 +47,19 @@ import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/ const $ = dom.$; -interface ICodeBlockDataCommon { - codeBlockIndex: number; - element: unknown; - parentContextKeyService?: IContextKeyService; - hideToolbar?: boolean; -} +export interface ICodeBlockData { + readonly codeBlockIndex: number; + readonly element: unknown; -export interface ISimpleCodeBlockData extends ICodeBlockDataCommon { - type: 'code'; - text: string; - languageId: string; - vulns?: IMarkdownVulnerability[]; -} + readonly textModel: Promise>; + readonly languageId: string; -export interface ILocalFileCodeBlockData extends ICodeBlockDataCommon { - type: 'localFile'; - uri: URI; - range?: Range; -} + readonly vulns?: readonly IMarkdownVulnerability[]; + readonly range?: Range; -export type ICodeBlockData = ISimpleCodeBlockData | ILocalFileCodeBlockData; + readonly parentContextKeyService?: IContextKeyService; + readonly hideToolbar?: boolean; +} /** * Special markdown code block language id used to render a local file. @@ -118,19 +107,20 @@ export interface ICodeBlockActionContext { } -export interface ICodeBlockPart { +export interface ICodeBlockPart { + readonly editor: CodeEditorWidget; readonly onDidChangeContentHeight: Event; readonly element: HTMLElement; - readonly uri: URI; + readonly uri: URI | undefined; layout(width: number): void; - render(data: Data, width: number): Promise; + render(data: ICodeBlockData, width: number): Promise; focus(): void; reset(): unknown; dispose(): void; } const defaultCodeblockPadding = 10; -abstract class BaseCodeBlockPart extends Disposable implements ICodeBlockPart { +export class CodeBlockPart extends Disposable implements ICodeBlockPart { protected readonly _onDidChangeContentHeight = this._register(new Emitter()); public readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event; @@ -138,9 +128,12 @@ abstract class BaseCodeBlockPart extends Disposable protected readonly toolbar: MenuWorkbenchToolBar; private readonly contextKeyService: IContextKeyService; - abstract readonly uri: URI; public readonly element: HTMLElement; + private readonly vulnsButton: Button; + private readonly vulnsListElement: HTMLElement; + + private currentCodeBlockData: ICodeBlockData | undefined; private currentScrollWidth = 0; constructor( @@ -152,7 +145,7 @@ abstract class BaseCodeBlockPart extends Disposable @IContextKeyService contextKeyService: IContextKeyService, @IModelService protected readonly modelService: IModelService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(); this.element = $('.interactive-result-code-block'); @@ -173,6 +166,13 @@ abstract class BaseCodeBlockPart extends Disposable scrollbar: { alwaysConsumeMouseWheel: false }, + definitionLinkOpensInPeek: false, + gotoLocation: { + multiple: 'goto', + multipleDeclarations: 'goto', + multipleDefinitions: 'goto', + multipleImplementations: 'goto', + }, ariaLabel: localize('chat.codeBlockHelp', 'Code block'), overflowWidgetsDomNode, ...this.getEditorOptionsFromConfig(), @@ -187,6 +187,31 @@ abstract class BaseCodeBlockPart extends Disposable } })); + const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); + const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); + this.vulnsButton = new Button(vulnsHeaderElement, { + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSeparator: undefined, + supportIcons: true + }); + + this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); + + this.vulnsButton.onDidClick(() => { + const element = this.currentCodeBlockData!.element as IChatResponseViewModel; + element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; + this.vulnsButton.label = this.getVulnerabilitiesLabel(); + this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); + this._onDidChangeContentHeight.fire(); + // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); + }); + this._register(this.toolbar.onDidChangeDropdownVisibility(e => { toolbarElement.classList.toggle('force-visibility', e); })); @@ -229,7 +254,27 @@ abstract class BaseCodeBlockPart extends Disposable } } - protected abstract createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget; + get uri(): URI | undefined { + return this.editor.getModel()?.uri; + } + + private createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { + return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { + isSimpleWidget: false, + contributions: EditorExtensionsRegistry.getSomeEditorContributions([ + MenuPreventer.ID, + SelectionClipboardContributionID, + ContextMenuController.ID, + + WordHighlighterContribution.ID, + ViewportSemanticTokensContribution.ID, + BracketMatchingController.ID, + SmartSelectController.ID, + HoverController.ID, + GotoDefinitionAtPositionEditorContribution.ID, + ]) + })); + } focus(): void { this.editor.focus(); @@ -277,17 +322,22 @@ abstract class BaseCodeBlockPart extends Disposable this.updatePaddingForLayout(); } - protected getContentHeight() { + private getContentHeight() { + if (this.currentCodeBlockData?.range) { + const lineCount = this.currentCodeBlockData.range.endLineNumber - this.currentCodeBlockData.range.startLineNumber + 1; + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + return lineCount * lineHeight; + } return this.editor.getContentHeight(); } - async render(data: Data, width: number) { + async render(data: ICodeBlockData, width: number) { if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); } if (this.options.configuration.resultEditor.wordWrap === 'on') { - // Intialize the editor with the new proper width so that getContentHeight + // Initialize the editor with the new proper width so that getContentHeight // will be computed correctly in the next call to layout() this.layout(width); } @@ -302,102 +352,6 @@ abstract class BaseCodeBlockPart extends Disposable } else { dom.show(this.toolbar.getElement()); } - } - - protected abstract updateEditor(data: Data): void | Promise; - - reset() { - this.clearWidgets(); - } - - private clearWidgets() { - HoverController.get(this.editor)?.hideContentHover(); - } -} - - -export class SimpleCodeBlockPart extends BaseCodeBlockPart { - - private readonly vulnsButton: Button; - private readonly vulnsListElement: HTMLElement; - - private currentCodeBlockData: ISimpleCodeBlockData | undefined; - - private readonly textModel: Promise; - - private readonly _uri: URI; - - constructor( - options: ChatEditorOptions, - menuId: MenuId, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IModelService modelService: IModelService, - @ITextModelService textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService, - @IAccessibilityService accessibilityService: IAccessibilityService, - @ILanguageService private readonly languageService: ILanguageService, - ) { - super(options, menuId, delegate, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); - - const vulnsContainer = dom.append(this.element, $('.interactive-result-vulns')); - const vulnsHeaderElement = dom.append(vulnsContainer, $('.interactive-result-vulns-header', undefined)); - this.vulnsButton = new Button(vulnsHeaderElement, { - buttonBackground: undefined, - buttonBorder: undefined, - buttonForeground: undefined, - buttonHoverBackground: undefined, - buttonSecondaryBackground: undefined, - buttonSecondaryForeground: undefined, - buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined, - supportIcons: true - }); - this._uri = URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: generateUuid() }); - this.textModel = textModelService.createModelReference(this._uri).then(ref => { - this.editor.setModel(ref.object.textEditorModel); - this._register(ref); - return ref.object.textEditorModel; - }); - - this.vulnsListElement = dom.append(vulnsContainer, $('ul.interactive-result-vulns-list')); - - this.vulnsButton.onDidClick(() => { - const element = this.currentCodeBlockData!.element as IChatResponseViewModel; - element.vulnerabilitiesListExpanded = !element.vulnerabilitiesListExpanded; - this.vulnsButton.label = this.getVulnerabilitiesLabel(); - this.element.classList.toggle('chat-vulnerabilities-collapsed', !element.vulnerabilitiesListExpanded); - this._onDidChangeContentHeight.fire(); - // this.updateAriaLabel(collapseButton.element, referencesLabel, element.usedReferencesExpanded); - }); - } - - get uri(): URI { - return this._uri; - } - - protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { - return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { - isSimpleWidget: false, - contributions: EditorExtensionsRegistry.getSomeEditorContributions([ - MenuPreventer.ID, - SelectionClipboardContributionID, - ContextMenuController.ID, - - WordHighlighterContribution.ID, - ViewportSemanticTokensContribution.ID, - BracketMatchingController.ID, - SmartSelectController.ID, - HoverController.ID, - GotoDefinitionAtPositionEditorContribution.ID, - ]) - })); - } - - override async render(data: ISimpleCodeBlockData, width: number): Promise { - await super.render(data, width); if (data.vulns?.length && isResponseVM(data.element)) { dom.clearNode(this.vulnsListElement); @@ -410,20 +364,27 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart } } - protected override async updateEditor(data: ISimpleCodeBlockData): Promise { - this.editor.setModel(await this.textModel); - const text = this.fixCodeText(data.text, data.languageId); - this.setText(text); + reset() { + this.clearWidgets(); + } + + private clearWidgets() { + HoverController.get(this.editor)?.hideContentHover(); + } - const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(data.languageId) ?? undefined; - this.setLanguage(vscodeLanguageId); - data.languageId = vscodeLanguageId ?? 'plaintext'; + private async updateEditor(data: ICodeBlockData): Promise { + const textModel = (await data.textModel).object.textEditorModel; + this.editor.setModel(textModel); + if (data.range) { + this.editor.setSelection(data.range); + this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); + } this.toolbar.context = { - code: data.text, + code: textModel.getTextBuffer().getValueInRange(data.range ?? textModel.getFullModelRange(), EndOfLinePreference.TextDefined), codeBlockIndex: data.codeBlockIndex, element: data.element, - languageId: data.languageId + languageId: textModel.getLanguageId() } satisfies ICodeBlockActionContext; } @@ -438,110 +399,8 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart const icon = (element: IChatResponseViewModel) => element.vulnerabilitiesListExpanded ? Codicon.chevronDown : Codicon.chevronRight; return `${referencesLabel} $(${icon(this.currentCodeBlockData.element as IChatResponseViewModel).id})`; } - - private fixCodeText(text: string, languageId: string): string { - if (languageId === 'php') { - if (!text.trim().startsWith('<')) { - return ``; - } - } - - return text; - } - - private async setText(newText: string): Promise { - const model = await this.textModel; - const currentText = model.getValue(EndOfLinePreference.LF); - if (newText === currentText) { - return; - } - - if (newText.startsWith(currentText)) { - const text = newText.slice(currentText.length); - const lastLine = model.getLineCount(); - const lastCol = model.getLineMaxColumn(lastLine); - model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); - } else { - // console.log(`Failed to optimize setText`); - model.setValue(newText); - } - } - - private async setLanguage(vscodeLanguageId: string | undefined): Promise { - (await this.textModel).setLanguage(vscodeLanguageId ?? PLAINTEXT_LANGUAGE_ID); - } } -export class LocalFileCodeBlockPart extends BaseCodeBlockPart { - - private readonly textModelReference = this._register(new MutableDisposable>()); - private currentCodeBlockData?: ILocalFileCodeBlockData; - - constructor( - options: ChatEditorOptions, - menuId: MenuId, - delegate: IChatRendererDelegate, - overflowWidgetsDomNode: HTMLElement | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IModelService modelService: IModelService, - @ITextModelService private readonly textModelService: ITextModelService, - @IConfigurationService configurationService: IConfigurationService, - @IAccessibilityService accessibilityService: IAccessibilityService - ) { - super(options, menuId, delegate, overflowWidgetsDomNode, instantiationService, contextKeyService, modelService, configurationService, accessibilityService); - } - - get uri(): URI { - return this.currentCodeBlockData!.uri; - } - - protected override getContentHeight() { - if (this.currentCodeBlockData?.range) { - const lineCount = this.currentCodeBlockData.range.endLineNumber - this.currentCodeBlockData.range.startLineNumber + 1; - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - return lineCount * lineHeight; - } - return super.getContentHeight(); - } - - protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { - return this._register(instantiationService.createInstance(CodeEditorWidget, parent, { - ...options, - }, { - // TODO: be more selective about contributions - })); - } - - protected override async updateEditor(data: ILocalFileCodeBlockData): Promise { - let model: ITextModel; - if (this.currentCodeBlockData?.uri.toString() === data.uri.toString()) { - this.currentCodeBlockData = data; - model = this.editor.getModel()!; - } else { - this.currentCodeBlockData = data; - const result = await this.textModelService.createModelReference(data.uri); - model = result.object.textEditorModel; - this.textModelReference.value = result; - this.editor.setModel(model); - } - - - if (data.range) { - this.editor.setSelection(data.range); - this.editor.revealRangeInCenter(data.range, ScrollType.Immediate); - } - - this.toolbar.context = { - code: model.getTextBuffer().getValueInRange(data.range ?? model.getFullModelRange(), EndOfLinePreference.TextDefined), - codeBlockIndex: data.codeBlockIndex, - element: data.element, - languageId: model.getLanguageId() - } satisfies ICodeBlockActionContext; - } -} - - export class ChatCodeBlockContentProvider extends Disposable implements ITextModelContentProvider { constructor( diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index a91512a6840..fc1328ca803 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -5,14 +5,19 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { marked } from 'vs/base/common/marked/marked'; import { URI } from 'vs/base/common/uri'; +import { Range } from 'vs/editor/common/core/range'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { EndOfLinePreference } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; +import { CodeBlockModelCollection } from './codeBlockModelCollection'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { return !!item && typeof item === 'object' && 'message' in item; @@ -134,6 +139,7 @@ export interface IChatResponseViewModel { } export class ChatViewModel extends Disposable implements IChatViewModel { + private readonly _onDidDisposeModel = this._register(new Emitter()); readonly onDidDisposeModel = this._onDidDisposeModel.event; @@ -179,12 +185,17 @@ export class ChatViewModel extends Disposable implements IChatViewModel { constructor( private readonly _model: IChatModel, + public readonly codeBlockModelCollection: CodeBlockModelCollection, @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); _model.getRequests().forEach((request, i) => { - this._items.push(new ChatRequestViewModel(request)); + const requestModel = new ChatRequestViewModel(request); + this._items.push(requestModel); + this.updateCodeBlockTextModels(requestModel); + if (request.response) { this.onAddResponse(request.response); } @@ -193,7 +204,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { this._register(_model.onDidDispose(() => this._onDidDisposeModel.fire())); this._register(_model.onDidChange(e => { if (e.kind === 'addRequest') { - this._items.push(new ChatRequestViewModel(e.request)); + const requestModel = new ChatRequestViewModel(e.request); + this._items.push(requestModel); + this.updateCodeBlockTextModels(requestModel); + if (e.request.response) { this.onAddResponse(e.request.response); } @@ -224,8 +238,12 @@ export class ChatViewModel extends Disposable implements IChatViewModel { private onAddResponse(responseModel: IChatResponseModel) { const response = this.instantiationService.createInstance(ChatResponseViewModel, responseModel); - this._register(response.onDidChange(() => this._onDidChange.fire(null))); + this._register(response.onDidChange(() => { + this.updateCodeBlockTextModels(response); + return this._onDidChange.fire(null); + })); this._items.push(response); + this.updateCodeBlockTextModels(response); } getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel)[] { @@ -238,6 +256,79 @@ export class ChatViewModel extends Disposable implements IChatViewModel { .filter((item): item is ChatResponseViewModel => item instanceof ChatResponseViewModel) .forEach((item: ChatResponseViewModel) => item.dispose()); } + + private updateCodeBlockTextModels(model: IChatRequestViewModel | IChatResponseViewModel) { + const content = isRequestVM(model) ? model.messageText : model.response.asString(); + const renderer = new marked.Renderer(); + + let codeBlockIndex = 0; + renderer.code = (value, languageId) => { + languageId ??= ''; + const newText = this.fixCodeText(value, languageId); + const textModel = this.codeBlockModelCollection.getOrCreate(model.id, codeBlockIndex++); + textModel.then(ref => { + const model = ref.object.textEditorModel; + if (languageId) { + const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(languageId); + if (vscodeLanguageId && vscodeLanguageId !== ref.object.textEditorModel.getLanguageId()) { + ref.object.textEditorModel.setLanguage(vscodeLanguageId); + } + } + + const currentText = ref.object.textEditorModel.getValue(EndOfLinePreference.LF); + if (newText === currentText) { + return; + } + + if (newText.startsWith(currentText)) { + const text = newText.slice(currentText.length); + const lastLine = model.getLineCount(); + const lastCol = model.getLineMaxColumn(lastLine); + model.applyEdits([{ range: new Range(lastLine, lastCol, lastLine, lastCol), text }]); + } else { + // console.log(`Failed to optimize setText`); + model.setValue(newText); + } + }); + return ''; + }; + + marked.parse(this.ensureFencedCodeBlocksTerminated(content), { renderer }); + } + + private fixCodeText(text: string, languageId: string): string { + if (languageId === 'php') { + if (!text.trim().startsWith('<')) { + return ``; + } + } + + return text; + } + + /** + * Marked doesn't consistently render fenced code blocks that aren't terminated. + * + * Try to close them ourselves to workaround this. + */ + private ensureFencedCodeBlocksTerminated(content: string): string { + const lines = content.split('\n'); + let inCodeBlock = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.startsWith('```')) { + inCodeBlock = !inCodeBlock; + } + } + + // If we're still in a code block at the end of the content, add a closing fence + if (inCodeBlock) { + lines.push('```'); + } + + return lines.join('\n'); + } } export class ChatRequestViewModel implements IChatRequestViewModel { diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts new file mode 100644 index 00000000000..edf5b4ae445 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IReference } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; + + +export class CodeBlockModelCollection extends Disposable { + + private readonly _models = new ResourceMap>>(); + + constructor( + @ITextModelService private readonly textModelService: ITextModelService + ) { + super(); + } + + public override dispose(): void { + super.dispose(); + this.clear(); + } + + get(responseId: string, codeBlockIndex: number): Promise> | undefined { + const uri = this.getUri(responseId, codeBlockIndex); + return this._models.get(uri); + } + + getOrCreate(responseId: string, codeBlockIndex: number): Promise> { + const existing = this.get(responseId, codeBlockIndex); + if (existing) { + return existing; + } + + const uri = this.getUri(responseId, codeBlockIndex); + const ref = this.textModelService.createModelReference(uri); + this._models.set(uri, ref); + return ref; + } + + clear(): void { + this._models.forEach(async (model) => (await model).dispose()); + this._models.clear(); + } + + private getUri(responseId: string, index: number): URI { + return URI.from({ scheme: Schemas.vscodeChatCodeBlock, path: `/${responseId}/${index}` }); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 04d8fe1ff4f..5af8f2acc53 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -62,6 +62,7 @@ import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/cha import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -240,6 +241,7 @@ export class InlineChatWidget { private _slashCommandUsedDisposables = this._store.add(new DisposableStore()); private _chatMessage: MarkdownString | undefined; + private readonly _codeBlockModelCollection: CodeBlockModelCollection; constructor( private readonly parentEditor: ICodeEditor, @@ -451,6 +453,9 @@ export class InlineChatWidget { this._elements.followUps.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); } })); + + // Code block rendering + this._codeBlockModelCollection = this._store.add(this._instantiationService.createInstance(CodeBlockModelCollection)); } @@ -615,7 +620,7 @@ export class InlineChatWidget { const viewModel = this._chatMessageDisposables.add(new ChatResponseViewModel(responseModel, this._logService)); const renderOptions: IChatListItemRendererOptions = { renderStyle: 'compact', noHeader: true, noPadding: true }; const chatRendererDelegate: IChatRendererDelegate = { getListLength() { return 1; } }; - const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, undefined)); + const renderer = this._chatMessageDisposables.add(this._instantiationService.createInstance(ChatListItemRenderer, this._editorOptions, renderOptions, chatRendererDelegate, this._codeBlockModelCollection, undefined)); renderer.layout(this._elements.chatMessageContent.clientWidth - 4); // 2 for the padding used for the tab index border this._chatMessageDisposables.add(this._onDidChangeLayout.event(() => { renderer.layout(this._elements.chatMessageContent.clientWidth - 4); From f9377b87afb68830547acf13a81119b6e07f1b5b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:29:51 -0800 Subject: [PATCH 151/753] clearMarker -> clear --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 2 +- .../contrib/terminal/browser/xterm/markNavigationAddon.ts | 2 +- .../terminalContrib/links/browser/terminalLinkQuickpick.ts | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d8d7ebeb111..db2791f9bda 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -113,7 +113,7 @@ export interface IMarkTracker { selectToNextMark(): void; selectToPreviousLine(): void; selectToNextLine(): void; - clearMarker(): void; + clear(): void; scrollToClosestMarker(startMarkerId: string, endMarkerId?: string, highlight?: boolean | undefined): void; scrollToLine(line: number, position: ScrollPosition): void; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index aae4ac6f275..9a2fbf8e6d7 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -101,7 +101,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return undefined; } - clearMarker(): void { + clear(): void { // Clear the current marker so successive focus/selection actions are performed from the // bottom of the buffer this._currentMarker = Boundary.Bottom; diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 544612bcb53..e3bd74aec3b 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -140,8 +140,7 @@ export class TerminalLinkQuickpick extends DisposableStore { const markTracker = this._instance?.xterm?.markTracker; if (markTracker) { markTracker.restoreScrollState(); - // TODO: This name isn't great - markTracker.clearMarker(); + markTracker.clear(); this._terminalScrollStateSaved = false; } } From 12633b44242d02ff0079ddf5c12272669d1c19db Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:58:57 -0800 Subject: [PATCH 152/753] Ignore links with text that equals the empty string fixes #206258 --- .../terminalContrib/links/browser/terminalLinkParsing.ts | 5 +++++ .../links/test/browser/terminalLinkParsing.test.ts | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts index 8328315b957..94dbbd2f5cd 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing.ts @@ -277,6 +277,11 @@ function detectLinksViaSuffix(line: string): IParsedLink[] { }; path = path.substring(prefix.text.length); + // Don't allow suffix links to be returned when the link itself is the empty string + if (path.trim().length === 0) { + continue; + } + // If there are multiple characters in the prefix, trim the prefix if the _first_ // suffix character is the same as the last prefix character. For example, for the // text `echo "'foo' on line 1"`: diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts index 71d641d0739..04e4aeb5e9b 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkParsing.test.ts @@ -706,5 +706,11 @@ suite('TerminalLinkParsing', () => { }); } }); + suite('should ignore links with suffixes when the path itself is the empty string', () => { + deepStrictEqual( + detectLinks('""",1', OperatingSystem.Linux), + [] as IParsedLink[] + ); + }); }); }); From 9b3f22b333b4bb4d065abb787411411494f7c45b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:03:16 -0800 Subject: [PATCH 153/753] only allow starting chat when terminal agent has been registered --- .../contrib/terminalContrib/chat/browser/terminalChatActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 04ff3b3a472..878246a2e26 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -28,6 +28,7 @@ registerActiveXtermAction({ precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + TerminalChatContextKeys.agentRegistered ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 51209b3a1448ff6df84c973ca1ab786fa3a99d72 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:26:30 -0800 Subject: [PATCH 154/753] on hide, reset input value --- .../contrib/terminalContrib/chat/browser/terminalChatWidget.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index aa43972ef64..74bb3b4ee9d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -132,6 +132,7 @@ export class TerminalChatWidget extends Disposable { this._inlineChatWidget.updateToolbar(false); this._focusedContextKey.set(false); this._visibleContextKey.set(false); + this._inlineChatWidget.value = ''; this._instance.focus(); } focus(): void { From a1070cb7f172f4930edfe3aa38f7b473c85169f4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 26 Feb 2024 12:40:34 -0800 Subject: [PATCH 155/753] set vertical position to below cursor line --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index 74bb3b4ee9d..26efec69639 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -112,9 +112,9 @@ export class TerminalChatWidget extends Disposable { if (!font?.charHeight) { return; } - const cursorY = this._instance.xterm?.raw.buffer.active.cursorY ?? 0; + const cursorY = (this._instance.xterm?.raw.buffer.active.cursorY ?? 0) + 1; const height = font.charHeight * font.lineHeight; - const top = cursorY * height + 10; + const top = cursorY * height + 12; this._container.style.top = `${top}px`; const terminalHeight = this._instance.domElement.clientHeight; if (terminalHeight && top > terminalHeight - this._inlineChatWidget.getHeight()) { From 6b6482cf322cc31960b4c8007d77b31e120167c6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 26 Feb 2024 16:12:49 -0800 Subject: [PATCH 156/753] fix: command quoting for wt.exe (#206305) Fixes #204039 --- .../externalTerminal/node/externalTerminalService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index a8df823266a..5086c95a802 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -80,8 +80,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl return new Promise((resolve, reject) => { const title = `"${dir} - ${TERMINAL_TITLE}"`; - const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code - + const command = `"${args.join('" "')}" & pause`; // use '|' to only pause on non-zero exit code // merge environment variables into a copy of the process.env const env = Object.assign({}, getSanitizedEnvironment(process), envVars); @@ -110,7 +109,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl cmdArgs = ['-d', '.', exec, '/c', command]; } else { spawnExec = WindowsExternalTerminalService.CMD; - cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; + cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', `"${command}"`]; } const cmd = cp.spawn(spawnExec, cmdArgs, options); From 150d9e622b41ac36e186a6d7196dbaa36ccd5254 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 26 Feb 2024 18:39:38 -0800 Subject: [PATCH 157/753] debug: cleanup cancellation tokens in inline value preview (#206307) Also fixes a race where we could have outdated decorations show Refs #205966 --- .../test/browser/mainThreadWorkspace.test.ts | 12 ++++----- .../test/browser/bulkCellEdits.test.ts | 4 +-- .../debug/browser/debugEditorContribution.ts | 27 +++++++++++-------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index 2cc5a637a6a..5234d7abb34 100644 --- a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -41,7 +41,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, CancellationToken.None); }); test('exclude defaults', () => { @@ -63,7 +63,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, CancellationToken.None); }); test('disregard excludes', () => { @@ -84,7 +84,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, CancellationToken.None); }); test('do not disregard anything if disregardExcludeSettings is true', () => { @@ -106,7 +106,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, CancellationToken.None); }); test('exclude string', () => { @@ -120,6 +120,6 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, CancellationToken.None); }); }); diff --git a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts index 76497826d74..22c507ca0b3 100644 --- a/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts +++ b/src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { mockObject } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -35,7 +35,7 @@ suite('BulkCellEdits', function () { const edits = [ new ResourceNotebookCellEdit(inputUri, { index: 0, count: 1, editType: CellEditType.Replace, cells: [] }) ]; - const bce = new BulkCellEdits(new UndoRedoGroup(), new UndoRedoSource(), progress, new CancellationTokenSource().token, edits, editorService, notebookService as any); + const bce = new BulkCellEdits(new UndoRedoGroup(), new UndoRedoSource(), progress, CancellationToken.None, edits, editorService, notebookService as any); await bce.apply(); const resolveArgs = notebookService.resolve.args[0]; diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index a800e241511..30789475e42 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -6,7 +6,7 @@ import { addDisposableListener, isKeyboardEvent } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { distinct, flatten } from 'vs/base/common/arrays'; +import { distinct } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; @@ -15,7 +15,7 @@ import { Event } from 'vs/base/common/event'; import { visit } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import * as env from 'vs/base/common/platform'; @@ -217,6 +217,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private altListener = new MutableDisposable(); private altPressed = false; private oldDecorations = this.editor.createDecorationsCollection(); + private displayedStore = new DisposableStore(); private editorHoverOptions: IEditorHoverOptions | undefined; private readonly debounceInfo: IFeatureDebounceInformation; @@ -237,7 +238,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { ) { this.debounceInfo = featureDebounceService.for(languageFeaturesService.inlineValuesProvider, 'InlineValues', { min: DEAFULT_INLINE_DEBOUNCE_DELAY }); this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor); - this.toDispose = [this.defaultHoverLockout, this.altListener]; + this.toDispose = [this.defaultHoverLockout, this.altListener, this.displayedStore]; this.registerListeners(); this.exceptionWidgetVisible = CONTEXT_EXCEPTION_WIDGET_VISIBLE.bindTo(contextKeyService); this.toggleExceptionWidget(); @@ -639,7 +640,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private get removeInlineValuesScheduler(): RunOnceScheduler { return new RunOnceScheduler( () => { - this.oldDecorations.clear(); + this.displayedStore.clear(); }, 100 ); @@ -670,10 +671,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { } this.removeInlineValuesScheduler.cancel(); + this.displayedStore.clear(); const viewRanges = this.editor.getVisibleRangesPlusViewportAboveBelow(); let allDecorations: IModelDeltaDecoration[]; + const cts = new CancellationTokenSource(); + this.displayedStore.add(toDisposable(() => cts.dispose(true))); + if (this.languageFeaturesService.inlineValuesProvider.has(model)) { const findVariable = async (_key: string, caseSensitiveLookup: boolean): Promise => { @@ -693,14 +698,13 @@ export class DebugEditorContribution implements IDebugEditorContribution { frameId: stackFrame.frameId, stoppedLocation: new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1, stackFrame.range.endLineNumber, stackFrame.range.endColumn + 1) }; - const token = new CancellationTokenSource().token; const providers = this.languageFeaturesService.inlineValuesProvider.ordered(model).reverse(); allDecorations = []; const lineDecorations = new Map(); - const promises = flatten(providers.map(provider => viewRanges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, token)).then(async (result) => { + const promises = providers.flatMap(provider => viewRanges.map(range => Promise.resolve(provider.provideInlineValues(model, range, ctx, cts.token)).then(async (result) => { if (result) { for (const iv of result) { @@ -753,7 +757,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } }, err => { onUnexpectedExternalError(err); - })))); + }))); const startTime = Date.now(); @@ -794,12 +798,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { return createInlineValueDecorationsInsideRange(variables, ownRanges, model, this._wordToLineNumbersMap.value); })); - allDecorations = distinct(decorationsPerScope.reduce((previous, current) => previous.concat(current), []), + allDecorations = distinct(decorationsPerScope.flat(), // Deduplicate decorations since same variable can appear in multiple scopes, leading to duplicated decorations #129770 decoration => `${decoration.range.startLineNumber}:${decoration?.options.after?.content}`); } - this.oldDecorations.set(allDecorations); + if (!cts.token.isCancellationRequested) { + this.oldDecorations.set(allDecorations); + this.displayedStore.add(toDisposable(() => this.oldDecorations.clear())); + } } dispose(): void { @@ -810,8 +817,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.configurationWidget.dispose(); } this.toDispose = dispose(this.toDispose); - - this.oldDecorations.clear(); } } From 6e1561e0e58a44c2e37293532ad50ac327d9a1b6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 07:27:54 +0100 Subject: [PATCH 158/753] editors - introduce `transient` editor state (#205530) --- src/vs/platform/editor/common/editor.ts | 10 ++ .../api/browser/mainThreadEditorTabs.ts | 3 + .../browser/parts/editor/editorGroupView.ts | 14 ++- src/vs/workbench/common/editor.ts | 1 + .../common/editor/editorGroupModel.ts | 75 ++++++++++++- .../common/editor/filteredEditorGroupModel.ts | 1 + .../quickTextSearch/textSearchQuickAccess.ts | 18 +-- .../links/browser/terminalLinkQuickpick.ts | 25 +---- .../editor/common/editorGroupsService.ts | 18 +++ .../test/browser/editorGroupsService.test.ts | 66 +++++++++++ .../history/browser/historyService.ts | 32 ++---- .../services/history/common/history.ts | 6 - .../test/browser/historyService.test.ts | 105 +----------------- .../editor/filteredEditorGroupModel.test.ts | 22 ++++ .../test/browser/workbenchTestServices.ts | 2 + .../test/common/workbenchTestServices.ts | 1 - 16 files changed, 233 insertions(+), 166 deletions(-) diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 24e2e4c5506..51060787d4a 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -288,6 +288,16 @@ export interface IEditorOptions { * applied when opening the editor. */ viewState?: object; + + /** + * A transient editor will attempt to appear as preview and certain components + * (such as history tracking) may decide to ignore the editor when it becomes + * active. + * This option is meant to be used only when the editor is used for a short + * period of time, for example when opening a preview of the editor from a + * picker control in the background while navigating through results of the picker. + */ + transient?: boolean; } export interface ITextEditorSelection { diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index d23f8e91fd5..41ed2e74e2c 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -553,6 +553,9 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { this._onDidTabPreviewChange(groupId, event.editorIndex, event.editor); break; } + case GroupModelChangeKind.EDITOR_TRANSIENT: + // Currently not exposed in the API + break; case GroupModelChangeKind.EDITOR_MOVE: if (isGroupEditorMoveEvent(event) && event.editor && event.editorIndex !== undefined && event.oldEditorIndex !== undefined) { this._onDidTabMove(groupId, event.editorIndex, event.oldEditorIndex, event.editor); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index f0bdfdd5bc9..e0761b1f1db 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -886,6 +886,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.model.isSticky(editorOrIndex); } + isTransient(editorOrIndex: EditorInput | number): boolean { + return this.model.isTransient(editorOrIndex); + } + isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); } @@ -1004,6 +1008,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } } + setTransient(candidate: EditorInput | undefined, transient: boolean): void { + const editor = candidate ?? this.activeEditor; + if (editor) { + this.model.setTransient(editor, transient); + } + } + //#endregion //#region openEditor() @@ -1033,7 +1044,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Determine options const pinned = options?.sticky - || !this.groupsView.partOptions.enablePreview + || (!this.groupsView.partOptions.enablePreview && !options?.transient) || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this.model.isSticky(options.index)) @@ -1042,6 +1053,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { index: options ? options.index : undefined, pinned, sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)), + transient: !!options?.transient, active: this.count === 0 || !options || !options.inactive, supportSideBySide: internalOptions?.supportSideBySide }; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 9652c2dfe00..c27b43d382c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1133,6 +1133,7 @@ export const enum GroupModelChangeKind { EDITOR_LABEL, EDITOR_CAPABILITIES, EDITOR_PIN, + EDITOR_TRANSIENT, EDITOR_STICKY, EDITOR_DIRTY, EDITOR_WILL_DISPOSE diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 8a018234256..17c5ab59c26 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -22,7 +22,8 @@ const EditorOpenPositioning = { export interface IEditorOpenOptions { readonly pinned?: boolean; - sticky?: boolean; + readonly sticky?: boolean; + readonly transient?: boolean; active?: boolean; readonly index?: number; readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; @@ -180,6 +181,7 @@ export interface IReadonlyEditorGroupModel { isActive(editor: EditorInput | IUntypedEditorInput): boolean; isPinned(editorOrIndex: EditorInput | number): boolean; isSticky(editorOrIndex: EditorInput | number): boolean; + isTransient(editorOrIndex: EditorInput | number): boolean; isFirst(editor: EditorInput, editors?: EditorInput[]): boolean; isLast(editor: EditorInput, editors?: EditorInput[]): boolean; findEditor(editor: EditorInput | null, options?: IMatchEditorOptions): [EditorInput, number /* index */] | undefined; @@ -217,6 +219,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private preview: EditorInput | null = null; // editor in preview state private active: EditorInput | null = null; // editor in active state private sticky = -1; // index of first editor in sticky state + private transient = new Set(); // editors in transient state private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined; private focusRecentEditorAfterClose: boolean | undefined; @@ -295,6 +298,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { openEditor(candidate: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult { const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index)); const makePinned = options?.pinned || options?.sticky; + const makeTransient = !!options?.transient; const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); const existingEditorAndIndex = this.findEditor(candidate, options); @@ -381,6 +385,11 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.preview = newEditor; } + // Handle transient + if (makeTransient) { + this.doSetTransient(newEditor, targetIndex, true); + } + // Listeners this.registerEditorListeners(newEditor); @@ -412,6 +421,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doPin(existingEditor, existingEditorIndex); } + // Update transient + this.doSetTransient(existingEditor, existingEditorIndex, makeTransient); + // Activate it if (makeActive) { this.doSetActive(existingEditor, existingEditorIndex); @@ -563,6 +575,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.preview = null; } + // Remove from transient + this.transient.delete(editor); + // Remove from arrays this.splice(index, true); @@ -860,6 +875,62 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return index <= this.sticky; } + setTransient(candidate: EditorInput, transient: boolean): EditorInput | undefined { + if (!transient && this.transient.size === 0) { + return; // no transient editor + } + + const res = this.findEditor(candidate); + if (!res) { + return; // not found + } + + const [editor, editorIndex] = res; + + this.doSetTransient(editor, editorIndex, transient); + + return editor; + } + + private doSetTransient(editor: EditorInput, editorIndex: number, transient: boolean): void { + if (transient) { + if (this.transient.has(editor)) { + return; + } + + this.transient.add(editor); + } else { + if (!this.transient.has(editor)) { + return; + } + + this.transient.delete(editor); + } + + // Event + const event: IGroupEditorChangeEvent = { + kind: GroupModelChangeKind.EDITOR_TRANSIENT, + editor, + editorIndex + }; + this._onDidModelChange.fire(event); + } + + isTransient(editorOrIndex: EditorInput | number): boolean { + if (this.transient.size === 0) { + return false; // no transient editor + } + + let editor: EditorInput | undefined; + if (typeof editorOrIndex === 'number') { + editor = this.editors[editorOrIndex]; + } else { + editor = this.findEditor(editorOrIndex)?.[0]; + } + + return !!editor && this.transient.has(editor); + } + private splice(index: number, del: boolean, editor?: EditorInput): void { const editorToDeleteOrReplace = this.editors[index]; @@ -1124,6 +1195,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { dispose(Array.from(this.editorListeners)); this.editorListeners.clear(); + this.transient.clear(); + super.dispose(); } } diff --git a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts index 7b427fe5ded..390b19874c8 100644 --- a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts +++ b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts @@ -38,6 +38,7 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE get previewEditor(): EditorInput | null { return this.model.previewEditor && this.filter(this.model.previewEditor) ? this.model.previewEditor : null; } isPinned(editorOrIndex: EditorInput | number): boolean { return this.model.isPinned(editorOrIndex); } + isTransient(editorOrIndex: EditorInput | number): boolean { return this.model.isTransient(editorOrIndex); } isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); } isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 3691e98ec1d..c4e31c3c903 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -30,7 +30,6 @@ import { IPatternInfo, ISearchComplete, ITextQuery, VIEW_ID } from 'vs/workbench import { Event } from 'vs/base/common/event'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { Sequencer } from 'vs/base/common/async'; export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; @@ -84,8 +83,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { - // disable and re-enable history service so that we can ignore this history entry - const disposable = this._historyService.suspendTracking(); - try { - await this._editorService.openEditor({ - resource: itemMatch.parent().resource, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } - }); - } finally { - disposable.dispose(); - } + await this._editorService.openEditor({ + resource: itemMatch.parent().resource, + options: { transient: true, preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } + }); }); } })); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index e3bd74aec3b..bfa7bbfb687 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -17,11 +17,8 @@ import type { TerminalLink } from 'vs/workbench/contrib/terminalContrib/links/br import { Sequencer, timeout } from 'vs/base/common/async'; import { EditorViewState } from 'vs/workbench/browser/quickaccess'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; import { TerminalBuiltinLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import type { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; @@ -36,9 +33,7 @@ export class TerminalLinkQuickpick extends DisposableStore { readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( - @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, - @IHistoryService private readonly _historyService: IHistoryService, @ILabelService private readonly _labelService: ILabelService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService @@ -247,12 +242,6 @@ export class TerminalLinkQuickpick extends DisposableStore { return; } - // Don't open if preview editors are disabled as it may open many editor - const config = this._configurationService.getValue(); - if (!config.workbench?.editor?.enablePreview) { - return; - } - this._previewItemInEditor(link); } @@ -267,16 +256,10 @@ export class TerminalLinkQuickpick extends DisposableStore { this._editorViewState.set(); this._editorSequencer.queue(async () => { - // disable and re-enable history service so that we can ignore this history entry - const disposable = this._historyService.suspendTracking(); - try { - await this._editorService.openEditor({ - resource: link.uri, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection, } - }); - } finally { - disposable.dispose(); - } + await this._editorService.openEditor({ + resource: link.uri, + options: { transient: true, preserveFocus: true, revealIfOpened: true, ignoreError: true, selection, } + }); }); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 650df06217d..fd2f81f70e9 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -727,6 +727,11 @@ export interface IEditorGroup { */ isSticky(editorOrIndex: EditorInput | number): boolean; + /** + * Find out if the provided editor or index of editor is transient in the group. + */ + isTransient(editorOrIndex: EditorInput | number): boolean; + /** * Find out if the provided editor is active in the group. */ @@ -832,6 +837,19 @@ export interface IEditorGroup { */ unstickEditor(editor?: EditorInput): void; + /** + * A transient editor will attempt to appear as preview and certain components + * (such as history tracking) may decide to ignore the editor when it becomes + * active. + * This option is meant to be used only when the editor is used for a short + * period of time, for example when opening a preview of the editor from a + * picker control in the background while navigating through results of the picker. + * + * @param editor the editor to update transient state, or the currently active editor + * if unspecified. + */ + setTransient(editor: EditorInput | undefined, transient: boolean): void; + /** * Whether this editor group should be locked or not. * diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index d87cda2d39c..e42c6116bc7 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -477,6 +477,7 @@ suite('EditorGroupsService', () => { const editorCloseEvents: IGroupModelChangeEvent[] = []; let editorPinCounter = 0; let editorStickyCounter = 0; + let editorTransientCounter = 0; let editorCapabilitiesCounter = 0; const editorGroupModelChangeListener = group.onDidModelChange(e => { if (e.kind === GroupModelChangeKind.EDITOR_OPEN) { @@ -489,6 +490,9 @@ suite('EditorGroupsService', () => { } else if (e.kind === GroupModelChangeKind.EDITOR_STICKY) { assert.ok(e.editor); editorStickyCounter++; + } else if (e.kind === GroupModelChangeKind.EDITOR_TRANSIENT) { + assert.ok(e.editor); + editorTransientCounter++; } else if (e.kind === GroupModelChangeKind.EDITOR_CAPABILITIES) { assert.ok(e.editor); editorCapabilitiesCounter++; @@ -593,6 +597,15 @@ suite('EditorGroupsService', () => { group.unstickEditor(input); assert.strictEqual(editorStickyCounter, 2); + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(editorTransientCounter, 0); + group.setTransient(input, true); + assert.strictEqual(group.isTransient(input), true); + assert.strictEqual(editorTransientCounter, 1); + group.setTransient(input, false); + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(editorTransientCounter, 2); + editorCloseListener.dispose(); editorWillCloseListener.dispose(); editorDidCloseListener.dispose(); @@ -1817,5 +1830,58 @@ suite('EditorGroupsService', () => { maxiizeGroupEventDisposable.dispose(); }); + test('transient editors - basics', async () => { + const [part] = await createPart(); + const group = part.activeGroup; + + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(input, { pinned: true }); + await group.openEditor(inputInactive, { inactive: true }); + + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(group.isTransient(inputInactive), false); + + group.setTransient(input, true); + + assert.strictEqual(group.isTransient(input), true); + assert.strictEqual(group.isTransient(inputInactive), false); + + group.setTransient(input, false); + + assert.strictEqual(group.isTransient(input), false); + assert.strictEqual(group.isTransient(inputInactive), false); + + const inputTransient = createTestFileEditorInput(URI.file('foo/bar/transient'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(inputTransient, { transient: true }); + assert.strictEqual(group.isTransient(inputTransient), true); + + await group.openEditor(inputTransient, {}); + assert.strictEqual(group.isTransient(inputTransient), false); + }); + + test('transient editors - overrides enablePreview setting', async function () { + const instantiationService = workbenchInstantiationService(undefined, disposables); + const configurationService = new TestConfigurationService(); + await configurationService.setUserConfiguration('workbench', { 'editor': { 'enablePreview': false } }); + instantiationService.stub(IConfigurationService, configurationService); + + const [part] = await createPart(instantiationService); + + const group = part.activeGroup; + assert.strictEqual(group.isEmpty, true); + + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + + await group.openEditor(input, { pinned: false }); + assert.strictEqual(group.isPinned(input), true); + + await group.openEditor(input2, { transient: true }); + assert.strictEqual(group.isPinned(input2), false); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 108685831ad..3cef7fa9416 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -12,7 +12,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { GoFilter, GoScope, IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { dispose, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -73,31 +73,16 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private trackingSuspended = false; - suspendTracking(): IDisposable { - this.trackingSuspended = true; - - return toDisposable(() => this.trackingSuspended = false); - } - private registerListeners(): void { // Mouse back/forward support this.registerMouseNavigationListener(); // Editor changes - this._register(this.editorService.onDidActiveEditorChange((e) => { - if (!this.trackingSuspended) { - this.onDidActiveEditorChange(); - } - })); + this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); this._register(this.editorService.onDidOpenEditorFail(event => this.remove(event.editor))); this._register(this.editorService.onDidCloseEditor(event => this.onDidCloseEditor(event))); - this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => { - if (!this.trackingSuspended) { - this.handleEditorEventInRecentEditorsStack(); - } - })); + this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // Editor group changes this._register(this.editorGroupService.onDidRemoveGroup(e => this.onDidRemoveGroup(e))); @@ -188,14 +173,15 @@ export class HistoryService extends Disposable implements IHistoryService { // Dispose old listeners this.activeEditorListeners.clear(); - // Handle editor change - this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + // Handle editor change unless the editor is transient + if (!activeEditorPane?.group.isTransient(activeEditorPane.input)) { + this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + } - // Listen to selection changes if the editor pane - // is having a selection concept. + // Listen to selection changes unless the editor is transient if (isEditorPaneWithSelection(activeEditorPane)) { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { - if (!this.trackingSuspended) { + if (!activeEditorPane.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); } })); diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 3d135ec2a70..b5abcd2dad3 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -8,7 +8,6 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { URI } from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; export const IHistoryService = createDecorator('historyService'); @@ -138,9 +137,4 @@ export interface IHistoryService { * Clear list of recently opened editors. */ clearRecentlyOpened(): void; - - /** - * Temporarily suspend tracking of editor events for the history. - */ - suspendTracking(): IDisposable; } diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index 43838aad189..28f269d3bee 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -807,7 +807,7 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test('suspend should suspend editor changes- skip two editors and continue (single group)', async () => { + test('transient editors suspends editor change tracking', async () => { const [part, historyService, editorService, , instantiationService] = await createServices(); const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); @@ -821,19 +821,13 @@ suite('HistoryService', function () { assert.strictEqual(part.activeGroup.activeEditor, input1); await editorChangePromise; - const disposable = historyService.suspendTracking(); - - // wait on two editor changes before disposing - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) - .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); - - await part.activeGroup.openEditor(input2, { pinned: true }); + await part.activeGroup.openEditor(input2, { transient: true }); assert.strictEqual(part.activeGroup.activeEditor, input2); - await part.activeGroup.openEditor(input3, { pinned: true }); + await part.activeGroup.openEditor(input3, { transient: true }); assert.strictEqual(part.activeGroup.activeEditor, input3); - await editorChangePromise; - disposable.dispose(); + editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) + .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); await part.activeGroup.openEditor(input4, { pinned: true }); assert.strictEqual(part.activeGroup.activeEditor, input4); @@ -856,94 +850,5 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test('suspend should suspend editor changes- skip two editors and continue (multi group)', async () => { - const [part, historyService, editorService, , instantiationService] = await createServices(); - const rootGroup = part.activeGroup; - - const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); - const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); - const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); - const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); - const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - - let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await rootGroup.openEditor(input1, { pinned: true }); - await editorChangePromise; - - const disposable = historyService.suspendTracking(); - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange) - .then(() => Event.toPromise(editorService.onDidActiveEditorChange)); - await sideGroup.openEditor(input2, { pinned: true }); - await rootGroup.openEditor(input3, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - - await sideGroup.openEditor(input4, { pinned: true }); - await rootGroup.openEditor(input5, { pinned: true }); - - // stack should be [input1, input4, input5] - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input4); - assert.strictEqual(part.activeGroup, sideGroup); - assert.strictEqual(rootGroup.activeEditor, input5); - - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - assert.strictEqual(part.activeGroup, rootGroup); - assert.strictEqual(sideGroup.activeEditor, input4); - - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - - await historyService.goForward(); - assert.strictEqual(part.activeGroup.activeEditor, input4); - await historyService.goForward(); - assert.strictEqual(part.activeGroup.activeEditor, input5); - - return workbenchTeardown(instantiationService); - }); - - test('suspend should suspend editor changes - interleaved skips', async () => { - const [part, historyService, editorService, , instantiationService] = await createServices(); - - const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID)); - const input2 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID)); - const input3 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID)); - const input4 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID)); - const input5 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID)); - - let editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input1, { pinned: true }); - await editorChangePromise; - - let disposable = historyService.suspendTracking(); - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input2, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - - await part.activeGroup.openEditor(input3, { pinned: true }); - - disposable = historyService.suspendTracking(); - editorChangePromise = Event.toPromise(editorService.onDidActiveEditorChange); - await part.activeGroup.openEditor(input4, { pinned: true }); - await editorChangePromise; - disposable.dispose(); - - await part.activeGroup.openEditor(input5, { pinned: true }); - - // stack should be [input1, input3, input5] - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input3); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - await historyService.goBack(); - assert.strictEqual(part.activeGroup.activeEditor, input1); - - return workbenchTeardown(instantiationService); - }); - ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts index e2b8639f9cc..80765957797 100644 --- a/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/filteredEditorGroupModel.test.ts @@ -789,5 +789,27 @@ suite('FilteredEditorGroupModel', () => { assert.strictEqual(label1ChangeCounterUnsticky, 1); }); + test('Sticky/Unsticky isTransient()', async () => { + const model = createEditorGroupModel(); + + const stickyFilteredEditorGroup = disposables.add(new StickyEditorGroupModel(model)); + const unstickyFilteredEditorGroup = disposables.add(new UnstickyEditorGroupModel(model)); + + const input1 = input(); + const input2 = input(); + const input3 = input(); + const input4 = input(); + + model.openEditor(input1, { pinned: true, transient: false }); + model.openEditor(input2, { pinned: true }); + model.openEditor(input3, { pinned: true, transient: true }); + model.openEditor(input4, { pinned: false, transient: true }); + + assert.strictEqual(stickyFilteredEditorGroup.isTransient(input1), false); + assert.strictEqual(unstickyFilteredEditorGroup.isTransient(input2), false); + assert.strictEqual(stickyFilteredEditorGroup.isTransient(input3), true); + assert.strictEqual(unstickyFilteredEditorGroup.isTransient(input4), true); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 6db63c1cc2e..4f297a34c83 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -904,6 +904,7 @@ export class TestEditorGroupView implements IEditorGroupView { openEditors(_editors: EditorInputWithOptions[]): Promise { throw new Error('not implemented'); } isPinned(_editor: EditorInput): boolean { return false; } isSticky(_editor: EditorInput): boolean { return false; } + isTransient(_editor: EditorInput): boolean { return false; } isActive(_editor: EditorInput | IUntypedEditorInput): boolean { return false; } contains(candidate: EditorInput | IUntypedEditorInput): boolean { return false; } moveEditor(_editor: EditorInput, _target: IEditorGroup, _options?: IEditorOptions): void { } @@ -917,6 +918,7 @@ export class TestEditorGroupView implements IEditorGroupView { pinEditor(_editor?: EditorInput): void { } stickEditor(editor?: EditorInput | undefined): void { } unstickEditor(editor?: EditorInput | undefined): void { } + setTransient(editor: EditorInput | undefined, transient: boolean): void { } lock(locked: boolean): void { } focus(): void { } get scopedContextKeyService(): IContextKeyService { throw new Error('not implemented'); } diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 1d09528dcf6..1a938d7dbd6 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -165,7 +165,6 @@ export class TestHistoryService implements IHistoryService { async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } - suspendTracking() { return Disposable.None; } } export class TestWorkingCopy extends Disposable implements IWorkingCopy { From 24d41e4a2da7b689a76166eb029ddc9740b3c005 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 08:44:50 +0100 Subject: [PATCH 159/753] editor - more `transient` fixes (#206320) * editors - clear preview flag when tranient move leaves and preview is disabled * history - log transient state * editors - update accordingly --- .../browser/parts/editor/editorGroupView.ts | 29 ++++++++++++++ .../test/browser/editorGroupsService.test.ts | 3 ++ .../history/browser/historyService.ts | 39 ++++++++++--------- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index e0761b1f1db..faddd4e7b06 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -544,6 +544,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Visibility this._register(this.groupsView.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); + + // Focus + this._register(this.onDidFocus(() => this.onDidGainFocus())); } private onDidGroupModelChange(e: IGroupModelChangeEvent): void { @@ -578,6 +581,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.EDITOR_DIRTY: this.onDidChangeEditorDirty(e.editor); break; + case GroupModelChangeKind.EDITOR_TRANSIENT: + this.onDidChangeEditorTransient(e.editor); + break; case GroupModelChangeKind.EDITOR_LABEL: this.onDidChangeEditorLabel(e.editor); break; @@ -762,6 +768,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleControl.updateEditorDirty(editor); } + private onDidChangeEditorTransient(editor: EditorInput): void { + const transient = this.model.isTransient(editor); + + // Transient state overrides the `enablePreview` setting, + // so when an editor leaves the transient state, we have + // to ensure its preview state is also cleared. + if (!transient && !this.groupsView.partOptions.enablePreview) { + this.pinEditor(editor); + } + } + private onDidChangeEditorLabel(editor: EditorInput): void { // Forward to title control @@ -774,6 +791,18 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorPane.setVisible(visible); } + private onDidGainFocus(): void { + if (this.activeEditor) { + + // We aggressively clear the transient state of editors + // as soon as the group gains focus. This is to ensure + // that the transient state is not staying around when + // the user interacts with the editor. + + this.setTransient(this.activeEditor, false); + } + } + //#endregion //#region IEditorGroupView diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index e42c6116bc7..f1e25f4a355 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -1881,6 +1881,9 @@ suite('EditorGroupsService', () => { await group.openEditor(input2, { transient: true }); assert.strictEqual(group.isPinned(input2), false); + + group.setTransient(input2, false); + assert.strictEqual(group.isPinned(input2), true); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 3cef7fa9416..efaf3103c6c 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -35,6 +35,21 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { mainWindow } from 'vs/base/browser/window'; +interface ISerializedEditorHistoryEntry { + readonly editor: Omit & { resource: string }; +} + +interface IRecentlyClosedEditor { + readonly editorId: string | undefined; + readonly editor: IUntypedEditorInput; + + readonly resource: URI | undefined; + readonly associatedResources: URI[]; + + readonly index: number; + readonly sticky: boolean; +} + export class HistoryService extends Disposable implements IHistoryService { declare readonly _serviceBrand: undefined; @@ -47,8 +62,6 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); - - constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -59,7 +72,8 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILogService private readonly logService: ILogService ) { super(); @@ -176,6 +190,8 @@ export class HistoryService extends Disposable implements IHistoryService { // Handle editor change unless the editor is transient if (!activeEditorPane?.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorChange(activeEditorGroup, activeEditorPane); + } else { + this.logService.trace(`[History]: ignoring transient editor change (editor: ${activeEditorPane.input?.resource?.toString()}})`); } // Listen to selection changes unless the editor is transient @@ -183,6 +199,8 @@ export class HistoryService extends Disposable implements IHistoryService { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { if (!activeEditorPane.group.isTransient(activeEditorPane.input)) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); + } else { + this.logService.trace(`[History]: ignoring transient editor selection change (editor: ${activeEditorPane.input?.resource?.toString()}})`); } })); } @@ -2077,18 +2095,3 @@ class EditorHelper { } } } - -interface ISerializedEditorHistoryEntry { - editor: Omit & { resource: string }; -} - -interface IRecentlyClosedEditor { - editorId: string | undefined; - editor: IUntypedEditorInput; - - resource: URI | undefined; - associatedResources: URI[]; - - index: number; - sticky: boolean; -} From f79ac8713f8d9a355a948ab026b2227f29820780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chanchevrier?= Date: Tue, 27 Feb 2024 09:34:31 +0100 Subject: [PATCH 160/753] fix: account for sidebar position when resizing terminal --- .../contrib/terminal/browser/terminalGroup.ts | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 30a95315f33..171c69d6ed3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -7,7 +7,7 @@ import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal' import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalInstance, Direction, ITerminalGroup, ITerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; @@ -42,7 +42,6 @@ class SplitPaneContainer extends Disposable { constructor( private _container: HTMLElement, public orientation: Orientation, - @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService ) { super(); this._width = this._container.offsetWidth; @@ -61,25 +60,7 @@ class SplitPaneContainer extends Disposable { this._addChild(instance, index); } - resizePane(index: number, direction: Direction, amount: number, part: Parts): void { - const isHorizontal = (direction === Direction.Left) || (direction === Direction.Right); - - if ((isHorizontal && this.orientation !== Orientation.HORIZONTAL) || - (!isHorizontal && this.orientation !== Orientation.VERTICAL)) { - // Resize the entire pane as a whole - if ( - (this.orientation === Orientation.HORIZONTAL && direction === Direction.Down) || - (this.orientation === Orientation.VERTICAL && direction === Direction.Right) || - (part === Parts.AUXILIARYBAR_PART && direction === Direction.Right) - ) { - amount *= -1; - } - - this._layoutService.resizePart(part, amount, amount); - return; - } - - // Resize left/right in horizontal or up/down in vertical + resizePane(index: number, direction: Direction, amount: number): void { // Only resize when there is more than one pane if (this._children.length <= 1) { return; @@ -570,28 +551,57 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this.setActiveInstanceByIndex(newIndex); } + private _getPosition(): Position { + switch (this._terminalLocation) { + case ViewContainerLocation.Panel: + return this._panelPosition; + case ViewContainerLocation.Sidebar: + return this._layoutService.getSideBarPosition(); + case ViewContainerLocation.AuxiliaryBar: + return this._layoutService.getSideBarPosition() === Position.LEFT ? Position.RIGHT : Position.LEFT; + } + } + + private _getOrientation(): Orientation { + return this._getPosition() === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; + } + resizePane(direction: Direction): void { if (!this._splitPaneContainer) { return; } - const isHorizontal = (direction === Direction.Left || direction === Direction.Right); + const isHorizontalResize = (direction === Direction.Left || direction === Direction.Right); - const part = getPartByLocation(this._terminalLocation); + const groupOrientation = this._getOrientation(); - const isTerminalLeft = this._panelPosition === Position.LEFT || part === Parts.SIDEBAR_PART; - - // Left-positionned panels have inverted controls - // see https://github.com/microsoft/vscode/issues/140873 - const shouldInvertHorizontalResize = (isHorizontal && isTerminalLeft); - - const resizeDirection = shouldInvertHorizontalResize ? direction === Direction.Left ? Direction.Right : Direction.Left : direction; + const shouldResizePart = + (isHorizontalResize && groupOrientation === Orientation.VERTICAL) || + (!isHorizontalResize && groupOrientation === Orientation.HORIZONTAL); const font = this._terminalService.configHelper.getFont(getWindow(this._groupElement)); // TODO: Support letter spacing and line height - const charSize = (isHorizontal ? font.charWidth : font.charHeight); + const charSize = (isHorizontalResize ? font.charWidth : font.charHeight); + if (charSize) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, resizeDirection, charSize * Constants.ResizePartCellCount, part); + let resizeAmount = charSize * Constants.ResizePartCellCount; + + if (shouldResizePart) { + + const shouldShrink = + (this._getPosition() === Position.LEFT && direction === Direction.Left) || + (this._getPosition() === Position.RIGHT && direction === Direction.Right) || + (this._getPosition() === Position.BOTTOM && direction === Direction.Down); + + if (shouldShrink) { + resizeAmount *= -1; + } + + this._layoutService.resizePart(getPartByLocation(this._terminalLocation), resizeAmount, resizeAmount); + } else { + this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, resizeAmount); + } + } } From 3a1f27a6e657002489f8034b1f1f1afebd46ca7e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 09:40:43 +0100 Subject: [PATCH 161/753] Speech: allow `auto` setting for language to derive from display language (fix #206321) (#206322) --- .../browser/accessibilityConfiguration.ts | 89 ++------------- .../contrib/speech/browser/speechService.ts | 4 +- .../contrib/speech/common/speechService.ts | 102 ++++++++++++++++++ .../speech/test/common/speechService.test.ts | 27 +++++ 4 files changed, 138 insertions(+), 84 deletions(-) create mode 100644 src/vs/workbench/contrib/speech/test/common/speechService.test.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 9a42093991c..33a1379d3bd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; import { AccessibilityAlertSettingId, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; -import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, SPEECH_LANGUAGES, SPEECH_LANGUAGE_CONFIG } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Event } from 'vs/base/common/event'; @@ -664,10 +664,9 @@ export function registerAccessibilityConfiguration() { export const enum AccessibilityVoiceSettingId { SpeechTimeout = 'accessibility.voice.speechTimeout', - SpeechLanguage = 'accessibility.voice.speechLanguage' + SpeechLanguage = SPEECH_LANGUAGE_CONFIG } export const SpeechTimeoutDefault = 1200; -const SpeechLanguageDefault = 'en-US'; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { @@ -703,10 +702,10 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen 'tags': ['accessibility'] }, [AccessibilityVoiceSettingId.SpeechLanguage]: { - 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize."), + 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize. Select `auto` to use the configured display language if possible. Note that not all display languages maybe supported by speech recognition"), 'type': 'string', 'enum': languagesSorted, - 'default': SpeechLanguageDefault, + 'default': 'auto', 'tags': ['accessibility'], 'enumDescriptions': languagesSorted.map(key => languages[key].name), 'enumItemLabels': languagesSorted.map(key => languages[key].name) @@ -717,84 +716,10 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen private getLanguages(): { [locale: string]: { name: string } } { return { - ['da-DK']: { - name: localize('speechLanguage.da-DK', "Danish (Denmark)") + ['auto']: { + name: localize('speechLanguage.auto', "Auto (Use Display Language)") }, - ['de-DE']: { - name: localize('speechLanguage.de-DE', "German (Germany)") - }, - ['en-AU']: { - name: localize('speechLanguage.en-AU', "English (Australia)") - }, - ['en-CA']: { - name: localize('speechLanguage.en-CA', "English (Canada)") - }, - ['en-GB']: { - name: localize('speechLanguage.en-GB', "English (United Kingdom)") - }, - ['en-IE']: { - name: localize('speechLanguage.en-IE', "English (Ireland)") - }, - ['en-IN']: { - name: localize('speechLanguage.en-IN', "English (India)") - }, - ['en-NZ']: { - name: localize('speechLanguage.en-NZ', "English (New Zealand)") - }, - [SpeechLanguageDefault]: { - name: localize('speechLanguage.en-US', "English (United States)") - }, - ['es-ES']: { - name: localize('speechLanguage.es-ES', "Spanish (Spain)") - }, - ['es-MX']: { - name: localize('speechLanguage.es-MX', "Spanish (Mexico)") - }, - ['fr-CA']: { - name: localize('speechLanguage.fr-CA', "French (Canada)") - }, - ['fr-FR']: { - name: localize('speechLanguage.fr-FR', "French (France)") - }, - ['hi-IN']: { - name: localize('speechLanguage.hi-IN', "Hindi (India)") - }, - ['it-IT']: { - name: localize('speechLanguage.it-IT', "Italian (Italy)") - }, - ['ja-JP']: { - name: localize('speechLanguage.ja-JP', "Japanese (Japan)") - }, - ['ko-KR']: { - name: localize('speechLanguage.ko-KR', "Korean (South Korea)") - }, - ['nl-NL']: { - name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") - }, - ['pt-PT']: { - name: localize('speechLanguage.pt-PT', "Portuguese (Portugal)") - }, - ['pt-BR']: { - name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") - }, - ['ru-RU']: { - name: localize('speechLanguage.ru-RU', "Russian (Russia)") - }, - ['sv-SE']: { - name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") - }, - ['tr-TR']: { - name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") - }, - ['zh-CN']: { - name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") - }, - ['zh-HK']: { - name: localize('speechLanguage.zh-HK', "Chinese (Traditional, Hong Kong)") - }, - ['zh-TW']: { - name: localize('speechLanguage.zh-TW', "Chinese (Traditional, Taiwan)") - } + ...SPEECH_LANGUAGES }; } } diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index 00943f3262b..d0255933209 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -11,7 +11,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { DeferredPromise } from 'vs/base/common/async'; -import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, ISpeechProvider, HasSpeechProvider, ISpeechToTextSession, SpeechToTextInProgress, IKeywordRecognitionSession, KeywordRecognitionStatus, SpeechToTextStatus, speechLanguageConfigToLanguage, SPEECH_LANGUAGE_CONFIG } from 'vs/workbench/contrib/speech/common/speechService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -80,7 +80,7 @@ export class SpeechService extends Disposable implements ISpeechService { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - const language = this.configurationService.getValue('accessibility.voice.speechLanguage'); + const language = speechLanguageConfigToLanguage(this.configurationService.getValue(SPEECH_LANGUAGE_CONFIG)); const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); const sessionStart = Date.now(); diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index a594b4f3acf..e1bbf0f4f8e 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -10,6 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { language } from 'vs/base/common/platform'; export const ISpeechService = createDecorator('speechService'); @@ -97,3 +98,104 @@ export interface ISpeechService { */ recognizeKeyword(token: CancellationToken): Promise; } + +export const SPEECH_LANGUAGE_CONFIG = 'accessibility.voice.speechLanguage'; + +export const SPEECH_LANGUAGES = { + ['da-DK']: { + name: localize('speechLanguage.da-DK', "Danish (Denmark)") + }, + ['de-DE']: { + name: localize('speechLanguage.de-DE', "German (Germany)") + }, + ['en-AU']: { + name: localize('speechLanguage.en-AU', "English (Australia)") + }, + ['en-CA']: { + name: localize('speechLanguage.en-CA', "English (Canada)") + }, + ['en-GB']: { + name: localize('speechLanguage.en-GB', "English (United Kingdom)") + }, + ['en-IE']: { + name: localize('speechLanguage.en-IE', "English (Ireland)") + }, + ['en-IN']: { + name: localize('speechLanguage.en-IN', "English (India)") + }, + ['en-NZ']: { + name: localize('speechLanguage.en-NZ', "English (New Zealand)") + }, + ['en-US']: { + name: localize('speechLanguage.en-US', "English (United States)") + }, + ['es-ES']: { + name: localize('speechLanguage.es-ES', "Spanish (Spain)") + }, + ['es-MX']: { + name: localize('speechLanguage.es-MX', "Spanish (Mexico)") + }, + ['fr-CA']: { + name: localize('speechLanguage.fr-CA', "French (Canada)") + }, + ['fr-FR']: { + name: localize('speechLanguage.fr-FR', "French (France)") + }, + ['hi-IN']: { + name: localize('speechLanguage.hi-IN', "Hindi (India)") + }, + ['it-IT']: { + name: localize('speechLanguage.it-IT', "Italian (Italy)") + }, + ['ja-JP']: { + name: localize('speechLanguage.ja-JP', "Japanese (Japan)") + }, + ['ko-KR']: { + name: localize('speechLanguage.ko-KR', "Korean (South Korea)") + }, + ['nl-NL']: { + name: localize('speechLanguage.nl-NL', "Dutch (Netherlands)") + }, + ['pt-PT']: { + name: localize('speechLanguage.pt-PT', "Portuguese (Portugal)") + }, + ['pt-BR']: { + name: localize('speechLanguage.pt-BR', "Portuguese (Brazil)") + }, + ['ru-RU']: { + name: localize('speechLanguage.ru-RU', "Russian (Russia)") + }, + ['sv-SE']: { + name: localize('speechLanguage.sv-SE', "Swedish (Sweden)") + }, + ['tr-TR']: { + name: localize('speechLanguage.tr-TR', "Turkish (Turkey)") + }, + ['zh-CN']: { + name: localize('speechLanguage.zh-CN', "Chinese (Simplified, China)") + }, + ['zh-HK']: { + name: localize('speechLanguage.zh-HK', "Chinese (Traditional, Hong Kong)") + }, + ['zh-TW']: { + name: localize('speechLanguage.zh-TW', "Chinese (Traditional, Taiwan)") + } +}; + +export function speechLanguageConfigToLanguage(config: unknown, lang = language): string { + if (typeof config === 'string') { + if (config === 'auto') { + if (lang !== 'en') { + const langParts = lang.split('-'); + + return speechLanguageConfigToLanguage(`${langParts[0]}-${(langParts[1] ?? langParts[0]).toUpperCase()}`); + } + } else { + if (SPEECH_LANGUAGES[config as keyof typeof SPEECH_LANGUAGES]) { + return config; + } + } + } + + return 'en-US'; +} diff --git a/src/vs/workbench/contrib/speech/test/common/speechService.test.ts b/src/vs/workbench/contrib/speech/test/common/speechService.test.ts new file mode 100644 index 00000000000..d757eace7e0 --- /dev/null +++ b/src/vs/workbench/contrib/speech/test/common/speechService.test.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { speechLanguageConfigToLanguage } from 'vs/workbench/contrib/speech/common/speechService'; + +suite('SpeechService', () => { + + test('resolve language', async () => { + assert.strictEqual(speechLanguageConfigToLanguage(undefined), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage(3), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('foo'), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('foo-bar'), 'en-US'); + + assert.strictEqual(speechLanguageConfigToLanguage('tr-TR'), 'tr-TR'); + assert.strictEqual(speechLanguageConfigToLanguage('zh-TW'), 'zh-TW'); + + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'en'), 'en-US'); + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'tr'), 'tr-TR'); + assert.strictEqual(speechLanguageConfigToLanguage('auto', 'zh-tw'), 'zh-TW'); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); From c036c4dadf9d26ea3a6dffdc73ca3f1d04506b6b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Feb 2024 09:56:36 +0100 Subject: [PATCH 162/753] Support local install for more error cases (#206323) --- .../electron-sandbox/remoteExtensionManagementService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 476712eaf1f..68181b0b830 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -65,7 +65,10 @@ export class NativeRemoteExtensionManagementService extends RemoteExtensionManag } catch (error) { switch (error.name) { case ExtensionManagementErrorCode.Download: + case ExtensionManagementErrorCode.DownloadSignature: + case ExtensionManagementErrorCode.Gallery: case ExtensionManagementErrorCode.Internal: + case ExtensionManagementErrorCode.Unknown: try { this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error)); return await this.downloadAndInstall(extension, installOptions || {}); From 82bba2e78bbac1a35809e74cfc878df8a6f193ef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Feb 2024 11:06:22 +0100 Subject: [PATCH 163/753] fix #206249 (#206328) --- src/vs/workbench/api/browser/mainThreadLanguageModels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 6e33d7da99d..f8167d4dc52 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -64,7 +64,7 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { } dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, - label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), + label: localize('languageModels', "Language Model ({0})", `${identifier}`), access: { canToggle: false, }, From e9a8b6add5329f8124f23ff7143d95c22ce39f76 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 12:14:40 +0100 Subject: [PATCH 164/753] Update grammars (#206330) --- extensions/dart/cgmanifest.json | 2 +- extensions/dart/syntaxes/dart.tmLanguage.json | 10 +- extensions/go/cgmanifest.json | 4 +- extensions/go/syntaxes/go.tmLanguage.json | 140 ++++++++++++------ extensions/latex/cgmanifest.json | 4 +- extensions/razor/cgmanifest.json | 2 +- .../razor/syntaxes/cshtml.tmLanguage.json | 80 +++++++++- extensions/scss/cgmanifest.json | 4 +- 8 files changed, 188 insertions(+), 58 deletions(-) diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json index 0086a5158e5..9c90588adf1 100644 --- a/extensions/dart/cgmanifest.json +++ b/extensions/dart/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dart-lang/dart-syntax-highlight", "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "0a6648177bdbb91a4e1a38c16e57ede0ccba4f18" + "commitHash": "272e2f89f85073c04b7e15b582257f76d2489970" } }, "licenseDetail": [ diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json index ae4db9698e9..cc9dee8d275 100644 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ b/extensions/dart/syntaxes/dart.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/0a6648177bdbb91a4e1a38c16e57ede0ccba4f18", + "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/272e2f89f85073c04b7e15b582257f76d2489970", "name": "Dart", "scopeName": "source.dart", "patterns": [ @@ -308,7 +308,7 @@ }, { "name": "keyword.control.dart", - "match": "(?\\-]+(?:\\s*)(?:\\/(?:\\/|\\*).*)?)$)", + "match": "(?:(?<=\\))(?:\\s*)((?:(?:\\s*(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+)?[\\w\\*\\.\\[\\]\\<\\>\\-]+(?:\\s*)(?:\\/(?:\\/|\\*).*)?)$)", "captures": { "1": { "patterns": [ @@ -1272,7 +1267,7 @@ "include": "#parameter-variable-types" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -1513,7 +1508,7 @@ }, "functions_inline": { "comment": "functions in-line with multi return types", - "match": "(?:(\\bfunc\\b)((?:\\((?:[^/]*)\\))(?:\\s+)(?:\\((?:[^/]*)\\)))(?:\\s+)(?=\\{))", + "match": "(?:(\\bfunc\\b)((?:\\((?:[^/]*?)\\))(?:\\s+)(?:\\((?:[^/]*?)\\)))(?:\\s+)(?=\\{))", "captures": { "1": { "name": "keyword.function.go" @@ -1571,7 +1566,7 @@ }, "support_functions": { "comment": "Support Functions", - "match": "(?:(?:((?<=\\.)\\w+)|(\\w+))(\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}\"\\']+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?(?=\\())", + "match": "(?:(?:((?<=\\.)\\b\\w+)|(\\b\\w+))(\\[(?:(?:[\\w\\.\\*\\[\\]\\{\\}\"\\']+)(?:(?:\\,\\s*(?:[\\w\\.\\*\\[\\]\\{\\}]+))*))?\\])?(?=\\())", "captures": { "1": { "name": "entity.name.function.support.go" @@ -1867,11 +1862,18 @@ "patterns": [ { "comment": "Struct variable for struct in struct types", - "begin": "(?:\\s*)?([\\s\\,\\w]+)(?:\\s+)(?:(?:[\\[\\]\\*])+)?(\\bstruct\\b)\\s*(\\{)", + "begin": "(?:(\\w+(?:\\,\\s*\\w+)*)(?:\\s+)(?:(?:[\\[\\]\\*])+)?(\\bstruct\\b)(?:\\s*)(\\{))", "beginCaptures": { "1": { - "match": "(?:\\w+)", - "name": "variable.other.property.go" + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "variable.other.property.go" + } + ] }, "2": { "name": "keyword.struct.go" @@ -1911,6 +1913,42 @@ } }, "patterns": [ + { + "include": "#support_functions" + }, + { + "include": "#type-declarations-without-brackets" + }, + { + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, { "begin": "\\(", "beginCaptures": { @@ -1927,18 +1965,12 @@ "patterns": [ { "include": "#function_param_types" - }, - { - "include": "$self" } ] }, { - "include": "#support_functions" - }, - { - "comment": "single declaration | with or declarations", - "match": "((?:\\s+\\|)?(?:[\\w\\.\\[\\]\\*]+)(?:\\s+\\|)?)", + "comment": "other types", + "match": "([\\w\\.]+)", "captures": { "1": { "patterns": [ @@ -1946,10 +1978,7 @@ "include": "#type-declarations" }, { - "include": "#generic_types" - }, - { - "match": "(?:\\w+)", + "match": "\\w+", "name": "entity.name.type.go" } ] @@ -2145,7 +2174,7 @@ }, "after_control_variables": { "comment": "After control variables, to not highlight as a struct/interface (before formatting with gofmt)", - "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)([[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", + "match": "(?:(?<=\\brange\\b|\\bswitch\\b|\\;|\\bif\\b|\\bfor\\b|\\<|\\>|\\<\\=|\\>\\=|\\=\\=|\\!\\=|\\w(?:\\+|/|\\-|\\*|\\%)|\\w(?:\\+|/|\\-|\\*|\\%)\\=|\\|\\||\\&\\&)(?:\\s*)((?![\\[\\]]+)[[:alnum:]\\-\\_\\!\\.\\[\\]\\<\\>\\=\\*/\\+\\%\\:]+)(?:\\s*)(?=\\{))", "captures": { "1": { "patterns": [ @@ -2234,7 +2263,7 @@ }, { "comment": "make keyword", - "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*(?:[\\w\\.\\(\\)]+)?)+)?(\\))))", + "match": "(?:(\\bmake\\b)(?:(\\()((?:(?:(?:[\\*\\[\\]]+)?(?:\\<\\-\\s*)?\\bchan\\b(?:\\s*\\<\\-)?\\s*)+(?:\\([^\\)]+\\))?)?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?(?:\\[(?:[^\\]]+)?\\])?(?:[\\w\\.\\*\\[\\]\\{\\}]+)?)?((?:\\,\\s*(?:[\\w\\.\\(\\)/\\+\\-\\<\\>\\&\\|\\%\\*]+)?)+)?(\\))))", "captures": { "1": { "name": "entity.name.function.support.builtin.go" @@ -2291,6 +2320,7 @@ } }, "switch_types": { + "comment": "switch type assertions, only highlights types after case keyword", "begin": "(?<=\\bswitch\\b)(?:\\s*)(?:(\\w+\\s*\\:\\=)?\\s*([\\w\\.\\*\\(\\)\\[\\]]+))(\\.\\(\\btype\\b\\)\\s*)(\\{)", "beginCaptures": { "1": { @@ -2299,7 +2329,7 @@ "include": "#operators" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.assignment.go" } ] @@ -2313,7 +2343,7 @@ "include": "#type-declarations" }, { - "match": "(?:\\w+)", + "match": "\\w+", "name": "variable.other.go" } ] @@ -2344,9 +2374,7 @@ }, "patterns": [ { - "include": "#type-declarations" - }, - { + "comment": "types after case keyword with single line", "match": "(?:^\\s*(\\bcase\\b))(?:\\s+)([\\w\\.\\,\\*\\=\\<\\>\\!\\s]+)(:)(\\s*/(?:/|\\*)\\s*.*)?$", "captures": { "1": { @@ -2375,6 +2403,30 @@ } } }, + { + "comment": "types after case keyword with multi lines", + "begin": "\\bcase\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.go" + } + }, + "end": "\\:", + "endCaptures": { + "0": { + "name": "punctuation.other.colon.go" + } + }, + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "\\w+", + "name": "entity.name.type.go" + } + ] + }, { "include": "$self" } @@ -2573,7 +2625,7 @@ } }, "switch_select_case_variables": { - "comment": "variables after case control keyword in switch/select expression", + "comment": "variables after case control keyword in switch/select expression, to not scope them as property variables", "match": "(?:(?:^\\s*(\\bcase\\b))(?:\\s+)([\\s\\S]+(?:\\:)\\s*(?:/(?:/|\\*).*)?)$)", "captures": { "1": { @@ -2587,6 +2639,9 @@ { "include": "#support_functions" }, + { + "include": "#variable_assignment" + }, { "match": "\\w+", "name": "variable.other.go" @@ -2710,7 +2765,7 @@ }, "double_parentheses_types": { "comment": "double parentheses types", - "match": "(?:(\\((?:[\\w\\.\\[\\]\\*\\&]+)\\))(?=\\())", + "match": "(?:(? Date: Tue, 27 Feb 2024 12:15:12 +0100 Subject: [PATCH 165/753] Remove unused code-feature in release notes (#206331) --- src/vs/base/common/network.ts | 5 - .../browser/markdownSettingRenderer.ts | 44 +------- .../update/browser/releaseNotesEditor.ts | 104 +----------------- 3 files changed, 7 insertions(+), 146 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 5cbdad174b7..cfc5b173887 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -117,11 +117,6 @@ export namespace Schemas { * Scheme used for special rendering of settings in the release notes */ export const codeSetting = 'code-setting'; - - /** - * Scheme used for special rendering of features in the release notes - */ - export const codeFeature = 'code-feature'; } export function matchesScheme(target: URI | string, scheme: string): boolean { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index c4cf0aa94fe..095a2978564 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -17,7 +17,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; const codeSettingRegex = /^/; -const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -45,10 +44,10 @@ export class SimpleSettingRenderer { getHtmlRenderer(): (html: string) => string { return (html): string => { - const match = codeSettingRegex.exec(html) ?? codeFeatureRegex.exec(html); + const match = codeSettingRegex.exec(html); if (match && match.length === 4) { const settingId = match[2]; - const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); + const rendered = this.render(settingId, match[3]); if (rendered) { html = html.replace(codeSettingRegex, rendered); } @@ -61,10 +60,6 @@ export class SimpleSettingRenderer { return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; } - featureToUriString(settingId: string, value?: any): string { - return `${Schemas.codeFeature}://${settingId}${value ? `/${value}` : ''}`; - } - private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { if (!this.settingsGroups) { @@ -106,16 +101,13 @@ export class SimpleSettingRenderer { } } - private render(settingId: string, newValue: string, asFeature: boolean): string | undefined { + private render(settingId: string, newValue: string): string | undefined { const setting = this.getSetting(settingId); if (!setting) { return ''; } - if (asFeature) { - return this.renderFeature(setting, newValue); - } else { - return this.renderSetting(setting, newValue); - } + + return this.renderSetting(setting, newValue); } private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { @@ -176,15 +168,6 @@ export class SimpleSettingRenderer { `; } - private renderFeature(setting: ISetting, newValue: string): string | undefined { - const href = this.featureToUriString(setting.key, newValue); - const parsedValue = this.parseValue(setting.key, newValue); - const isChecked = this._configurationService.getValue(setting.key) === parsedValue; - this._featuredSettings.set(setting.key, parsedValue); - const title = nls.localize('changeFeatureTitle', "Toggle feature with setting {0}", setting.key); - return `
`; - } - private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { if (setting.type === 'boolean') { return this.booleanSettingMessage(setting, newValue as boolean); @@ -289,21 +272,6 @@ export class SimpleSettingRenderer { }); } - private async setFeatureState(uri: URI) { - const settingId = uri.authority; - const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); - let valueToSetSetting: any; - if (this._updatedSettings.has(settingId)) { - valueToSetSetting = this._updatedSettings.get(settingId); - this._updatedSettings.delete(settingId); - } else if (newSettingValue !== this._configurationService.getValue(settingId)) { - valueToSetSetting = newSettingValue; - } else { - valueToSetSetting = undefined; - } - await this._configurationService.updateValue(settingId, valueToSetSetting, ConfigurationTarget.USER); - } - async updateSetting(uri: URI, x: number, y: number) { if (uri.scheme === Schemas.codeSetting) { type ReleaseNotesSettingUsedClassification = { @@ -318,8 +286,6 @@ export class SimpleSettingRenderer { settingId: uri.authority }); return this.showContextMenu(uri, x, y); - } else if (uri.scheme === Schemas.codeFeature) { - return this.setFeatureState(uri); } } } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index b5e2de0ed52..9f6cc07ba5f 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -38,7 +38,6 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService export class ReleaseNotesManager { private readonly _simpleSettingRenderer: SimpleSettingRenderer; private readonly _releaseNotesCache = new Map>(); - private scrollPosition: { x: number; y: number } | undefined; private _currentReleaseNotes: WebviewInput | undefined = undefined; private _lastText: string | undefined; @@ -72,11 +71,9 @@ export class ReleaseNotesManager { if (!this._currentReleaseNotes || !this._lastText) { return; } - const captureScroll = this.scrollPosition; const html = await this.renderBody(this._lastText); if (this._currentReleaseNotes) { this._currentReleaseNotes.webview.setHtml(html); - this._currentReleaseNotes.webview.postMessage({ type: 'setScroll', value: { scrollPosition: captureScroll } }); } } @@ -116,8 +113,6 @@ export class ReleaseNotesManager { disposables.add(this._currentReleaseNotes.webview.onMessage(e => { if (e.message.type === 'showReleaseNotes') { this._configurationService.updateValue('update.showReleaseNotes', e.message.value); - } else if (e.message.type === 'scroll') { - this.scrollPosition = e.message.value.scrollPosition; } else if (e.message.type === 'clickSetting') { const x = this._currentReleaseNotes?.webview.container.offsetLeft + e.message.value.x; const y = this._currentReleaseNotes?.webview.container.offsetTop + e.message.value.y; @@ -234,7 +229,7 @@ export class ReleaseNotesManager { } private async onDidClickLink(uri: URI) { - if (uri.scheme === Schemas.codeSetting || uri.scheme === Schemas.codeFeature) { + if (uri.scheme === Schemas.codeSetting) { // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') @@ -353,64 +348,6 @@ export class ReleaseNotesManager { margin-right: 8px; } - /* codefeature */ - - .codefeature-container { - display: flex; - } - - .codefeature { - position: relative; - display: inline-block; - width: 46px; - height: 24px; - } - - .codefeature-container input { - display: none; - } - - .toggle { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--vscode-button-background); - transition: .4s; - border-radius: 24px; - } - - .toggle:before { - position: absolute; - content: ""; - height: 16px; - width: 16px; - left: 4px; - bottom: 4px; - background-color: var(--vscode-editor-foreground); - transition: .4s; - border-radius: 50%; - } - - input:checked+.codefeature > .toggle:before { - transform: translateX(22px); - } - - .codefeature-container:has(input) .title { - line-height: 30px; - padding-left: 4px; - font-weight: bold; - } - - .codefeature-container:has(input:checked) .title:after { - content: "${nls.localize('disableFeature', "Disable this feature")}"; - } - .codefeature-container:has(input:not(:checked)) .title:after { - content: "${nls.localize('enableFeature', "Enable this feature")}"; - } - header { display: flex; align-items: center; padding-top: 1em; } @@ -443,40 +380,13 @@ export class ReleaseNotesManager { window.addEventListener('message', event => { if (event.data.type === 'showReleaseNotes') { input.checked = event.data.value; - } else if (event.data.type === 'setScroll') { - window.scrollTo(event.data.value.scrollPosition.x, event.data.value.scrollPosition.y); - } else if (event.data.type === 'setFeaturedSettings') { - for (const [settingId, value] of event.data.value) { - const setting = document.getElementById(settingId); - if (setting instanceof HTMLInputElement) { - setting.checked = value; - } - } } }); - window.onscroll = () => { - vscode.postMessage({ - type: 'scroll', - value: { - scrollPosition: { - x: window.scrollX, - y: window.scrollY - } - } - }); - }; - window.addEventListener('click', event => { const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; - if (href && (href.startsWith('${Schemas.codeSetting}') || href.startsWith('${Schemas.codeFeature}'))) { + if (href && (href.startsWith('${Schemas.codeSetting}'))) { vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.clientX, y: event.clientY }}); - if (href.startsWith('${Schemas.codeFeature}')) { - const featureInput = event.target.parentElement.previousSibling; - if (featureInput instanceof HTMLInputElement) { - featureInput.checked = !featureInput.checked; - } - } } }); @@ -506,7 +416,6 @@ export class ReleaseNotesManager { private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { this.updateCheckboxWebview(); - this.updateFeaturedSettingsWebview(); } } @@ -518,13 +427,4 @@ export class ReleaseNotesManager { }); } } - - private updateFeaturedSettingsWebview() { - if (this._currentReleaseNotes) { - this._currentReleaseNotes.webview.postMessage({ - type: 'setFeaturedSettings', - value: this._simpleSettingRenderer.featuredSettingStates - }); - } - } } From e378f55a1f9c52edaa4284bf60e990ad3daf2897 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 12:15:34 +0100 Subject: [PATCH 166/753] Add error logging for PortAttributesProvider (#206332) --- src/vs/workbench/api/common/extHostTunnelService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 7200372acca..650b852e1e4 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -103,6 +103,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { + if (portSelector.portRange === undefined && portSelector.commandPattern === undefined) { + this.logService.error('PortAttributesProvider must specify either a portRange or a commandPattern'); + } const providerHandle = this.nextPortAttributesProviderHandle(); this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider }); From 8f44284342a7fe8f566c52bebf8b30746fe5d63e Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 27 Feb 2024 12:28:09 +0100 Subject: [PATCH 167/753] add 'AI' as extension category --- src/vs/platform/extensions/common/extensions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 62977ee07e3..331aba1b55f 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -255,6 +255,7 @@ export const EXTENSION_CATEGORIES = [ 'Testing', 'Themes', 'Visualization', + 'AI', 'Chat', 'Other', ]; From cf03d0e639074ad51db82075e5a9745163af7f50 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 27 Feb 2024 14:17:19 +0100 Subject: [PATCH 168/753] Error: Uncaught exception in sharedProcess (fix #206035) (#206339) --- src/vs/base/parts/ipc/common/ipc.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index f943347519e..06aefade7ad 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1093,6 +1093,9 @@ export namespace ProxyChannel { // Buffer any event that should be supported by // iterating over all property keys and finding them + // However, this will not work for services that + // are lazy and use a Proxy within. For that we + // still need to check later (see below). const mapEventNameToEvent = new Map>(); for (const key in handler) { if (propertyIsEvent(key)) { @@ -1108,11 +1111,17 @@ export namespace ProxyChannel { return eventImpl as Event; } - if (propertyIsDynamicEvent(event)) { - const target = handler[event]; - if (typeof target === 'function') { + const target = handler[event]; + if (typeof target === 'function') { + if (propertyIsDynamicEvent(event)) { return target.call(handler, arg); } + + if (propertyIsEvent(event)) { + mapEventNameToEvent.set(event, Event.buffer(handler[event] as Event, true, undefined, disposables)); + + return mapEventNameToEvent.get(event) as Event; + } } throw new ErrorNoTelemetry(`Event not found: ${event}`); From 0c3664116e7e594bbe5f6d4e5bfc032226d1b62e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 27 Feb 2024 10:36:47 -0300 Subject: [PATCH 169/753] Warm up slash command cache- port candidate to main (#206302) Warm up slash command cache (#206290) Fix #206050 --- .../workbench/contrib/chat/common/chatAgents.ts | 15 +++++++++------ .../contrib/chat/common/chatServiceImpl.ts | 14 +++++++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 4ee4c07f0cf..b1ef64dae9d 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -107,7 +107,10 @@ export const IChatAgentService = createDecorator('chatAgentSe export interface IChatAgentService { _serviceBrand: undefined; - readonly onDidChangeAgents: Event; + /** + * undefined when an agent was removed + */ + readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; @@ -127,8 +130,8 @@ export class ChatAgentService extends Disposable implements IChatAgentService { private readonly _agents = new Map(); - private readonly _onDidChangeAgents = this._register(new Emitter()); - readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + private readonly _onDidChangeAgents = this._register(new Emitter()); + readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; override dispose(): void { super.dispose(); @@ -140,11 +143,11 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`Already registered an agent with id ${agent.id}`); } this._agents.set(agent.id, { agent }); - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(agent); return toDisposable(() => { if (this._agents.delete(agent.id)) { - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(undefined); } }); } @@ -155,7 +158,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No agent with id ${id} registered`); } data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; - this._onDidChangeAgents.fire(); + this._onDidChangeAgents.fire(data.agent); } getDefaultAgent(): IChatAgent | undefined { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6d89b981afc..e28b1e14e28 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,7 +20,7 @@ import { Progress } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -193,6 +193,12 @@ export class ChatService extends Disposable implements IChatService { } this._register(storageService.onWillSaveState(() => this.saveState())); + + this._register(Event.debounce(this.chatAgentService.onDidChangeAgents, () => { }, 500)(() => { + for (const model of this._sessionModels.values()) { + this.warmSlashCommandCache(model); + } + })); } private saveState(): void { @@ -358,9 +364,15 @@ export class ChatService extends Disposable implements IChatService { this.initializeSession(model, CancellationToken.None); } + private warmSlashCommandCache(model: IChatModel, agent?: IChatAgent) { + const agents = agent ? [agent] : this.chatAgentService.getAgents(); + agents.forEach(agent => agent.provideSlashCommands(model, [], CancellationToken.None)); + } + private async initializeSession(model: ChatModel, token: CancellationToken): Promise { try { this.trace('initializeSession', `Initialize session ${model.sessionId}`); + this.warmSlashCommandCache(model); model.startInitialize(); await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); From 03bd0bb8d117dbe4ea13da19b03b82e8b70194a8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 27 Feb 2024 15:16:20 +0100 Subject: [PATCH 170/753] Commenting range resource change proposal (#206346) Part of #185551 --- src/vs/editor/common/languages.ts | 8 +++++++ .../api/browser/mainThreadComments.ts | 8 +++++++ .../workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostComments.ts | 11 +++++++++- .../comments/browser/commentService.ts | 21 ++++++++++++++++++- .../comments/browser/commentsController.ts | 7 ++++++- .../common/extensionsApiProposals.ts | 1 + ...posed.commentingRangeResourcesChanged.d.ts | 11 ++++++++++ 8 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 16550bdef8d..01eb0df109e 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1888,6 +1888,14 @@ export interface CommentThreadChangedEvent { readonly changed: CommentThread[]; } +/** + * @internal + */ +export interface CommentingRangeResources { + schemes: string[]; + uris: URI[]; +} + export interface CodeLens { range: IRange; id?: string; diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 538c754e1b3..f885f89333a 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -550,6 +550,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments provider.updateFeatures(features); } + async $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise { + const provider = this._commentControllers.get(handle); + if (!provider) { + return; + } + this._commentService.setResourcesWithCommentingRanges(provider.id, { schemes, uris: uris.map(uri => URI.revive(uri)) }); + } + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 29b99b8cc41..b512d5da601 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -146,6 +146,7 @@ export interface MainThreadCommentsShape extends IDisposable { $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $updateCommentingRanges(handle: number): void; + $onDidChangeResourcesWithCommentingRanges(handle: number, schemes: string[], uris: UriComponents[]): Promise; } export interface AuthenticationForceNewSessionOptions { diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 0f04b3f2455..18764746b77 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -563,8 +563,17 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return this._commentingRangeProvider; } + private _commentingRangeProviderResourcesChanged: vscode.Disposable | undefined; set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) { this._commentingRangeProvider = provider; + this._commentingRangeProviderResourcesChanged?.dispose(); + this._commentingRangeProviderResourcesChanged = undefined; + if (this._commentingRangeProvider?.onDidChangeResourcesWithCommentingRanges) { + checkProposedApiEnabled(this._extension, 'commentingRangeResourcesChanged'); + this._commentingRangeProviderResourcesChanged = this._commentingRangeProvider.onDidChangeResourcesWithCommentingRanges(e => { + proxy.$onDidChangeResourcesWithCommentingRanges(this.handle, e.schemes, e.resources); + }); + } proxy.$updateCommentingRanges(this.handle); } @@ -695,7 +704,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this._threads.forEach(value => { value.dispose(); }); - + this._commentingRangeProviderResourcesChanged?.dispose(); this._localDisposables.forEach(disposable => disposable.dispose()); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 1072a60aedf..2d9dcdde5e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResources } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; @@ -112,6 +112,8 @@ export interface ICommentService { enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; + setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void; + resourceHasCommentingRanges(resource: URI): boolean; } const CONTINUE_ON_COMMENTS = 'comments.continueOnComments'; @@ -169,6 +171,8 @@ export class CommentService extends Disposable implements ICommentService { private readonly _commentsModel: CommentsModel = this._register(new CommentsModel()); public readonly commentsModel: ICommentsModel = this._commentsModel; + private _commentingRangeResources = new Map(); // owner -> CommentingRangeResources + constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @@ -494,4 +498,19 @@ export class CommentService extends Disposable implements ICommentService { } return changedOwners; } + + setResourcesWithCommentingRanges(owner: string, resources: CommentingRangeResources): void { + this._commentingRangeResources.set(owner, resources); + } + + resourceHasCommentingRanges(resource: URI): boolean { + for (const resources of this._commentingRangeResources.values()) { + if (resources.schemes.includes(resource.scheme)) { + return true; + } else if (resources.uris.some(uri => uri.toString() === resource.toString())) { + return true; + } + } + return false; + } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 4faf49116c0..1f795367c89 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -780,6 +780,7 @@ export class CommentController implements IEditorContribution { public onModelChanged(): void { this.localToDispose.clear(); + this.tryUpdateReservedSpace(); this.removeCommentWidgetsAndStoreCache(); if (!this.editor) { @@ -1194,10 +1195,14 @@ export class CommentController implements IEditorContribution { return; } - const hasCommentsOrRanges = this._commentInfos.some(info => { + const hasCommentsOrRangesInInfo = this._commentInfos.some(info => { const hasRanges = Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length); return hasRanges || (info.threads.length > 0); }); + const uri = this.editor.getModel()?.uri; + const resourceHasCommentingRanges = uri ? this.commentService.resourceHasCommentingRanges(uri) : false; + + const hasCommentsOrRanges = hasCommentsOrRangesInInfo || resourceHasCommentingRanges; if (hasCommentsOrRanges && !this._commentingRangeSpaceReserved && this.commentService.isCommentingEnabled) { this._commentingRangeSpaceReserved = true; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 76ffed1feeb..48e060c3e8f 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -20,6 +20,7 @@ export const allApiProposals = Object.freeze({ codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts', commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts', + commentingRangeResourcesChanged: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts', commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts', contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts', contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts', diff --git a/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts b/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts new file mode 100644 index 00000000000..7a4f22aecf7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.commentingRangeResourcesChanged.d.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface CommentingRangeProvider { + readonly onDidChangeResourcesWithCommentingRanges?: Event<{ schemes: string[]; resources: Uri[] }>; + } +} From 3b8fe3ec81c6f49d109830db8b775beba23196af Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:55:13 +0100 Subject: [PATCH 171/753] Add custom hover to highlight labels and links (#206351) Add custom hover to highlight labels and Links --- .../browser/ui/actionbar/actionViewItems.ts | 2 +- .../ui/highlightedlabel/highlightedLabel.ts | 23 +++++++++++--- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 14 +++++---- src/vs/base/browser/ui/icons/iconSelectBox.ts | 2 +- .../test/browser/highlightedLabel.test.ts | 4 +++ .../gotoSymbol/browser/peek/referencesTree.ts | 9 ++++-- src/vs/platform/opener/browser/link.ts | 19 +++++++++--- .../bulkEdit/browser/preview/bulkEditTree.ts | 2 +- .../browser/outline/documentSymbolsTree.ts | 6 +++- .../contrib/debug/browser/baseDebugView.ts | 2 +- .../contrib/debug/browser/callStackView.ts | 7 +++-- .../contrib/debug/browser/replViewer.ts | 4 +-- .../contrib/debug/browser/variablesView.ts | 2 +- .../debug/test/browser/baseDebugView.test.ts | 4 +-- .../contrib/markers/browser/markersTable.ts | 31 +++++++++++++------ .../markers/browser/markersTreeViewer.ts | 24 ++++++++------ .../preferences/browser/keybindingsEditor.ts | 18 +++++++---- 17 files changed, 117 insertions(+), 56 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 698d711f4dc..df6ec99660c 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -227,7 +227,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this.updateAriaLabel(); if (this.options.hoverDelegate?.showNativeHover) { - /* While custom hover is not supported with context view */ + /* While custom hover is not inside custom hover */ this.element.title = title; } else { if (!this.customHover && title !== '') { diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index c2b41545d79..5aaa8bcc551 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; /** @@ -22,13 +26,15 @@ export interface IHighlightedLabelOptions { * Whether the label supports rendering icons. */ readonly supportIcons?: boolean; + + readonly hoverDelegate?: IHoverDelegate; } /** * A widget which can render a label with substring highlights, often * originating from a filter function like the fuzzy matcher. */ -export class HighlightedLabel { +export class HighlightedLabel extends Disposable { private readonly domNode: HTMLElement; private text: string = ''; @@ -36,13 +42,16 @@ export class HighlightedLabel { private highlights: readonly IHighlight[] = []; private supportIcons: boolean; private didEverRender: boolean = false; + private customHover: ICustomHover | undefined; /** * Create a new {@link HighlightedLabel}. * * @param container The parent container to append to. */ - constructor(container: HTMLElement, options?: IHighlightedLabelOptions) { + constructor(container: HTMLElement, private readonly options?: IHighlightedLabelOptions) { + super(); + this.supportIcons = options?.supportIcons ?? false; this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label')); } @@ -125,10 +134,16 @@ export class HighlightedLabel { dom.reset(this.domNode, ...children); - if (this.title) { + if (this.options?.hoverDelegate?.showNativeHover) { + /* While custom hover is not inside custom hover */ this.domNode.title = this.title; } else { - this.domNode.removeAttribute('title'); + if (!this.customHover && this.title !== '') { + const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); + this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.domNode, this.title)); + } else if (this.customHover) { + this.customHover.update(this.title); + } } this.didEverRender = true; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 5bf35b8ffc2..c0e0544a93b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -109,7 +109,7 @@ export class IconLabel extends Disposable { this.nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container')); if (options?.supportHighlights || options?.supportIcons) { - this.nameNode = new LabelWithHighlights(this.nameContainer, !!options.supportIcons); + this.nameNode = this._register(new LabelWithHighlights(this.nameContainer, !!options.supportIcons)); } else { this.nameNode = new Label(this.nameContainer); } @@ -218,7 +218,7 @@ export class IconLabel extends Disposable { if (!this.descriptionNode) { const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); if (this.creationOptions?.supportDescriptionHighlights) { - this.descriptionNode = new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons }); + this.descriptionNode = this._register(new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons })); } else { this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description')))); } @@ -291,13 +291,15 @@ function splitMatches(labels: string[], separator: string, matches: readonly IMa }); } -class LabelWithHighlights { +class LabelWithHighlights extends Disposable { private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; private options: IIconLabelValueOptions | undefined; - constructor(private container: HTMLElement, private supportIcons: boolean) { } + constructor(private container: HTMLElement, private supportIcons: boolean) { + super(); + } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { if (this.label === label && equals(this.options, options)) { @@ -311,7 +313,7 @@ class LabelWithHighlights { if (!this.singleLabel) { this.container.innerText = ''; this.container.classList.remove('multiple'); - this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons }); + this.singleLabel = this._register(new HighlightedLabel(dom.append(this.container, dom.$('a.label-name', { id: options?.domId })), { supportIcons: this.supportIcons })); } this.singleLabel.set(label, options?.matches, undefined, options?.labelEscapeNewLines); @@ -329,7 +331,7 @@ class LabelWithHighlights { const id = options?.domId && `${options?.domId}_${i}`; const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); - const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons }); + const highlightedLabel = this._register(new HighlightedLabel(dom.append(this.container, name), { supportIcons: this.supportIcons })); highlightedLabel.set(l, m, undefined, options?.labelEscapeNewLines); if (i < label.length - 1) { diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index 465c1dc1181..b59529ffd81 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -81,7 +81,7 @@ export class IconSelectBox extends Disposable { dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode()); if (this.options.showIconInfo) { - this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label'))); + this.iconIdElement = this._register(new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')))); } const iconsDisposables = disposables.add(new MutableDisposable()); diff --git a/src/vs/base/test/browser/highlightedLabel.test.ts b/src/vs/base/test/browser/highlightedLabel.test.ts index 4f5eb5ca015..fe2ceb43d61 100644 --- a/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/src/vs/base/test/browser/highlightedLabel.test.ts @@ -61,5 +61,9 @@ suite('HighlightedLabel', () => { assert.deepStrictEqual(highlights, [{ start: 5, end: 8 }, { start: 10, end: 11 }]); }); + teardown(() => { + label.dispose(); + }); + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts index ec127689cb0..bb76dd67d6c 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts @@ -162,12 +162,14 @@ export class FileReferencesRenderer implements ITreeRenderer, index: number, templateData: OneReferenceTemplate): void { templateData.set(node.element, node.filterData); } - disposeTemplate(): void { + disposeTemplate(templateData: OneReferenceTemplate): void { + templateData.dispose(); } } diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index 2b455fa8dc6..a52cad5c5f0 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -12,6 +12,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import 'vs/css!./link'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface ILinkDescriptor { readonly label: string | HTMLElement; @@ -28,6 +30,8 @@ export interface ILinkOptions { export class Link extends Disposable { private el: HTMLAnchorElement; + private hover?: ICustomHover; + private _enabled: boolean = true; get enabled(): boolean { @@ -68,9 +72,7 @@ export class Link extends Disposable { this.el.tabIndex = link.tabIndex; } - if (typeof link.title !== 'undefined') { - this.el.title = link.title; - } + this.setTooltip(link.title); this._link = link; } @@ -86,9 +88,10 @@ export class Link extends Disposable { this.el = append(container, $('a.monaco-link', { tabIndex: _link.tabIndex ?? 0, href: _link.href, - title: _link.title }, _link.label)); + this.setTooltip(_link.title); + this.el.setAttribute('role', 'button'); const onClickEmitter = this._register(new DomEmitter(this.el, 'click')); @@ -117,4 +120,12 @@ export class Link extends Disposable { this.enabled = true; } + + private setTooltip(title: string | undefined): void { + if (!this.hover && title) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.el, title)); + } else if (this.hover) { + this.hover.update(title); + } + } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index c5fd28d6a8e..e45a95008c3 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -580,7 +580,7 @@ class TextEditElementTemplate { this._icon = document.createElement('div'); container.appendChild(this._icon); - this._label = new HighlightedLabel(container); + this._label = this._disposables.add(new HighlightedLabel(container)); } dispose(): void { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index c4f2a2def5a..3d9af339abe 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -66,6 +66,10 @@ class DocumentSymbolGroupTemplate { readonly labelContainer: HTMLElement, readonly label: HighlightedLabel, ) { } + + dispose() { + this.label.dispose(); + } } class DocumentSymbolTemplate { @@ -107,7 +111,7 @@ export class DocumentSymbolGroupRenderer implements ITreeRenderer implements IT templateDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), lazyButton, localize('debug.lazyButton.tooltip', "Click to expand"))); const value = dom.append(expression, $('span.value')); - const label = new HighlightedLabel(name); + const label = templateDisposable.add(new HighlightedLabel(name)); const inputBoxContainer = dom.append(expression, $('.inputBoxContainer')); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 139928ec9fe..b9d319bad85 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -541,8 +541,8 @@ class SessionsRenderer implements ICompressibleTreeRenderer { renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); assert.strictEqual(value.textContent, 'hey'); assert.strictEqual(label.element.textContent, 'foo:'); - assert.strictEqual(label.element.title, 'string'); variable.value = isWindows ? 'C:\\foo.js:5' : '/foo.js:5'; expression = $('.'); @@ -122,8 +121,9 @@ suite('Debug - Base Debug View', () => { renderVariable(variable, { expression, name, value, label, lazyButton }, false, [], linkDetector); assert.strictEqual(name.className, 'virtual'); assert.strictEqual(label.element.textContent, 'console:'); - assert.strictEqual(label.element.title, 'console'); assert.strictEqual(value.className, 'value number'); + + label.dispose(); }); test('statusbar in debug mode', () => { diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index bb63d92e9d2..44467e7e01d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { ITableContextMenuEvent, ITableEvent, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IOpenEvent, IWorkbenchTableOptions, WorkbenchTable } from 'vs/platform/list/browser/listService'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -43,6 +43,7 @@ interface IMarkerCodeColumnTemplateData { readonly sourceLabel: HighlightedLabel; readonly codeLabel: HighlightedLabel; readonly codeLink: Link; + readonly templateDisposable: DisposableStore; } interface IMarkerFileColumnTemplateData { @@ -121,17 +122,18 @@ class MarkerCodeColumnRenderer implements ITableRenderer { @@ -185,7 +189,9 @@ class MarkerMessageColumnRenderer implements ITableRenderer { @@ -216,7 +222,10 @@ class MarkerFileColumnRenderer implements ITableRenderer { @@ -236,7 +245,9 @@ class MarkerOwnerColumnRenderer implements ITableRenderer { diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 180a378ac81..a3556f80678 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -51,6 +51,8 @@ import { MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/marker import { unsupportedSchemas } from 'vs/platform/markers/common/markerService'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; import Severity from 'vs/base/common/severity'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; interface IResourceMarkersTemplateData { readonly resourceLabel: IResourceLabel; @@ -285,6 +287,7 @@ class MarkerWidget extends Disposable { private readonly icon: HTMLElement; private readonly iconContainer: HTMLElement; private readonly messageAndDetailsContainer: HTMLElement; + private readonly messageAndDetailsContainerHover: ICustomHover; private readonly disposables = this._register(new DisposableStore()); constructor( @@ -304,6 +307,7 @@ class MarkerWidget extends Disposable { this.iconContainer = dom.append(parent, dom.$('')); this.icon = dom.append(this.iconContainer, dom.$('')); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); + this.messageAndDetailsContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.messageAndDetailsContainer, '')); } render(element: Marker, filterData: MarkerFilterData | undefined): void { @@ -366,13 +370,13 @@ class MarkerWidget extends Disposable { const viewState = this.markersViewModel.getViewModel(element); const multiline = !viewState || viewState.multiline; const lineMatches = filterData && filterData.lineMatches || []; - this.messageAndDetailsContainer.title = element.marker.message; + this.messageAndDetailsContainerHover.update(element.marker.message); const lineElements: HTMLElement[] = []; for (let index = 0; index < (multiline ? lines.length : 1); index++) { const lineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); const messageElement = dom.append(lineElement, dom.$('.marker-message')); - const highlightedLabel = new HighlightedLabel(messageElement); + const highlightedLabel = this.disposables.add(new HighlightedLabel(messageElement)); highlightedLabel.set(lines[index].length > 1000 ? `${lines[index].substring(0, 1000)}...` : lines[index], lineMatches[index]); if (lines[index] === '') { lineElement.style.height = `${VirtualDelegate.LINE_HEIGHT}px`; @@ -387,18 +391,18 @@ class MarkerWidget extends Disposable { parent.classList.add('details-container'); if (marker.source || marker.code) { - const source = new HighlightedLabel(dom.append(parent, dom.$('.marker-source'))); + const source = this.disposables.add(new HighlightedLabel(dom.append(parent, dom.$('.marker-source')))); const sourceMatches = filterData && filterData.sourceMatches || []; source.set(marker.source, sourceMatches); if (marker.code) { if (typeof marker.code === 'string') { - const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code'))); + const code = this.disposables.add(new HighlightedLabel(dom.append(parent, dom.$('.marker-code')))); const codeMatches = filterData && filterData.codeMatches || []; code.set(marker.code, codeMatches); } else { const container = dom.$('.marker-code'); - const code = new HighlightedLabel(container); + const code = this.disposables.add(new HighlightedLabel(container)); const link = marker.code.target.toString(true); this.disposables.add(new Link(parent, { href: link, label: container, title: link }, undefined, this._openerService)); const codeMatches = filterData && filterData.codeMatches || []; @@ -443,15 +447,15 @@ export class RelatedInformationRenderer implements ITreeRenderer Date: Tue, 27 Feb 2024 10:10:08 -0600 Subject: [PATCH 172/753] progress on AI TextSearchProvider (#205319) These are the initial steps to having an API to contribute AI text results. --- .../workbench/api/browser/mainThreadSearch.ts | 24 +- .../workbench/api/common/extHost.api.impl.ts | 6 + .../workbench/api/common/extHost.protocol.ts | 2 + src/vs/workbench/api/common/extHostSearch.ts | 51 +++- .../api/test/node/extHostSearch.test.ts | 4 + .../search/test/browser/searchModel.test.ts | 26 +- .../common/extensionsApiProposals.ts | 1 + .../services/search/common/search.ts | 27 +- .../services/search/common/searchExtTypes.ts | 40 +++ .../services/search/common/searchService.ts | 78 +++++- .../search/common/textSearchManager.ts | 31 ++- .../services/search/node/textSearchManager.ts | 2 +- .../vscode.proposed.aiTextSearchProvider.d.ts | 261 ++++++++++++++++++ 13 files changed, 516 insertions(+), 37 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadSearch.ts b/src/vs/workbench/api/browser/mainThreadSearch.ts index 797a4ee92a4..236148f08ff 100644 --- a/src/vs/workbench/api/browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/browser/mainThreadSearch.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IFileQuery, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, QueryType, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { ExtHostContext, ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; import { revive } from 'vs/base/common/marshalling'; @@ -38,6 +38,10 @@ export class MainThreadSearch implements MainThreadSearchShape { this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.text, scheme, handle, this._proxy)); } + $registerAITextSearchProvider(handle: number, scheme: string): void { + this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.aiText, scheme, handle, this._proxy)); + } + $registerFileSearchProvider(handle: number, scheme: string): void { this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy)); } @@ -64,7 +68,6 @@ export class MainThreadSearch implements MainThreadSearchShape { provider.handleFindMatch(session, data); } - $handleTelemetry(eventName: string, data: any): void { this._telemetryService.publicLog(eventName, data); } @@ -126,7 +129,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { return this.doSearch(query, onProgress, token); } - doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { + doSearch(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { if (!query.folderQueries.length) { throw new Error('Empty folderQueries'); } @@ -134,9 +137,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { const search = new SearchOperation(onProgress); this._searches.set(search.id, search); - const searchP = query.type === QueryType.File - ? this._proxy.$provideFileSearchResults(this._handle, search.id, query, token) - : this._proxy.$provideTextSearchResults(this._handle, search.id, query, token); + const searchP = this._provideSearchResults(query, search.id, token); return Promise.resolve(searchP).then((result: ISearchCompleteStats) => { this._searches.delete(search.id); @@ -169,4 +170,15 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { } }); } + + private _provideSearchResults(query: ISearchQuery, session: number, token: CancellationToken): Promise { + switch (query.type) { + case QueryType.File: + return this._proxy.$provideFileSearchResults(this._handle, session, query, token); + case QueryType.Text: + return this._proxy.$provideTextSearchResults(this._handle, session, query, token); + default: + return this._proxy.$provideAITextSearchResults(this._handle, session, query, token); + } + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 65b718f3f05..20b128c9af4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1112,6 +1112,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'textSearchProvider'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, + registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { + // there are some dependencies on textSearchProvider, so we need to check for both + checkProposedApiEnabled(extension, 'aiTextSearchProvider'); + checkProposedApiEnabled(extension, 'textSearchProvider'); + return extHostSearch.registerAITextSearchProvider(scheme, provider); + }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { checkProposedApiEnabled(extension, 'resolvers'); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b512d5da601..869fc7dc50d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1404,6 +1404,7 @@ export interface MainThreadLabelServiceShape extends IDisposable { export interface MainThreadSearchShape extends IDisposable { $registerFileSearchProvider(handle: number, scheme: string): void; + $registerAITextSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; $handleFileMatch(handle: number, session: number, data: UriComponents[]): void; @@ -1817,6 +1818,7 @@ export interface ExtHostSecretStateShape { export interface ExtHostSearchShape { $enableExtensionHostSearch(): void; $provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise; + $provideAITextSearchResults(handle: number, session: number, query: search.IRawAITextQuery, token: CancellationToken): Promise; $provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise; $clearCache(cacheKey: string): Promise; } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index d0289669abf..c2e0b93f7b9 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -11,13 +11,14 @@ import { FileSearchManager } from 'vs/workbench/services/search/common/fileSearc import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery } from 'vs/workbench/services/search/common/search'; +import { IRawFileQuery, ISearchCompleteStats, IFileQuery, IRawTextQuery, IRawQuery, ITextQuery, IFolderQuery, IRawAITextQuery, IAITextQuery } from 'vs/workbench/services/search/common/search'; import { URI, UriComponents } from 'vs/base/common/uri'; import { TextSearchManager } from 'vs/workbench/services/search/common/textSearchManager'; import { CancellationToken } from 'vs/base/common/cancellation'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } @@ -31,6 +32,10 @@ export class ExtHostSearch implements ExtHostSearchShape { private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); + + private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchUsedSchemes = new Set(); + private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); @@ -62,6 +67,22 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { + if (this._aiTextSearchUsedSchemes.has(scheme)) { + throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); + } + + this._aiTextSearchUsedSchemes.add(scheme); + const handle = this._handlePool++; + this._aiTextSearchProvider.set(handle, provider); + this._proxy.$registerAITextSearchProvider(handle, this._transformScheme(scheme)); + return toDisposable(() => { + this._aiTextSearchUsedSchemes.delete(scheme); + this._aiTextSearchProvider.delete(handle); + this._proxy.$unregisterProvider(handle); + }); + } + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable { if (this._fileSearchUsedSchemes.has(scheme)) { throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); @@ -86,7 +107,7 @@ export class ExtHostSearch implements ExtHostSearchShape { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }, token); } else { - throw new Error('unknown provider: ' + handle); + throw new Error('3 unknown provider: ' + handle); } } @@ -103,7 +124,7 @@ export class ExtHostSearch implements ExtHostSearchShape { $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: vscode.CancellationToken): Promise { const provider = this._textSearchProvider.get(handle); if (!provider || !provider.provideTextSearchResults) { - throw new Error(`Unknown provider ${handle}`); + throw new Error(`2 Unknown provider ${handle}`); } const query = reviveQuery(rawQuery); @@ -111,17 +132,35 @@ export class ExtHostSearch implements ExtHostSearchShape { return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); } + $provideAITextSearchResults(handle: number, session: number, rawQuery: IRawAITextQuery, token: vscode.CancellationToken): Promise { + const provider = this._aiTextSearchProvider.get(handle); + if (!provider || !provider.provideAITextSearchResults) { + throw new Error(`1 Unknown provider ${handle}`); + } + + const query = reviveQuery(rawQuery); + const engine = this.createAITextSearchManager(query, provider); + return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token); + } + $enableExtensionHostSearch(): void { } protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider): TextSearchManager { - return new TextSearchManager(query, provider, { - readdir: resource => Promise.resolve([]), // TODO@rob implement + return new TextSearchManager({ query, provider }, { + readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding }, 'textSearchProvider'); } + + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider): TextSearchManager { + return new TextSearchManager({ query, provider }, { + readdir: resource => Promise.resolve([]), + toCanonicalName: encoding => encoding + }, 'aiTextSearchProvider'); + } } -export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { +export function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : U extends IRawAITextQuery ? IAITextQuery : IFileQuery { return { ...rawQuery, // TODO@rob ??? ...{ diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 1d903c40815..1502ef3f565 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -43,6 +43,10 @@ class MockMainThreadSearch implements MainThreadSearchShape { this.lastHandle = handle; } + $registerAITextSearchProvider(handle: number, scheme: string): void { + this.lastHandle = handle; + } + $unregisterProvider(handle: number): void { } diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 24e2448a9b3..17f9e88b338 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -14,7 +14,7 @@ import { ModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextQuery, ITextSearchMatch, OneLineRange, QueryType, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { IAITextQuery, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextQuery, ITextSearchMatch, OneLineRange, QueryType, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { CellMatch, MatchInNotebook, SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; @@ -122,6 +122,14 @@ suite('SearchModel', () => { }); }, + aiTextSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + return new Promise(resolve => { + queueMicrotask(() => { + results.forEach(onProgress!); + resolve(complete!); + }); + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { return { syncResults: { @@ -153,6 +161,11 @@ suite('SearchModel', () => { }); }); }, + aiTextSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + return new Promise((resolve, reject) => { + reject(error); + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { return { syncResults: { @@ -188,6 +201,17 @@ suite('SearchModel', () => { }); }); }, + aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { + const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); + if (disposable) { + store.add(disposable); + } + + return Promise.resolve({ + results: [], + messages: [] + }); + }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); if (disposable) { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 48e060c3e8f..7e70af867c3 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -8,6 +8,7 @@ export const allApiProposals = Object.freeze({ activeComment: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts', aiRelatedInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts', + aiTextSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts', authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index ca93adc467b..0ebb5a9f69d 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -44,6 +44,7 @@ export const ISearchService = createDecorator('searchService'); export interface ISearchService { readonly _serviceBrand: undefined; textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; + aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined, notebookFilesToIgnore?: ResourceSet, asyncNotebookFilesToIgnore?: Promise): { syncResults: ISearchComplete; asyncResults: Promise }; fileSearch(query: IFileQuery, token?: CancellationToken): Promise; clearCache(cacheKey: string): Promise; @@ -55,7 +56,8 @@ export interface ISearchService { */ export const enum SearchProviderType { file, - text + text, + aiText } export interface ISearchResultProvider { @@ -123,17 +125,32 @@ export interface ITextQueryProps extends ICommonQueryPr userDisabledExcludesAndIgnoreFiles?: boolean; } +export interface IAITextQueryProps extends ICommonQueryProps { + type: QueryType.aiText; + contentPattern: string; + + previewOptions?: ITextSearchPreviewOptions; + maxFileSize?: number; + afterContext?: number; + beforeContext?: number; + + userDisabledExcludesAndIgnoreFiles?: boolean; +} + export type IFileQuery = IFileQueryProps; export type IRawFileQuery = IFileQueryProps; export type ITextQuery = ITextQueryProps; export type IRawTextQuery = ITextQueryProps; +export type IAITextQuery = IAITextQueryProps; +export type IRawAITextQuery = IAITextQueryProps; -export type IRawQuery = IRawTextQuery | IRawFileQuery; -export type ISearchQuery = ITextQuery | IFileQuery; +export type IRawQuery = IRawTextQuery | IRawFileQuery | IRawAITextQuery; +export type ISearchQuery = ITextQuery | IFileQuery | IAITextQuery; export const enum QueryType { File = 1, - Text = 2 + Text = 2, + aiText = 3 } /* __GDPR__FRAGMENT__ @@ -249,7 +266,7 @@ export const enum SearchCompletionExitCode { } export interface ITextSearchStats { - type: 'textSearchProvider' | 'searchProcess'; + type: 'textSearchProvider' | 'searchProcess' | 'aiTextSearchProvider'; } export interface IFileSearchStats { diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 6d037174bed..48da6b3f96d 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -221,6 +221,35 @@ export interface TextSearchOptions extends SearchOptions { */ afterContext?: number; } +/** + * Options that apply to AI text search. + */ +export interface AITextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; +} /** * Represents the severity of a TextSearchComplete message. @@ -390,6 +419,17 @@ export interface TextSearchProvider { provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; } +export interface AITextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameter for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: IProgress, token: CancellationToken): ProviderResult; +} + /** * Options that can be set on a findTextInFiles search. */ diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index ac934456627..42ffb1111ed 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -21,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; +import { deserializeSearchError, FileMatch, IAITextQuery, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class SearchService extends Disposable implements ISearchService { @@ -30,9 +30,11 @@ export class SearchService extends Disposable implements ISearchService { private readonly fileSearchProviders = new Map(); private readonly textSearchProviders = new Map(); + private readonly aiTextSearchProviders = new Map(); private deferredFileSearchesByScheme = new Map>(); private deferredTextSearchesByScheme = new Map>(); + private deferredAITextSearchesByScheme = new Map>(); private loggedSchemesMissingProviders = new Set(); @@ -57,6 +59,9 @@ export class SearchService extends Disposable implements ISearchService { } else if (type === SearchProviderType.text) { list = this.textSearchProviders; deferredMap = this.deferredTextSearchesByScheme; + } else if (type === SearchProviderType.aiText) { + list = this.aiTextSearchProviders; + deferredMap = this.deferredAITextSearchesByScheme; } else { throw new Error('Unknown SearchProviderType'); } @@ -84,6 +89,24 @@ export class SearchService extends Disposable implements ISearchService { }; } + async aiTextSearch(query: IAITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { + const onProviderProgress = (progress: ISearchProgressItem) => { + // Match + if (onProgress) { // don't override open editor results + if (isFileMatch(progress)) { + onProgress(progress); + } else { + onProgress(progress); + } + } + + if (isProgressMessage(progress)) { + this.logService.debug('SearchService#search', progress.message); + } + }; + return this.doSearch(query, token, onProviderProgress); + } + textSearchSplitSyncAsync( query: ITextQuery, token?: CancellationToken | undefined, @@ -205,9 +228,7 @@ export class SearchService extends Disposable implements ISearchService { } private async waitForProvider(queryType: QueryType, scheme: string): Promise { - const deferredMap: Map> = queryType === QueryType.File ? - this.deferredFileSearchesByScheme : - this.deferredTextSearchesByScheme; + const deferredMap: Map> = this.getDeferredTextSearchesByScheme(queryType); if (deferredMap.has(scheme)) { return deferredMap.get(scheme)!.p; @@ -218,6 +239,32 @@ export class SearchService extends Disposable implements ISearchService { } } + private getSearchProvider(type: QueryType): Map { + switch (type) { + case QueryType.File: + return this.fileSearchProviders; + case QueryType.Text: + return this.textSearchProviders; + case QueryType.aiText: + return this.aiTextSearchProviders; + default: + throw new Error(`Unknown query type: ${type}`); + } + } + + private getDeferredTextSearchesByScheme(type: QueryType): Map> { + switch (type) { + case QueryType.File: + return this.deferredFileSearchesByScheme; + case QueryType.Text: + return this.deferredTextSearchesByScheme; + case QueryType.aiText: + return this.deferredAITextSearchesByScheme; + default: + throw new Error(`Unknown query type: ${type}`); + } + } + private async searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void, token?: CancellationToken) { const e2eSW = StopWatch.create(false); @@ -225,16 +272,12 @@ export class SearchService extends Disposable implements ISearchService { const fqs = this.groupFolderQueriesByScheme(query); const someSchemeHasProvider = [...fqs.keys()].some(scheme => { - return query.type === QueryType.File ? - this.fileSearchProviders.has(scheme) : - this.textSearchProviders.has(scheme); + return this.getSearchProvider(query.type).has(scheme); }); await Promise.all([...fqs.keys()].map(async scheme => { const schemeFQs = fqs.get(scheme)!; - let provider = query.type === QueryType.File ? - this.fileSearchProviders.get(scheme) : - this.textSearchProviders.get(scheme); + let provider = this.getSearchProvider(query.type).get(scheme); if (!provider) { if (someSchemeHasProvider) { @@ -259,9 +302,18 @@ export class SearchService extends Disposable implements ISearchService { } }; - searchPs.push(query.type === QueryType.File ? - provider.fileSearch(oneSchemeQuery, token) : - provider.textSearch(oneSchemeQuery, onProviderProgress, token)); + const doProviderSearch = () => { + switch (query.type) { + case QueryType.File: + return provider.fileSearch(oneSchemeQuery, token); + case QueryType.Text: + return provider.textSearch(oneSchemeQuery, onProviderProgress, token); + default: + return provider.textSearch(oneSchemeQuery, onProviderProgress, token); + } + }; + + searchPs.push(doProviderSearch()); })); return Promise.all(searchPs).then(completes => { diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index cb4af3dd4e9..eb83a7ae27e 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -11,14 +11,20 @@ import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { hasSiblingPromiseFn, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; -import { Range, TextSearchComplete, TextSearchMatch, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; +import { hasSiblingPromiseFn, IAITextQuery, IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, ITextSearchStats, QueryGlobTester, QueryType, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; +import { AITextSearchProvider, Range, TextSearchComplete, TextSearchMatch, TextSearchOptions, TextSearchProvider, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes'; export interface IFileUtils { readdir: (resource: URI) => Promise; toCanonicalName: (encoding: string) => string; } +interface IAITextQueryProviderPair { + query: IAITextQuery; provider: AITextSearchProvider; +} +interface ITextQueryProviderPair { + query: ITextQuery; provider: TextSearchProvider; +} export class TextSearchManager { private collector: TextSearchResultsCollector | null = null; @@ -26,7 +32,13 @@ export class TextSearchManager { private isLimitHit = false; private resultCount = 0; - constructor(private query: ITextQuery, private provider: TextSearchProvider, private fileUtils: IFileUtils, private processType: ITextSearchStats['type']) { } + constructor(private queryProviderPair: IAITextQueryProviderPair | ITextQueryProviderPair, + private fileUtils: IFileUtils, + private processType: ITextSearchStats['type']) { } + + private get query() { + return this.queryProviderPair.query; + } search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const folderQueries = this.query.folderQueries || []; @@ -146,7 +158,14 @@ export class TextSearchManager { }; const searchOptions = this.getSearchOptionsForFolder(folderQuery); - const result = await this.provider.provideTextSearchResults(patternInfoToQuery(this.query.contentPattern), searchOptions, progress, token); + + + let result; + if (this.queryProviderPair.query.type === QueryType.aiText) { + result = await (this.queryProviderPair as IAITextQueryProviderPair).provider.provideAITextSearchResults(this.queryProviderPair.query.contentPattern, searchOptions, progress, token); + } else { + result = await (this.queryProviderPair as ITextQueryProviderPair).provider.provideTextSearchResults(patternInfoToQuery(this.queryProviderPair.query.contentPattern), searchOptions, progress, token); + } if (testingPs.length) { await Promise.all(testingPs); } @@ -196,7 +215,9 @@ export class TextSearchManager { afterContext: this.query.afterContext, beforeContext: this.query.beforeContext }; - (options).usePCRE2 = this.query.usePCRE2; + if ('usePCRE2' in this.query) { + (options).usePCRE2 = this.query.usePCRE2; + } return options; } } diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 42cb5e59e80..34cf4cce311 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -12,7 +12,7 @@ import { TextSearchManager } from 'vs/workbench/services/search/common/textSearc export class NativeTextSearchManager extends TextSearchManager { constructor(query: ITextQuery, provider: TextSearchProvider, _pfs: typeof pfs = pfs, processType: ITextSearchStats['type'] = 'searchProcess') { - super(query, provider, { + super({ query, provider }, { readdir: resource => _pfs.Promises.readdir(resource.fsPath), toCanonicalName: name => toCanonicalName(name) }, processType); diff --git a/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts new file mode 100644 index 00000000000..93f9761211b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/205317 + + /** + * The parameters of a query for text search. + */ + export interface TextSearchQuery { + /** + * The text pattern to search for. + */ + pattern: string; + + /** + * Whether or not `pattern` should match multiple lines of text. + */ + isMultiline?: boolean; + + /** + * Whether or not `pattern` should be interpreted as a regular expression. + */ + isRegExp?: boolean; + + /** + * Whether or not the search should be case-sensitive. + */ + isCaseSensitive?: boolean; + + /** + * Whether or not to search for whole word matches only. + */ + isWordMatch?: boolean; + } + + /** + * Options common to file and text search + */ + export interface SearchOptions { + /** + * The root folder to search within. + */ + folder: Uri; + + /** + * Files that match an `includes` glob pattern should be included in the search. + */ + includes: GlobString[]; + + /** + * Files that match an `excludes` glob pattern should be excluded from the search. + */ + excludes: GlobString[]; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles: boolean; + } + + /** + * Options to specify the size of the result text preview. + * These options don't affect the size of the match itself, just the amount of preview text. + */ + export interface TextSearchPreviewOptions { + /** + * The maximum number of lines in the preview. + * Only search providers that support multiline search will ever return more than one line in the match. + */ + matchLines: number; + + /** + * The maximum number of characters included per line. + */ + charsPerLine: number; + } + + /** + * Options that apply to AI text search. + */ + export interface AITextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + + + /** + * A message regarding a completed search. + */ + export interface TextSearchCompleteMessage { + /** + * Markdown text of the message. + */ + text: string; + /** + * Whether the source of the message is trusted, command links are disabled for untrusted message sources. + * Messaged are untrusted by default. + */ + trusted?: boolean; + /** + * The message type, this affects how the message will be rendered. + */ + type: TextSearchCompleteMessageType; + } + + /** + * Information collected when text search is complete. + */ + export interface TextSearchComplete { + /** + * Whether the search hit the limit on the maximum number of search results. + * `maxResults` on {@linkcode AITextSearchOptions} specifies the max number of results. + * - If exactly that number of matches exist, this should be false. + * - If `maxResults` matches are returned and more exist, this should be true. + * - If search hits an internal limit which is less than `maxResults`, this should be true. + */ + limitHit?: boolean; + + /** + * Additional information regarding the state of the completed search. + * + * Messages with "Information" style support links in markdown syntax: + * - Click to [run a command](command:workbench.action.OpenQuickPick) + * - Click to [open a website](https://aka.ms) + * + * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again. + */ + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; + } + + /** + * A preview of the text result. + */ + export interface TextSearchMatchPreview { + /** + * The matching lines of text, or a portion of the matching line that contains the match. + */ + text: string; + + /** + * The Range within `text` corresponding to the text of the match. + * The number of matches must match the TextSearchMatch's range property. + */ + matches: Range | Range[]; + } + + /** + * A match from a text search + */ + export interface TextSearchMatch { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * The range of the match within the document, or multiple ranges for multiple matches. + */ + ranges: Range | Range[]; + + /** + * A preview of the text match. + */ + preview: TextSearchMatchPreview; + } + + /** + * A line of context surrounding a TextSearchMatch. + */ + export interface TextSearchContext { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * One line of text. + * previewOptions.charsPerLine applies to this + */ + text: string; + + /** + * The line number of this line of context. + */ + lineNumber: number; + } + + + /** + * An AITextSearchProvider provides additional AI text search results in the workspace. + */ + export interface AITextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameter for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideAITextSearchResults(query: string, options: AITextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register an AI text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerAITextSearchProvider(scheme: string, provider: AITextSearchProvider): Disposable; + } +} From 1025d0dafbc2ab15f18f7e74fd0850ce38ba414e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:33:49 +0100 Subject: [PATCH 173/753] Render hovers over context views (#206353) render hovers over context views --- src/vs/base/browser/ui/contextview/contextview.ts | 5 ++++- src/vs/editor/browser/services/hoverService/hoverService.ts | 3 +++ src/vs/platform/contextview/browser/contextView.ts | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index af49847a810..a7debba4e6c 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -60,6 +60,9 @@ export interface IDelegate { canRelayout?: boolean; // default: true onDOMEvent?(e: Event, activeElement: HTMLElement): void; onHide?(data?: unknown): void; + + // context views with higher layers are rendered over contet views with lower layers + layer?: number; // Default: 0 } export interface IContextViewProvider { @@ -222,7 +225,7 @@ export class ContextView extends Disposable { this.view.className = 'context-view'; this.view.style.top = '0px'; this.view.style.left = '0px'; - this.view.style.zIndex = '2575'; + this.view.style.zIndex = `${2575 + (delegate.layer ?? 0)}`; this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute'; DOM.show(this.view); diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index f3d6b5c9f16..38357608b86 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -194,6 +194,9 @@ function getHoverOptionsIdentity(options: IHoverOptions | undefined): IHoverOpti class HoverContextViewDelegate implements IDelegate { + // Render over all other context views + public readonly layer = 1; + get anchorPosition() { return this._hover.anchor; } diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 10158c8d75c..87c58811d8d 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -43,6 +43,9 @@ export interface IContextViewDelegate { focus?(): void; anchorAlignment?: AnchorAlignment; anchorAxisAlignment?: AnchorAxisAlignment; + + // context views with higher layers are rendered over contet views with lower layers + layer?: number; // Default: 0 } export const IContextMenuService = createDecorator('contextMenuService'); From ef300d9945b03454b84ac03d7e4562f3e9c39d75 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 27 Feb 2024 09:04:54 -0800 Subject: [PATCH 174/753] Fix extra background in markdown code blocks (#206304) Fixes #205129 --- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 4 ++-- .../workbench/contrib/webview/browser/pre/index-no-csp.html | 4 ++++ src/vs/workbench/contrib/webview/browser/pre/index.html | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 8ac086cada8..9b18b5cd028 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -205,7 +205,7 @@ export async function renderMarkdownDocument( } if (typeof lang !== 'string') { - callback(null, `${escape(code)}`); + callback(null, escape(code)); return ''; } @@ -217,7 +217,7 @@ export async function renderMarkdownDocument( const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); const html = await tokenizeToString(languageService, code, languageId); - callback(null, `${html}`); + callback(null, html); }); return ''; }; diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 8b22da14204..45a4086cc9e 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -128,6 +128,10 @@ border-radius: 4px; } + pre code { + padding: 0; + } + blockquote { background: var(--vscode-textBlockQuote-background); border-color: var(--vscode-textBlockQuote-border); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 277a619ddc7..0bf6401663d 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-zUToXLPvfhhGwJ9G2aKXxI1DL+LnafBpNyx1mQ/S5qU=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Tue, 27 Feb 2024 18:14:05 +0100 Subject: [PATCH 175/753] refine language model (#206358) * api - use `LanguageModelChat` prefix for messages and response, add todos * update todos, tweak how `chatRequest` errors, remove `LanguageModelChatResponse#result` * api - refine language model access removes `requestLanguageModelAccess`, removes `LanguageModelChatResponse#result`, adds `LanguageModelChatRequestOptions`, refines how errors happen when making a chat request * use `throw` over Promise.reject * don't error from `_getAuthAccess`, polish error messages * rename to `sendChatRequest` --- .../workbench/api/common/extHost.api.impl.ts | 29 +-- .../api/common/extHostLanguageModels.ts | 185 ++++++++---------- .../api/common/extHostTypeConverters.ts | 16 +- src/vs/workbench/api/common/extHostTypes.ts | 6 +- .../vscode.proposed.chatProvider.d.ts | 2 +- .../vscode.proposed.languageModels.d.ts | 136 +++++-------- 6 files changed, 152 insertions(+), 222 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 20b128c9af4..1b9c7eb2b08 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable } from 'vs/base/common/lifecycle'; @@ -1431,10 +1431,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: lm const lm: typeof vscode.lm = { - requestLanguageModelAccess(id, options) { - checkProposedApiEnabled(extension, 'languageModels'); - return extHostChatProvider.requestLanguageModelAccess(extension, id, options); - }, get languageModels() { checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.getLanguageModelIds(); @@ -1443,19 +1439,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); }, - chatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, token?: vscode.CancellationToken) { + sendChatRequest(languageModel: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: vscode.CancellationToken) { checkProposedApiEnabled(extension, 'languageModels'); - let options: Record; - if (CancellationToken.isCancellationToken(optionsOrToken)) { - options = {}; - token = optionsOrToken; - } else if (CancellationToken.isCancellationToken(token)) { - options = optionsOrToken; - token = token; - } else { - throw new Error('Invalid arguments'); - } - return extHostChatProvider.makeChatRequest(extension, languageModel, messages, options, token); + return extHostChatProvider.sendChatRequest(extension, languageModel, messages, options, token); } }; @@ -1699,9 +1685,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatRequestTurn: extHostTypes.ChatRequestTurn, ChatResponseTurn: extHostTypes.ChatResponseTurn, - LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, - LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, - LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, + LanguageModelChatSystemMessage: extHostTypes.LanguageModelChatSystemMessage, + LanguageModelChatUserMessage: extHostTypes.LanguageModelChatUserMessage, + LanguageModelChatAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, + LanguageModelSystemMessage: extHostTypes.LanguageModelChatSystemMessage, + LanguageModelUserMessage: extHostTypes.LanguageModelChatUserMessage, + LanguageModelAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, InlineEdit: extHostTypes.InlineEdit, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index aa45f8f58aa..e51c875984d 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -12,12 +12,12 @@ import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { AsyncIterableSource } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; +import { AsyncIterableSource, Barrier } from 'vs/base/common/async'; +import { Emitter } from 'vs/base/common/event'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { localize } from 'vs/nls'; import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { CancellationError } from 'vs/base/common/errors'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -36,43 +36,23 @@ class LanguageModelResponseStream { } } -class LanguageModelRequest { +class LanguageModelResponse { - static fromError(err: Error): vscode.LanguageModelResponse { - return new LanguageModelRequest(Promise.reject(err), new CancellationTokenSource()).apiObject; - } - - readonly apiObject: vscode.LanguageModelResponse; + readonly apiObject: vscode.LanguageModelChatResponse; private readonly _responseStreams = new Map(); private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; + private _isStreaming: boolean = false; + + constructor() { - constructor( - promise: Promise, - readonly cts: CancellationTokenSource - ) { const that = this; this.apiObject = { - result: promise, + // result: promise, stream: that._defaultStream.asyncIterable, - // responses: AsyncIterable[] // FUTURE responses per N + // streams: AsyncIterable[] // FUTURE responses per N }; - - promise.then(() => { - for (const stream of this._streams()) { - stream.resolve(); - } - }).catch(err => { - if (!(err instanceof Error)) { - err = new Error(toErrorMessage(err), { cause: err }); - } - for (const stream of this._streams()) { - stream.reject(err); - } - }).finally(() => { - this._isDone = true; - }); } private * _streams() { @@ -89,6 +69,7 @@ class LanguageModelRequest { if (this._isDone) { return; } + this._isStreaming = true; let res = this._responseStreams.get(fragment.index); if (!res) { if (this._responseStreams.size === 0) { @@ -102,6 +83,24 @@ class LanguageModelRequest { res.stream.emitOne(fragment.part); } + get isStreaming(): boolean { + return this._isStreaming; + } + + reject(err: Error): void { + this._isDone = true; + for (const stream of this._streams()) { + stream.reject(err); + } + } + + resolve(): void { + this._isDone = true; + for (const stream of this._streams()) { + stream.resolve(); + } + } + } export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { @@ -116,7 +115,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH private readonly _modelAccessList = new ExtensionIdentifierMap(); - private readonly _pendingRequest = new Map(); + private readonly _pendingRequest = new Map(); constructor( @@ -191,7 +190,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { // cancel pending requests for this model for (const [key, value] of this._pendingRequest) { if (value.languageModelId === id) { - value.res.cts.cancel(); + value.res.reject(new CancellationError()); this._pendingRequest.delete(key); } } @@ -227,86 +226,54 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } - async makeChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelMessage[], options: Record, token: CancellationToken) { + async sendChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelChatMessage[], options: vscode.LanguageModelChatRequestOptions, token: CancellationToken) { const from = extension.identifier; - // const justification = options?.justification; // TODO@jrieken - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, undefined); + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); if (!metadata || !this._languageModelIds.has(languageModelId)) { - return LanguageModelRequest.fromError(new Error(`Language model ${languageModelId} is unknown`)); + throw new Error(`Language model '${languageModelId}' is unknown.`); } if (this._isUsingAuth(from, metadata)) { - await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, undefined); + const success = await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, options.justification, options.silent); - if (!this._modelAccessList.get(from)?.has(metadata.extension)) { - return LanguageModelRequest.fromError(new Error('Access to chat has been revoked')); + if (!success || !this._modelAccessList.get(from)?.has(metadata.extension)) { + throw new Error(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); } } - const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); - const res = new LanguageModelRequest(requestPromise, cts); + const requestPromise = this._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options.modelOptions ?? {}, token); + + const barrier = new Barrier(); + + const res = new LanguageModelResponse(); this._pendingRequest.set(requestId, { languageModelId, res }); - requestPromise.finally(() => { + let error: Error | undefined; + + requestPromise.catch(err => { + if (barrier.isOpen()) { + // we received an error while streaming. this means we need to reject the "stream" + // because we have already returned the request object + res.reject(err); + } else { + error = err; + } + }).finally(() => { this._pendingRequest.delete(requestId); - cts.dispose(); + res.resolve(); + barrier.open(); }); - return res.apiObject; - } + await barrier.wait(); - async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { - const from = extension.identifier; - const justification = options?.justification; - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); - - if (!metadata) { - throw new Error(`Language model '${languageModelId}' NOT found`); + if (error) { + throw new Error(`Language model '${languageModelId}' errored, check cause for more details`, { cause: error }); } - if (this._isUsingAuth(from, metadata)) { - await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, justification); - } - - const that = this; - - return { - get model() { - return metadata.model; - }, - get isRevoked() { - return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); - }, - get onDidChangeAccess() { - const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); - const onDidChangeModelAccess = Event.filter(that._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from) && ExtensionIdentifier.equals(e.to, metadata.extension)); - return Event.signal(Event.any(onDidRemoveLM, onDidChangeModelAccess)); - }, - makeChatRequest(messages, options, token) { - if (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) { - throw new Error('Access to chat has been revoked'); - } - if (!that._languageModelIds.has(languageModelId)) { - throw new Error('Language Model has been removed'); - } - const cts = new CancellationTokenSource(token); - const requestId = (Math.random() * 1e6) | 0; - const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.LanguageModelMessage.from), options ?? {}, cts.token); - const res = new LanguageModelRequest(requestPromise, cts); - that._pendingRequest.set(requestId, { languageModelId, res }); - - requestPromise.finally(() => { - that._pendingRequest.delete(requestId); - cts.dispose(); - }); - - return res.apiObject; - }, - }; + return res.apiObject; } async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { @@ -317,22 +284,32 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification: string | undefined, silent: boolean | undefined): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); - if (!session) { - try { - const detail = justification - ? localize('chatAccessWithJustification', "To allow access to the language models provided by {0}. Justification:\n\n{1}", to.displayName, justification) - : localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName); - await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { detail } }); - } catch (err) { - throw new Error('Access to language models has not been granted'); - } + + if (session) { + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + return true; } - this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + if (silent) { + return false; + } + + try { + const detail = justification + ? localize('chatAccessWithJustification', "To allow access to the language models provided by {0}. Justification:\n\n{1}", to.displayName, justification) + : localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName); + await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { detail } }); + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); + return true; + + } catch (err) { + // ignore + return false; + } } private _isUsingAuth(from: ExtensionIdentifier, toMetadata: ILanguageModelChatMetadata): toMetadata is ILanguageModelChatMetadata & { auth: NonNullable } { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 816f0227361..68cbe330492 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2239,20 +2239,20 @@ export namespace ChatInlineFollowup { export namespace LanguageModelMessage { - export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelChatMessage { switch (message.role) { - case chatProvider.ChatMessageRole.System: return new types.LanguageModelSystemMessage(message.content); - case chatProvider.ChatMessageRole.User: return new types.LanguageModelUserMessage(message.content); - case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelAssistantMessage(message.content); + case chatProvider.ChatMessageRole.System: return new types.LanguageModelChatSystemMessage(message.content); + case chatProvider.ChatMessageRole.User: return new types.LanguageModelChatUserMessage(message.content); + case chatProvider.ChatMessageRole.Assistant: return new types.LanguageModelChatAssistantMessage(message.content); } } - export function from(message: vscode.LanguageModelMessage): chatProvider.IChatMessage { - if (message instanceof types.LanguageModelSystemMessage) { + export function from(message: vscode.LanguageModelChatMessage): chatProvider.IChatMessage { + if (message instanceof types.LanguageModelChatSystemMessage) { return { role: chatProvider.ChatMessageRole.System, content: message.content }; - } else if (message instanceof types.LanguageModelUserMessage) { + } else if (message instanceof types.LanguageModelChatUserMessage) { return { role: chatProvider.ChatMessageRole.User, content: message.content }; - } else if (message instanceof types.LanguageModelAssistantMessage) { + } else if (message instanceof types.LanguageModelChatAssistantMessage) { return { role: chatProvider.ChatMessageRole.Assistant, content: message.content }; } else { throw new Error('Invalid LanguageModelMessage'); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4fb94e3f1c8..4eec0964cd2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4271,7 +4271,7 @@ export class ChatResponseTurn implements vscode.ChatResponseTurn { ) { } } -export class LanguageModelSystemMessage { +export class LanguageModelChatSystemMessage { content: string; constructor(content: string) { @@ -4279,7 +4279,7 @@ export class LanguageModelSystemMessage { } } -export class LanguageModelUserMessage { +export class LanguageModelChatUserMessage { content: string; name: string | undefined; @@ -4289,7 +4289,7 @@ export class LanguageModelUserMessage { } } -export class LanguageModelAssistantMessage { +export class LanguageModelChatAssistantMessage { content: string; constructor(content: string) { diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 7a9e140b5a5..b6aa1ffdadf 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -16,7 +16,7 @@ declare module 'vscode' { * Represents a large language model that accepts ChatML messages and produces a streaming response */ export interface ChatResponseProvider { - provideLanguageModelResponse2(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2(messages: LanguageModelChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 258a5bf18e9..59b053f149d 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -8,20 +8,14 @@ declare module 'vscode' { /** * Represents a language model response. * - * @see {@link LanguageModelAccess.makeChatRequest} + * @see {@link LanguageModelAccess.chatRequest} */ - export interface LanguageModelResponse { - - /** - * The overall result of the request which represents failure or success - * but. The concrete value is not specified and depends on the selected language model. - * - * *Note* that the actual response represented by the {@link LanguageModelResponse.stream `stream`}-property - */ - result: Thenable; + export interface LanguageModelChatResponse { /** * An async iterable that is a stream of text chunks forming the overall response. + * + * *Note* that this stream will error when during receiving an error occurrs. */ stream: AsyncIterable; } @@ -35,7 +29,7 @@ declare module 'vscode' { * *Note* that a language model may choose to add additional system messages to the ones * provided by extensions. */ - export class LanguageModelSystemMessage { + export class LanguageModelChatSystemMessage { /** * The content of this message. @@ -53,7 +47,7 @@ declare module 'vscode' { /** * A language model message that represents a user message. */ - export class LanguageModelUserMessage { + export class LanguageModelChatUserMessage { /** * The content of this message. @@ -78,7 +72,7 @@ declare module 'vscode' { * A language model message that represents an assistant message, usually in response to a user message * or as a sample response/reply-pair. */ - export class LanguageModelAssistantMessage { + export class LanguageModelChatAssistantMessage { /** * The content of this message. @@ -93,63 +87,46 @@ declare module 'vscode' { constructor(content: string); } - export type LanguageModelMessage = LanguageModelSystemMessage | LanguageModelUserMessage | LanguageModelAssistantMessage; + export type LanguageModelChatMessage = LanguageModelChatSystemMessage | LanguageModelChatUserMessage | LanguageModelChatAssistantMessage; + /** - * Represents access to using a language model. Access can be revoked at any time and extension - * must check if the access is {@link LanguageModelAccess.isRevoked still valid} before using it. + * An event describing the change in the set of available language models. */ - export interface LanguageModelAccess { - - /** - * Whether the access to the language model has been revoked. - */ - readonly isRevoked: boolean; - - /** - * An event that is fired when the access the language model has has been revoked or re-granted. - */ - // TODO@API NAME? - readonly onDidChangeAccess: Event; - + export interface LanguageModelChangeEvent { /** - * The name of the model. - * - * It is expected that the model name can be used to lookup properties like token limits or what - * `options` are available. + * Added language models. */ - readonly model: string; - + readonly added: readonly string[]; /** - * Make a request to the language model. - * - * *Note:* This will throw an error if access has been revoked. - * - * @param messages - * @param options + * Removed language models. */ - makeChatRequest(messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; + readonly removed: readonly string[]; } - export interface LanguageModelAccessOptions { + /** + * Options for making a chat request using a language model. + * + * @see {@link lm.chatRequest} + */ + export interface LanguageModelChatRequestOptions { + /** * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. */ justification?: string; - } - /** - * An event describing the change in the set of available language models. - */ - export interface LanguageModelChangeEvent { /** - * Added language models. + * Do not show the consent UI if the user has not yet granted access to the language model but fail the request instead. */ - readonly added: readonly string[]; + // TODO@API refine/define + silent?: boolean; + /** - * Removed language models. + * A set of options that control the behavior of the language model. These options are specific to the language model + * and need to be lookup in the respective documentation. */ - readonly removed: readonly string[]; + modelOptions?: { [name: string]: any }; } /** @@ -157,52 +134,31 @@ declare module 'vscode' { */ export namespace lm { - /** - * Request access to a language model. - * - * - *Note 1:* This function will throw an error when the user didn't grant access or when the - * requested language model is not available. - * - * - *Note 2:* It is OK to hold on to the returned access object and use it later, but extensions - * should check {@link LanguageModelAccess.isRevoked} before using it. - * - * @param id The id of the language model, see {@link languageModels} for valid values. - * @returns A thenable that resolves to a language model access object, rejects if access wasn't granted - */ - export function requestLanguageModelAccess(id: string, options?: LanguageModelAccessOptions): Thenable; - - - /** * Make a chat request using a language model. * - * *Note* that language model use may be subject to access restrictions and user consent. This function always returns a response-object - * but its {@link LanguageModelResponse.result `result`}-property may indicate failure, e.g. due to + * *Note* that language model use may be subject to access restrictions and user consent. This function will return a rejected promise + * if access to the language model is not possible. Reasons for this can be: * * - user consent not given * - quote limits exceeded * - model does not exist * * @param languageModel A language model identifier. See {@link languageModels} for aviailable values. - * @param messages - * @param options - * @param token + * @param messages An array of message instances. + * @param options Objects that control the request. + * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. + * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when making the request failed. */ // TODO@API refine doc // TODO@API define specific error types? - // TODO@API NAME: chatRequest - // TODO@API NAME: ChatRequestXYZMessage - // TODO@API NAME: ChatRequestResponse - export function chatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; - - /** - * @see {@link chatRequest} - */ - export function chatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; - - // TODO@API probe on having access - // TODO@API, BETTER?: ExtensionContext.permissions.languageModels: Record; - // export function canMakeChatRequest(languageModel: string): Thenable; + // TODO@API NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest + // TODO@API ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} + // TODO@API ✅ NAME: LanguageModelChatXYZMessage + // TODO@API ✅ errors on everything that prevents us to make the actual request + // TODO@API ✅ double auth + // TODO@API ✅ NAME: LanguageModelChatResponse, ChatResponse, ChatRequestResponse + export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; /** * The identifiers of all language models that are currently available. @@ -214,4 +170,12 @@ declare module 'vscode' { */ export const onDidChangeLanguageModels: Event; } + + // export function chatRequest2(languageModel: string, callback: (request: LanguageModelRequest) => R): Thenable; + + // interface LanguageModelRequest { + // readonly quota: any; + // readonly permissions: any; + // makeRequest(messages: LanguageModelChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelChatResponse; + // } } From 8fd15a863481726f1ea4bdd4bd4f26f40f3e592d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 27 Feb 2024 18:50:12 +0100 Subject: [PATCH 176/753] add `LanguageModelError`-type (#206361) https://github.com/microsoft/vscode/issues/206265 --- .../workbench/api/common/extHost.api.impl.ts | 1 + .../api/common/extHostLanguageModels.ts | 11 ++++-- src/vs/workbench/api/common/extHostTypes.ts | 20 +++++++++++ .../vscode.proposed.languageModels.d.ts | 34 +++++++++++++++++-- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1b9c7eb2b08..8a99d63cb4c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1691,6 +1691,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelSystemMessage: extHostTypes.LanguageModelChatSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelChatUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelChatAssistantMessage, + LanguageModelError: extHostTypes.LanguageModelError, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, InlineEdit: extHostTypes.InlineEdit, diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index e51c875984d..ea10161e75f 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -8,6 +8,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { LanguageModelError } from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; @@ -232,14 +233,14 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options.justification); if (!metadata || !this._languageModelIds.has(languageModelId)) { - throw new Error(`Language model '${languageModelId}' is unknown.`); + throw LanguageModelError.NotFound(`Language model '${languageModelId}' is unknown.`); } if (this._isUsingAuth(from, metadata)) { const success = await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, options.justification, options.silent); if (!success || !this._modelAccessList.get(from)?.has(metadata.extension)) { - throw new Error(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); + throw LanguageModelError.NoPermissions(`Language model '${languageModelId}' cannot be used by '${from.value}'.`); } } @@ -270,7 +271,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { await barrier.wait(); if (error) { - throw new Error(`Language model '${languageModelId}' errored, check cause for more details`, { cause: error }); + throw new LanguageModelError( + `Language model '${languageModelId}' errored, check cause for more details`, + 'Unknown', + error + ); } return res.apiObject; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4eec0964cd2..2e1ff171d1f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4297,6 +4297,26 @@ export class LanguageModelChatAssistantMessage { } } +export class LanguageModelError extends Error { + + static NotFound(message?: string): LanguageModelError { + return new LanguageModelError(message, LanguageModelError.NotFound.name); + } + + static NoPermissions(message?: string): LanguageModelError { + return new LanguageModelError(message, LanguageModelError.NoPermissions.name); + } + + readonly code: string; + + constructor(message?: string, code?: string, cause?: Error) { + super(message, { cause }); + this.name = 'LanguageModelError'; + this.code = code ?? ''; + } + +} + //#endregion //#region ai diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 59b053f149d..775e4d5e1dc 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -104,6 +104,36 @@ declare module 'vscode' { readonly removed: readonly string[]; } + /** + * An error type for language model specific errors. + * + * Consumers of language models should check the code property to determine specific + * failure causes, like `if(someError.code === vscode.LanguageModelError.NotFound.name) {...}` + * for the case of referring to an unknown language model. + */ + export class LanguageModelError extends Error { + + /** + * The language model does not exist. + */ + static NotFound(message?: string): LanguageModelError; + + /** + * The requestor does not have permissions to use this + * language model + */ + static NoPermissions(message?: string): LanguageModelError; + + /** + * A code that identifies this error. + * + * Possible values are names of errors, like {@linkcode LanguageModelError.NotFound NotFound}, + * or `Unknown` for unspecified errors from the language model itself. In the latter case the + * `cause`-property will contain the actual error. + */ + readonly code: string; + } + /** * Options for making a chat request using a language model. * @@ -151,9 +181,9 @@ declare module 'vscode' { * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when making the request failed. */ // TODO@API refine doc - // TODO@API define specific error types? - // TODO@API NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest // TODO@API ExtensionContext#permission#languageModels: { languageModel: string: LanguageModelAccessInformation} + // TODO@API ✅ define specific error types? + // TODO@API ✅ NAME: sendChatRequest, fetchChatResponse, makeChatRequest, chat, chatRequest sendChatRequest // TODO@API ✅ NAME: LanguageModelChatXYZMessage // TODO@API ✅ errors on everything that prevents us to make the actual request // TODO@API ✅ double auth From 1d3ff8e891800d26b639d9ecc3656aa75f8ea983 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:53:46 +0100 Subject: [PATCH 177/753] Refactor and Improve Hover Functionality (#206357) * minor hover improvements? * :lipstick: --- src/vs/platform/hover/browser/hover.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 3a44ead957b..3dd6ec94d02 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -241,7 +241,7 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate private _delay: number; get delay(): number { - if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { + if (this.isInstantlyHovering()) { return 0; // show instantly when a hover was recently shown } return this._delay; @@ -280,6 +280,8 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate })); } + const id = options.content instanceof HTMLElement ? undefined : options.content.toString(); + return this.hoverService.showHover({ ...options, ...overrideOptions, @@ -288,14 +290,20 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate hideOnKeyDown: true, ...overrideOptions.persistence }, + id, appearance: { ...options.appearance, compact: true, + skipFadeInAnimation: this.isInstantlyHovering(), ...overrideOptions.appearance } }, focus); } + private isInstantlyHovering(): boolean { + return this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit; + } + setInstantHoverTimeLimit(timeLimit: number): void { if (!this.instantHover) { throw new Error('Instant hover is not enabled'); From 7d546baaeacf0a045fa68259f934109c9f51e973 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 27 Feb 2024 10:08:15 -0800 Subject: [PATCH 178/753] align start command name with inline chat, get it to show up in command pallette --- .../contrib/inlineChat/browser/inlineChatActions.ts | 2 +- .../terminalContrib/chat/browser/terminalChatActions.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 75ca16a1f24..ae407d07b67 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -35,7 +35,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); -export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start Inline Chat'); +export const LOCALIZED_START_INLINE_CHAT_STRING = localize2('run', 'Start in Editor'); export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.')); // some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 878246a2e26..d7e2d3850d3 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -9,7 +9,8 @@ import { localize2 } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -18,17 +19,18 @@ import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/cha registerActiveXtermAction({ id: TerminalChatCommandId.Start, - title: localize2('startChat', 'Start Chat'), + title: localize2('startChat', 'Start in Terminal'), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib, }, f1: true, + category: AbstractInlineChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.has(`config.${TerminalSettingId.ExperimentalInlineChat}`), ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - TerminalChatContextKeys.agentRegistered + CTX_INLINE_CHAT_HAS_PROVIDER ), run: (_xterm, _accessor, activeInstance) => { if (isDetachedTerminalInstance(activeInstance)) { From 70e4a844210fc82f0e58b2f7bb363fbe601a29d2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 27 Feb 2024 10:16:24 -0800 Subject: [PATCH 179/753] Move off in Webview --- .../api/browser/mainThreadCodeInsets.ts | 5 ++++- .../api/browser/mainThreadWebviewPanels.ts | 4 +++- .../customEditor/browser/customEditorInput.ts | 9 ++++++++- .../browser/customEditorInputFactory.ts | 6 +++++- .../customEditor/browser/customEditors.ts | 12 ++++++----- .../extensions/browser/extensionEditor.ts | 11 +++++++++- .../browser/diff/notebookDiffEditor.ts | 3 +-- .../view/renderers/backLayerWebView.ts | 2 +- .../contrib/webview/browser/overlayWebview.ts | 4 ++++ .../contrib/webview/browser/webview.ts | 7 ++++++- .../contrib/webview/browser/webviewElement.ts | 20 ++++++++++++------- .../browser/webviewWindowDragMonitor.ts | 10 +++++----- .../electron-sandbox/webviewCommands.ts | 6 +++--- .../webviewPanel/browser/webviewEditor.ts | 5 ++--- .../browser/webviewEditorInputSerializer.ts | 9 ++++++++- .../webviewView/browser/webviewViewPane.ts | 8 +++++--- .../browser/gettingStarted.ts | 6 +++--- 17 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 4ad9e654807..2d514b09b39 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getWindow } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -88,6 +89,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { } const disposables = new DisposableStore(); + const codeWindow = getWindow(editor.getDomNode()); const webview = this._webviewService.createWebviewElement({ title: undefined, @@ -95,7 +97,8 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { enableFindWidget: false, }, contentOptions: reviveWebviewContentOptions(options), - extension: { id: extensionId, location: URI.revive(extensionLocation) } + extension: { id: extensionId, location: URI.revive(extensionLocation) }, + codeWindow: codeWindow }); const webviewZone = new EditorWebviewZone(editor, line, height, webview); diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 996a8a2e657..69ca28691de 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveWindow } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; @@ -170,7 +171,8 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc title: initData.title, options: reviveWebviewOptions(initData.panelOptions), contentOptions: reviveWebviewContentOptions(initData.webviewOptions), - extension + extension, + codeWindow: getActiveWindow() }, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions); this.addWebviewInput(handle, webview, { serializeBuffersForPostMessage: initData.serializeBuffersForPostMessage }); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 4e677142d69..1119354a07a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getWindowById } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; import { VSBuffer } from 'vs/base/common/buffer'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IReference } from 'vs/base/common/lifecycle'; @@ -37,18 +39,21 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { resource: URI, viewType: string, group: GroupIdentifier | undefined, + windowId: number, options?: { readonly customClasses?: string; readonly oldResource?: URI }, ): EditorInput { return instantiationService.invokeFunction(accessor => { // If it's an untitled file we must populate the untitledDocumentData const untitledString = accessor.get(IUntitledTextEditorService).getValue(resource); const untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; + const codeWindow = getWindowById(windowId)?.window ?? mainWindow; const webview = accessor.get(IWebviewService).createWebviewOverlay({ providedViewType: viewType, title: undefined, options: { customClasses: options?.customClasses }, contentOptions: {}, extension: undefined, + codeWindow }); const input = instantiationService.createInstance(CustomEditorInput, { resource, viewType }, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); if (typeof group !== 'undefined') { @@ -65,6 +70,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private _defaultDirtyState: boolean | undefined; private readonly _backupId: string | undefined; + private readonly _windowId: number; private readonly _untitledDocumentData: VSBuffer | undefined; @@ -91,6 +97,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._defaultDirtyState = options.startsDirty; this._backupId = options.backupId; this._untitledDocumentData = options.untitledDocumentData; + this._windowId = webview.codeWindow.vscodeWindowId; this.registerListeners(); } @@ -250,7 +257,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public override copy(): EditorInput { - return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this.webview.options); + return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this._windowId, this.webview.options); } public override isReadonly(): boolean | IMarkdownString { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 8dc11b1af6b..d79928406dd 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveWindow } from 'vs/base/browser/dom'; +import { CodeWindow } from 'vs/base/browser/window'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; @@ -100,7 +102,7 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } } -function reviveWebview(webviewService: IWebviewService, data: { origin: string | undefined; viewType: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { +function reviveWebview(webviewService: IWebviewService, data: { origin: string | undefined; viewType: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription; codeWindow: CodeWindow }) { const webview = webviewService.createWebviewOverlay({ providedViewType: data.viewType, origin: data.origin, @@ -112,6 +114,7 @@ function reviveWebview(webviewService: IWebviewService, data: { origin: string | }, contentOptions: data.contentOptions, extension: data.extension, + codeWindow: data.codeWindow }); webview.state = data.state; return webview; @@ -185,6 +188,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements contentOptions: restoreWebviewContentOptions(backupData.webview.options), state: backupData.webview.state, extension, + codeWindow: getActiveWindow() }); const editor = this._instantiationService.createInstance(CustomEditorInput, { resource: URI.revive(backupData.editorResource), viewType: backupData.viewType }, webview, { backupId: backupData.backupId }); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 6c99db87599..d03d8c9b492 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -29,6 +29,7 @@ import { IEditorResolverService, IEditorType, RegisteredEditorPriority } from 'v import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ContributedCustomEditors } from '../common/contributedCustomEditors'; import { CustomEditorInput } from './customEditorInput'; +import { mainWindow } from 'vs/base/browser/window'; export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; @@ -131,10 +132,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ }, { createEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id, group.windowId) }; }, createUntitledEditorInput: ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), contributedEditor.id, group.id) }; + return { editor: CustomEditorInput.create(this.instantiationService, resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), contributedEditor.id, group.id, group.windowId) }; }, createDiffEditorInput: (diffEditorInput, group) => { return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; @@ -150,8 +151,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ editorID: string, group: IEditorGroup ): DiffEditorInput { - const modifiedOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.modified.resource), editorID, group.id, { customClasses: 'modified' }); - const originalOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.original.resource), editorID, group.id, { customClasses: 'original' }); + const modifiedOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.modified.resource), editorID, group.id, group.windowId, { customClasses: 'modified' }); + const originalOverride = CustomEditorInput.create(this.instantiationService, assertIsDefined(editor.original.resource), editorID, group.id, group.windowId, { customClasses: 'original' }); return this.instantiationService.createInstance(DiffEditorInput, editor.label, editor.description, originalOverride, modifiedOverride, true); } @@ -245,7 +246,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let replacement: EditorInput | IResourceEditorInput; if (possibleEditors.defaultEditor) { const viewType = possibleEditors.defaultEditor.id; - replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType, group); + const windowId = this.editorGroupService.getGroup(group)?.windowId ?? mainWindow.vscodeWindowId; + replacement = CustomEditorInput.create(this.instantiationService, newResource, viewType, group, windowId); } else { replacement = { resource: newResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 8f4ca1d9d6a..65cf6d01bc3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, addDisposableListener, append, setParentFlowTo } from 'vs/base/browser/dom'; +import { $, Dimension, addDisposableListener, append, getWindow, getWindowById, setParentFlowTo } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget'; @@ -661,6 +661,14 @@ export class ExtensionEditor extends EditorPane { return Promise.resolve(null); } + let codeWindow; + if (this.group?.windowId) { + const windowById = getWindowById(this.group.windowId); + codeWindow = windowById?.window ?? getWindow(container); + } else { + codeWindow = getWindow(container); + } + const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay({ title, options: { @@ -670,6 +678,7 @@ export class ExtensionEditor extends EditorPane { }, contentOptions: {}, extension: undefined, + codeWindow: codeWindow })); webview.initialScrollProgress = this.initialScrollProgress.get(webviewIndex) || 0; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index e2de1a22096..58807d6a591 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -47,7 +47,6 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookDiffOverviewRuler } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler'; import { registerZIndex, ZIndex } from 'vs/platform/layout/browser/zIndexRegistry'; -import { mainWindow } from 'vs/base/browser/window'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; const $ = DOM.$; @@ -154,7 +153,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD @ICodeEditorService codeEditorService: ICodeEditorService ) { super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); - this._notebookOptions = new NotebookOptions(DOM.getWindowById(this.group?.windowId, true).window ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, false); + this._notebookOptions = new NotebookOptions(DOM.getWindowById(this.group?.windowId, true).window, this.configurationService, notebookExecutionStateService, codeEditorService, false); this._register(this._notebookOptions); this._revealFirst = true; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index dd421641fb8..15b3d151ca3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -560,7 +560,7 @@ export class BackLayerWebView extends Themable { this.webview.mountTo(this.element); this._register(this.webview); - this._register(new WebviewWindowDragMonitor(() => this.webview)); + this._register(new WebviewWindowDragMonitor(codeWindow, () => this.webview)); const initializePromise = new DeferredPromise(); diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index 0c60dbd1e68..e58e399be2f 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -6,6 +6,7 @@ import { Dimension } from 'vs/base/browser/dom'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { CodeWindow } from 'vs/base/browser/window'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -44,6 +45,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { public readonly providedViewType?: string; public origin: string; + public codeWindow: CodeWindow; private _container: FastDomNode | undefined; @@ -62,6 +64,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { this._extension = initInfo.extension; this._options = initInfo.options; this._contentOptions = initInfo.contentOptions; + this.codeWindow = initInfo.codeWindow; } public get isFocused() { @@ -197,6 +200,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { options: this._options, contentOptions: this._contentOptions, extension: this.extension, + codeWindow: this.codeWindow }); this._webview.value = webview; webview.state = this._state; diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index dfd7c6fc5b4..c115a9927af 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -78,7 +78,7 @@ export interface WebviewInitInfo { readonly contentOptions: WebviewContentOptions; readonly extension: WebviewExtensionDescription | undefined; - readonly codeWindow?: CodeWindow; + readonly codeWindow: CodeWindow; } export const enum WebviewContentPurpose { @@ -189,6 +189,11 @@ export interface IWebview extends IDisposable { */ readonly origin: string; + /** + * The code window the webview is contained within. + */ + readonly codeWindow: CodeWindow; + /** * Set html content of the webview. */ diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 6514979da3d..2df70a23b32 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -37,7 +37,7 @@ import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/web import { FromWebviewMessage, KeyEvent, ToWebviewMessage } from 'vs/workbench/contrib/webview/browser/webviewMessages'; import { decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/contrib/webview/common/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { $window } from 'vs/base/browser/window'; +import { CodeWindow } from 'vs/base/browser/window'; interface WebviewContent { readonly html: string; @@ -88,6 +88,11 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD */ public readonly origin: string; + /** + * The code window the webview is contained within. + */ + public readonly codeWindow: CodeWindow; + private readonly _encodedWebviewOriginPromise: Promise; private _encodedWebviewOrigin: string | undefined; @@ -103,7 +108,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD if (!this._focused) { return false; } - if ($window.document.activeElement && $window.document.activeElement !== this.element) { + if (this.codeWindow.document.activeElement && this.codeWindow.document.activeElement !== this.element) { // looks like https://github.com/microsoft/vscode/issues/132641 // where the focus is actually not in the `