From a7eaf157d509a2d83e7bab2b04b25437d5b3e0ec Mon Sep 17 00:00:00 2001 From: Martin Sander Date: Sun, 7 Nov 2021 22:52:46 +0100 Subject: [PATCH 0001/1863] Middle-click paste in terminal on non-Linux --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/terminalEditor.ts | 19 +++++++++++++------ .../terminal/browser/terminalTabbedView.ts | 12 ++++++++---- .../contrib/terminal/common/terminal.ts | 1 + .../terminal/common/terminalConfiguration.ts | 10 ++++++++++ 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 71c62368b0967..79c9369e6bca0 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -71,6 +71,7 @@ export const enum TerminalSettingId { TerminalTitle = 'terminal.integrated.tabs.title', TerminalDescription = 'terminal.integrated.tabs.description', RightClickBehavior = 'terminal.integrated.rightClickBehavior', + MiddleClickBehavior = 'terminal.integrated.middleClickBehavior', Cwd = 'terminal.integrated.cwd', ConfirmOnExit = 'terminal.integrated.confirmOnExit', ConfirmOnKill = 'terminal.integrated.confirmOnKill', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 124238bcac632..c0c231efe17ec 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -131,12 +131,19 @@ export class TerminalEditor extends EditorPane { return; } - if (event.which === 2 && isLinux) { - // Drop selection and focus terminal on Linux to enable middle button paste when click - // occurs on the selection itself. - const terminal = this._terminalEditorService.activeInstance; - if (terminal) { - terminal.focus(); + if (event.which === 2) { + if (isLinux) { + // Drop selection and focus terminal on Linux to enable middle button paste when click + // occurs on the selection itself. + const terminal = this._terminalEditorService.activeInstance; + if (terminal) { + terminal.focus(); + } + } else if (this._terminalService.configHelper.config.middleClickBehavior === 'paste') { + const terminal = this._terminalEditorService.activeInstance; + if (terminal) { + terminal.paste(); + } } } else if (event.which === 3) { const rightClickBehavior = this._terminalService.configHelper.config.rightClickBehavior; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index adae67849dcca..30806c967f857 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -346,10 +346,14 @@ export class TerminalTabbedView extends Disposable { return; } - if (event.which === 2 && isLinux) { - // Drop selection and focus terminal on Linux to enable middle button paste when click - // occurs on the selection itself. - terminal.focus(); + if (event.which === 2) { + if (isLinux) { + // Drop selection and focus terminal on Linux to enable middle button paste when click + // occurs on the selection itself. + terminal.focus(); + } else if (this._terminalService.configHelper.config.middleClickBehavior === 'paste') { + terminal.paste(); + } } else if (event.which === 3) { const rightClickBehavior = this._terminalService.configHelper.config.rightClickBehavior; if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index bf99ce95676f7..b40de7524aff6 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -203,6 +203,7 @@ export interface ITerminalConfiguration { macOptionClickForcesSelection: boolean; gpuAcceleration: 'auto' | 'on' | 'canvas' | 'off'; rightClickBehavior: 'default' | 'copyPaste' | 'paste' | 'selectWord'; + middleClickBehavior: 'default' | 'paste'; cursorBlinking: boolean; cursorStyle: 'block' | 'underline' | 'line'; cursorWidth: number; diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 84d3e578a0273..e8ff265bb9eea 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -293,6 +293,16 @@ const terminalConfiguration: IConfigurationNode = { default: isMacintosh ? 'selectWord' : isWindows ? 'copyPaste' : 'default', description: localize('terminal.integrated.rightClickBehavior', "Controls how terminal reacts to right click.") }, + [TerminalSettingId.MiddleClickBehavior]: { + type: 'string', + enum: ['default', 'paste'], + enumDescriptions: [ + localize('terminal.integrated.middleClickBehavior.default', "Platform default"), + localize('terminal.integrated.middleClickBehavior.paste', "Paste on middle click."), + ], + default: 'default', + description: localize('terminal.integrated.middleClickBehavior', "Controls how terminal reacts to middle click.") + }, [TerminalSettingId.Cwd]: { restricted: true, description: localize('terminal.integrated.cwd', "An explicit start path where the terminal will be launched, this is used as the current working directory (cwd) for the shell process. This may be particularly useful in workspace settings if the root directory is not a convenient cwd."), From 51ea68dcd23eeb0235e07b854c51f03c1ce60ca5 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Mon, 11 Dec 2023 16:39:38 -0500 Subject: [PATCH 0002/1863] Fixes #200590 --- src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 7094cc9c41edf..b84a3d8a8b316 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -57,6 +57,7 @@ export class BreadcrumbsWidget { private _focusedItemIdx: number = -1; private _selectedItemIdx: number = -1; + private _pendingDimLayout: IDisposable | undefined; private _pendingLayout: IDisposable | undefined; private _dimension: dom.Dimension | undefined; @@ -100,6 +101,7 @@ export class BreadcrumbsWidget { dispose(): void { this._disposables.dispose(); this._pendingLayout?.dispose(); + this._pendingDimLayout?.dispose(); this._onDidSelectItem.dispose(); this._onDidFocusItem.dispose(); this._onDidChangeFocus.dispose(); @@ -112,11 +114,12 @@ export class BreadcrumbsWidget { if (dim && dom.Dimension.equals(dim, this._dimension)) { return; } - this._pendingLayout?.dispose(); if (dim) { // only measure - this._pendingLayout = this._updateDimensions(dim); + this._pendingDimLayout?.dispose(); + this._pendingDimLayout = this._updateDimensions(dim); } else { + this._pendingLayout?.dispose(); this._pendingLayout = this._updateScrollbar(); } } 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 0003/1863] 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 4029985233ad2..12f6e5cac1fc5 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 03f06fa04d538..45657928dbcdb 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 676d7ba4857f3781af8b58ea6da34bc2500706ba Mon Sep 17 00:00:00 2001 From: Justin Carrus Date: Sun, 24 Dec 2023 01:22:12 +0000 Subject: [PATCH 0004/1863] Increase editor.stickyScroll.maxLineCount from 10 to 20 --- src/vs/editor/common/config/editorOptions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 764e94ed31e2e..823718b890e81 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2822,7 +2822,7 @@ class EditorStickyScroll extends BaseEditorOption(input.defaultModel, this.defaultValue.defaultModel, ['outlineModel', 'foldingProviderModel', 'indentationModel']), scrollWithEditor: boolean(input.scrollWithEditor, this.defaultValue.scrollWithEditor) }; From bd0c11e1d4c4b126b0ef9433c092d895f09cb103 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 15 Nov 2023 11:32:53 -0800 Subject: [PATCH 0005/1863] Prevent incorrect indentation for Ruby's in and when keywords Co-authored-by: Soutaro Matsumoto --- extensions/ruby/language-configuration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index a86f592e3bdbd..e61f3ac410fbc 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -26,6 +26,6 @@ ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", - "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif|when|in)\\b)" + "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b)|((in|when)\\s)" } } From 978342ea412c3cd25a8b8e1b4258c16c47b0e060 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 30 Jan 2024 08:58:47 -0800 Subject: [PATCH 0006/1863] add basics --- .../audioCues/browser/audioCueService.ts | 16 + .../browser/audioCues.contribution.ts | 345 ++++++++++++++++++ 2 files changed, 361 insertions(+) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0e35e7f9e3ca6..f58928e3a24ef 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -54,6 +54,22 @@ export class AudioCueService extends Disposable implements IAudioCueService { @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); + this._migrateSettings(); + } + + private _migrateSettings(): void { + const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); + for (const entry in audioCuesConfig) { + if (entry) { + this.configurationService.updateValue('audioCues.' + entry, undefined); + if (entry === 'debouncePositionChanges') { + this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); + } else { + this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); + } + } + } + // do this for alerts as well } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 1fced33582713..d74fe0c1ee228 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -32,6 +32,351 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { tags: ['accessibility'] }; +const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'boolean', + 'default': true, + 'tags': ['accessibility'] +}; + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + 'properties': { + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasInlineSuggestion': { + 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasInlineSuggestion.alert', "Alerts when the active line has an inline suggestion."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasError': { + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasFoldedArea': { + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + }, + default: 'on' + }, + 'signals.diffLineInserted': { + 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineInserted.alert', "Alerts when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineDeleted': { + 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineDeleted.alert', "Alerts when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineModified': { + 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.diffLineModified.alert', "Alerts when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellCompleted': { + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponsePending': { + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived': { + 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponseReceived.alert', "Alerts on loop while the response has been received."), + ...alertFeatureBase + }, + }, + }, + 'signals.clear': { + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + } + }, + 'signals.format': { + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, + } +}); + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'properties': { 'audioCues.enabled': { From 28cf876f29477dc1e6fb7d9f7eec24ed510b04ac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 30 Jan 2024 10:57:58 -0800 Subject: [PATCH 0007/1863] wip --- .../audioCues/browser/audioCueService.ts | 25 ++++- .../browser/audioCues.contribution.ts | 99 +++++++++++++------ .../preferences/browser/settingsLayout.ts | 5 + 3 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index f58928e3a24ef..dfdb1979b9b72 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,15 +61,36 @@ export class AudioCueService extends Disposable implements IAudioCueService { const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); for (const entry in audioCuesConfig) { if (entry) { + console.log('cue ', entry); this.configurationService.updateValue('audioCues.' + entry, undefined); - if (entry === 'debouncePositionChanges') { + if (entry === 'volume' || entry === 'enabled') { + // ignore, these are audio cue settings + } else if (entry === 'debouncePositionChanges') { this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); } else { this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); } } } - // do this for alerts as well + const alertsConfig = this.configurationService.getValue<{ [key: string]: boolean }>('accessibility.alert'); + for (let entry in alertsConfig) { + if (entry) { + console.log('alert ', entry); + if (entry === 'warning') { + entry = 'lineHasWarning'; + } else if (entry === 'error') { + entry = 'lineHasError'; + } else if (entry === 'foldedArea') { + entry = 'lineHasFoldedArea'; + } else if (entry === 'breakpoint') { + entry = 'onDebugBreak'; + } + this.configurationService.updateValue('accessibility.alert.' + entry, undefined); + this.configurationService.updateValue('signals.' + entry + '.' + 'alert', alertsConfig[entry]); + } + } + const signals = this.configurationService.getValue<{ [key: string]: boolean }>('signals'); + console.log(JSON.stringify(signals)); } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index d74fe0c1ee228..a5df5042d0d44 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -35,10 +35,34 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { const alertFeatureBase: IConfigurationPropertySchema = { 'type': 'boolean', 'default': true, - 'tags': ['accessibility'] + 'tags': ['accessibility'], + required: ['audioCue'] +}; + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + default: { + audioCue: 'auto', + alert: true + } +}; + +const defaultNoAlert: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'default': { + 'audioCue': 'auto', + } }; Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'signals', + order: 100, + title: localize('signals', "Signals"), + type: 'object', 'properties': { 'signals.debouncePositionChanges': { 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), @@ -47,6 +71,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis tags: ['accessibility'] }, 'signals.lineHasBreakpoint': { + ...signalFeatureBase, 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { @@ -57,9 +82,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), ...alertFeatureBase }, - } + }, }, 'signals.lineHasInlineSuggestion': { + ...signalFeatureBase, 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), 'properties': { 'audioCue': { @@ -73,6 +99,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.lineHasError': { + ...signalFeatureBase, 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), 'properties': { 'audioCue': { @@ -83,9 +110,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), ...alertFeatureBase }, - } + }, }, 'signals.lineHasFoldedArea': { + ...signalFeatureBase, 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { @@ -99,6 +127,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.lineHasWarning': { + ...signalFeatureBase, 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { @@ -112,6 +141,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.onDebugBreak': { + ...signalFeatureBase, 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { @@ -125,6 +155,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.noInlayHints': { + ...signalFeatureBase, 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { @@ -138,6 +169,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.taskCompleted': { + ...signalFeatureBase, 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { @@ -151,6 +183,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.taskFailed': { + ...signalFeatureBase, 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { @@ -164,6 +197,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalCommandFailed': { + ...signalFeatureBase, 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { @@ -177,6 +211,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalQuickFix': { + ...signalFeatureBase, 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { @@ -190,6 +225,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.terminalBell': { + ...signalFeatureBase, 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { @@ -200,49 +236,40 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), ...alertFeatureBase }, - }, - default: 'on' + } }, 'signals.diffLineInserted': { + ...defaultNoAlert, 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineInserted.alert', "Alerts when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.diffLineDeleted': { + ...defaultNoAlert, 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineDeleted.alert', "Alerts when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.diffLineModified': { + ...defaultNoAlert, 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.diffLineModified.alert', "Alerts when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...alertFeatureBase - }, + } } }, 'signals.notebookCellCompleted': { + ...signalFeatureBase, 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { @@ -256,6 +283,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.notebookCellFailed': { + ...signalFeatureBase, 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { @@ -269,19 +297,21 @@ Registry.as(ConfigurationExtensions.Configuration).regis } }, 'signals.chatRequestSent': { + ...signalFeatureBase, 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'audioCue': { + 'signals.chatRequestSent.audioCue': { 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'alert': { + 'signals.chatRequestSent.alert': { 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, - }, + } }, 'signals.chatResponsePending': { + ...signalFeatureBase, 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { @@ -295,19 +325,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.chatResponseReceived': { + ...defaultNoAlert, 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), 'properties': { - 'audioCue': { + 'signals.chatResponseReceived.audioCue': { 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, - 'alert': { - 'description': localize('signals.chatResponseReceived.alert', "Alerts on loop while the response has been received."), - ...alertFeatureBase - }, }, }, 'signals.clear': { + ...signalFeatureBase, 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { @@ -321,6 +349,9 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { @@ -345,9 +376,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.save.alert.never', "Never plays the audio cue.") ], }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' } }, 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { @@ -372,6 +410,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.format.alert.never', "Never plays the alert.") ], }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' } }, } @@ -516,3 +558,4 @@ Registry.as(ConfigurationExtensions.Configuration).regis registerAction2(ShowAudioCueHelp); registerAction2(ShowAccessibilityAlertHelp); + diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b4b225d7bc80a..f10aaea48ebe9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -226,6 +226,11 @@ export const tocData: ITOCEntry = { label: localize('audioCues', 'Audio Cues'), settings: ['audioCues.*'] }, + { + id: 'features/signals', + label: localize('signals', 'Signals'), + settings: ['signals.*'] + }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From 92964ac45fb7750fd072890cc75e51d41e4fed11 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:33:02 -0600 Subject: [PATCH 0008/1863] fix issue with help from Daniel --- .../audioCues/browser/audioCueService.ts | 41 +++++-------------- .../browser/audioCues.contribution.ts | 6 +-- .../common/configurationEditing.ts | 2 +- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index dfdb1979b9b72..edb4f4809000c 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,39 +58,18 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - const audioCuesConfig = this.configurationService.getValue<{ [key: string]: boolean }>('audioCues'); - for (const entry in audioCuesConfig) { - if (entry) { - console.log('cue ', entry); - this.configurationService.updateValue('audioCues.' + entry, undefined); - if (entry === 'volume' || entry === 'enabled') { - // ignore, these are audio cue settings - } else if (entry === 'debouncePositionChanges') { - this.configurationService.updateValue('signals.' + entry, audioCuesConfig[entry]); - } else { - this.configurationService.updateValue('signals.' + entry + '.' + 'audioCue', audioCuesConfig[entry]); - } + this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + const config = AudioCue.allAudioCues; + for (const c of config) { + const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); + if (alertValue !== undefined || audioCueValue !== undefined) { + this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); + } else if (audioCueValue) { + this.configurationService.updateValue(newSettingsKey, { audioCue: audioCueValue }); } } - const alertsConfig = this.configurationService.getValue<{ [key: string]: boolean }>('accessibility.alert'); - for (let entry in alertsConfig) { - if (entry) { - console.log('alert ', entry); - if (entry === 'warning') { - entry = 'lineHasWarning'; - } else if (entry === 'error') { - entry = 'lineHasError'; - } else if (entry === 'foldedArea') { - entry = 'lineHasFoldedArea'; - } else if (entry === 'breakpoint') { - entry = 'onDebugBreak'; - } - this.configurationService.updateValue('accessibility.alert.' + entry, undefined); - this.configurationService.updateValue('signals.' + entry + '.' + 'alert', alertsConfig[entry]); - } - } - const signals = this.configurationService.getValue<{ [key: string]: boolean }>('signals'); - console.log(JSON.stringify(signals)); } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index a5df5042d0d44..3f10e272c2166 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -300,11 +300,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...signalFeatureBase, 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'signals.chatRequestSent.audioCue': { + 'audioCue': { 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'signals.chatRequestSent.alert': { + 'alert': { 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, @@ -328,7 +328,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...defaultNoAlert, 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), 'properties': { - 'signals.chatResponseReceived.audioCue': { + 'audioCue': { 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 721f789dadbe5..0c88c9fc11e83 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -471,7 +471,7 @@ export class ConfigurationEditing { } return this.textModelResolverService.createModelReference(resource); } - + // private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean { // If we write to a workspace standalone file and replace the entire contents (no key provided) // we can return here because any parse errors can safely be ignored since all contents are replaced From 93907a85e52bdb1467e849fc6684ed2617fbdf00 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:47:43 -0600 Subject: [PATCH 0009/1863] simplify settings with only cues --- .../audioCues/browser/audioCueService.ts | 4 +- .../browser/audioCues.contribution.ts | 61 ++++--------------- 2 files changed, 14 insertions(+), 51 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index edb4f4809000c..c8f74e4324dab 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -64,10 +64,10 @@ export class AudioCueService extends Disposable implements IAudioCueService { const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); - if (alertValue !== undefined || audioCueValue !== undefined) { + if (alertValue && audioCueValue) { this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); } else if (audioCueValue) { - this.configurationService.updateValue(newSettingsKey, { audioCue: audioCueValue }); + this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCueValue); } } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 3f10e272c2166..0226b3308bcfb 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -49,15 +49,6 @@ const signalFeatureBase: IConfigurationPropertySchema = { } }; -const defaultNoAlert: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'default': { - 'audioCue': 'auto', - } -}; - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'signals', order: 100, @@ -238,35 +229,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, } }, - 'signals.diffLineInserted': { - ...defaultNoAlert, - 'description': localize('signals.diffLineInserted', "Plays a signal when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineDeleted': { - ...defaultNoAlert, - 'description': localize('signals.diffLineDeleted', "Plays a signal when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineModified': { - ...defaultNoAlert, - 'description': localize('signals.diffLineModified', "Plays a signal when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - 'properties': { - 'audioCue': { - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase - } - } + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), }, 'signals.notebookCellCompleted': { ...signalFeatureBase, @@ -324,15 +297,9 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }, }, - 'signals.chatResponseReceived': { - ...defaultNoAlert, - 'description': localize('signals.chatResponseReceived', "Plays a signal on loop while the response has been received."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase }, 'signals.clear': { ...signalFeatureBase, @@ -410,10 +377,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('signals.format.alert.never', "Never plays the alert.") ], }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' } }, } From 0cd0c9705335a7dc9f5a8a49683f6f32a40d0153 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:56:40 -0600 Subject: [PATCH 0010/1863] Add deprecation messages --- .../browser/accessibilityConfiguration.ts | 106 +++++++----------- .../browser/audioCues.contribution.ts | 58 +++++----- 2 files changed, 74 insertions(+), 90 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 9c18fc789c89a..aca8a31a86377 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -70,11 +70,19 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseProperty: object = { +const baseVerbosityProperty: object = { type: 'boolean', default: true, tags: ['accessibility'] }; +const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const baseAlertProperty: object = { + type: 'boolean', + default: true, + tags: ['accessibility'], + markdownDeprecationMessage +}; + export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', @@ -87,47 +95,47 @@ const configuration: IConfigurationNode = { properties: { [AccessibilityVerbositySettingId.Terminal]: { description: localize('verbosity.terminal.description', 'Provide information about how to access the terminal accessibility help menu when the terminal is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.DiffEditor]: { description: localize('verbosity.diffEditor.description', 'Provide information about how to navigate changes in the diff editor when it is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Chat]: { description: localize('verbosity.chat.description', 'Provide information about how to access the chat help menu when the chat input is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.InlineChat]: { description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu and alert with hints that describe how to use the feature when the input is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.InlineCompletions]: { description: localize('verbosity.inlineCompletions.description', 'Provide information about how to access the inline completions hover and Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.KeybindingsEditor]: { description: localize('verbosity.keybindingsEditor.description', 'Provide information about how to change a keybinding in the keybindings editor when a row is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Notebook]: { description: localize('verbosity.notebook', 'Provide information about how to focus the cell container or inner editor when a notebook cell is focused.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Hover]: { description: localize('verbosity.hover', 'Provide information about how to open the hover in an Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Notification]: { description: localize('verbosity.notification', 'Provide information about how to open the notification in an Accessible View.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.EmptyEditorHint]: { description: localize('verbosity.emptyEditorHint', 'Provide information about relevant actions in an empty text editor.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Comments]: { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), - ...baseProperty + ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), @@ -138,13 +146,12 @@ const configuration: IConfigurationNode = { localize('alert.save.always', "Alerts whenever is a file is saved, including auto save."), localize('alert.save.never', "Never alerts.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), @@ -156,103 +163,72 @@ const configuration: IConfigurationNode = { localize('alert.format.always', "Alerts whenever is a file is formatted, including auto save, on cell execution, and more."), localize('alert.format.never', "Never alerts.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { 'markdownDescription': localize('alert.terminalQuickFix', "Alerts when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated."), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { 'markdownDescription': localize('alert.terminalCommandFailed', "Alerts when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { 'markdownDescription': localize('alert.taskFailed', "Alerts when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { 'markdownDescription': localize('alert.taskCompleted', "Alerts when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { 'markdownDescription': localize('alert.chatRequestSent', "Alerts when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { 'markdownDescription': localize('alert.chatResponsePending', "Alerts when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { 'markdownDescription': localize('alert.noInlayHints', "Alerts when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), - 'type': 'boolean', - 'default': true, - tags: ['accessibility'] + ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { markdownDescription: localize('terminal.integrated.accessibleView.closeOnKeyPress', "On keypress, close the Accessible View and focus the element from which it was invoked."), diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 0226b3308bcfb..e8605ed343498 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -29,7 +29,12 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { localize('audioCues.enabled.on', "Enable audio cue."), localize('audioCues.enabled.off', "Disable audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], +}; +const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); +const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...audioCueFeatureBase, + markdownDeprecationMessage }; const alertFeatureBase: IConfigurationPropertySchema = { @@ -400,96 +405,97 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, - tags: ['accessibility'] + tags: ['accessibility'], + 'markdownDeprecationMessage': localize('audioCues.debouncePositionChangesDeprecated', 'This setting is deprecated, instead use the `signals.debouncePositionChanges` setting.') }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { @@ -502,7 +508,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.save.always', "Plays the audio cue whenever a file is saved, including auto save."), localize('audioCues.save.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, 'audioCues.format': { 'markdownDescription': localize('audioCues.format', "Plays a sound when a file or notebook is formatted. Also see {0}", '`#accessibility.alert.format#`'), @@ -514,7 +521,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), localize('audioCues.format.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, }, }); From bb63da6b6271d2b96bf2bd5596e317bb64ecdcdc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 11:58:24 -0600 Subject: [PATCH 0011/1863] better type --- .../accessibility/browser/accessibilityConfiguration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index aca8a31a86377..265697be7e1a5 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -70,13 +70,13 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseVerbosityProperty: object = { +const baseVerbosityProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'] }; const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); -const baseAlertProperty: object = { +const baseAlertProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'], From bf0a1b52341af8524e9136cc709a8d1cdc4f0ade Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 31 Jan 2024 12:53:21 -0600 Subject: [PATCH 0012/1863] render in settings ui better --- .../contrib/audioCues/browser/audioCues.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index e8605ed343498..b066ce9a2ba43 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -47,7 +47,7 @@ const alertFeatureBase: IConfigurationPropertySchema = { const signalFeatureBase: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, default: { audioCue: 'auto', alert: true From 98715d77b1e1afeb9687941b92f8d2cc53a9ab91 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 14:33:57 -0600 Subject: [PATCH 0013/1863] use on or off for alert setting --- .../audioCues/browser/audioCueService.ts | 18 +- .../browser/accessibility.contribution.ts | 347 +++++++++++++++++ .../browser/audioCues.contribution.ts | 352 +----------------- 3 files changed, 360 insertions(+), 357 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index c8f74e4324dab..95e7354ca5ade 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,13 +61,19 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { - const alertValue = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCueValue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); - if (alertValue && audioCueValue) { - this.configurationService.updateValue(newSettingsKey, { alert: alertValue, audioCue: audioCueValue }); - } else if (audioCueValue) { - this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCueValue); + if (alertConfig !== undefined && audioCue !== undefined) { + let alert; + if (typeof alertConfig === 'string') { + alert = alertConfig; + } else { + alert = alertConfig === true && this.accessibilityService.isScreenReaderOptimized() ? 'on' : 'off'; + } + this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); + } else if (!c.alertSettingsKey && audioCue !== undefined) { + this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCue); } } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 884163dd8e212..1999a608d2b6b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -15,6 +15,9 @@ import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/ import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { localize } from 'vs/nls'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -31,3 +34,347 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAc workbenchContributionsRegistry.registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: false, + default: { + audioCue: 'auto', + alert: 'on' + } +}; + +export const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'string', + 'enum': ['on', 'off'], + 'default': 'on', + 'enumDescriptions': [ + localize('audioCues.enabled.on', "Enable alert, will only apply when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable alert.") + ], + tags: ['accessibility'], +}; + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'signals', + order: 100, + title: localize('signals', "Signals"), + type: 'object', + 'properties': { + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + ...signalFeatureBase, + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasInlineSuggestion.audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'signals.lineHasError': { + ...signalFeatureBase, + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasFoldedArea': { + ...signalFeatureBase, + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + ...signalFeatureBase, + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + ...signalFeatureBase, + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + ...signalFeatureBase, + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + ...signalFeatureBase, + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + ...signalFeatureBase, + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + ...signalFeatureBase, + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + ...signalFeatureBase, + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + ...signalFeatureBase, + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.notebookCellCompleted': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + ...signalFeatureBase, + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + } + }, + 'signals.chatResponsePending': { + ...signalFeatureBase, + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'signals.clear': { + ...signalFeatureBase, + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' + } + }, + 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, + } +}); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index b066ce9a2ba43..9059db0b31fb8 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -20,7 +20,7 @@ registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); -const audioCueFeatureBase: IConfigurationPropertySchema = { +export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', @@ -37,356 +37,6 @@ const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { markdownDeprecationMessage }; -const alertFeatureBase: IConfigurationPropertySchema = { - 'type': 'boolean', - 'default': true, - 'tags': ['accessibility'], - required: ['audioCue'] -}; - -const signalFeatureBase: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: false, - default: { - audioCue: 'auto', - alert: true - } -}; - -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'signals', - order: 100, - title: localize('signals', "Signals"), - type: 'object', - 'properties': { - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), - 'type': 'boolean', - 'default': false, - tags: ['accessibility'] - }, - 'signals.lineHasBreakpoint': { - ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasInlineSuggestion': { - ...signalFeatureBase, - 'description': localize('signals.lineHasInlineSuggestion', "Plays a signal when the active line has an inline suggestion."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasInlineSuggestion.alert', "Alerts when the active line has an inline suggestion."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasError': { - ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasFoldedArea': { - ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasWarning': { - ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), - ...alertFeatureBase - }, - }, - }, - 'signals.onDebugBreak': { - ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), - ...alertFeatureBase - }, - } - }, - 'signals.noInlayHints': { - ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), - 'properties': { - 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase - }, - } - }, - 'signals.taskCompleted': { - ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), - ...alertFeatureBase - }, - } - }, - 'signals.taskFailed': { - ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalCommandFailed': { - ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalQuickFix': { - ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalBell': { - ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), - ...alertFeatureBase - }, - } - }, - 'signals.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.notebookCellCompleted': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), - ...alertFeatureBase - }, - } - }, - 'signals.notebookCellFailed': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), - ...alertFeatureBase - }, - } - }, - 'signals.chatRequestSent': { - ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), - ...alertFeatureBase - }, - } - }, - 'signals.chatResponsePending': { - ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase - }, - }, - }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - 'signals.clear': { - ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - 'properties': { - 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), - ...alertFeatureBase - }, - }, - }, - 'signals.save': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), - 'properties': { - 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") - ], - }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' - } - }, - 'signals.format': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), - 'properties': { - 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") - ], - }, - } - }, - } -}); - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'properties': { 'audioCues.enabled': { From 4c30c08b665f8e61d8118145c30a2fbc0f7bd097 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 14:48:33 -0600 Subject: [PATCH 0014/1863] fix issue --- src/vs/platform/audioCues/browser/audioCueService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 95e7354ca5ade..0ad2662570f1d 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -61,8 +61,8 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { - const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; + const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; + const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; From 916c5ee556ec473f1792e382a3462c43804e2e46 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:13:36 -0600 Subject: [PATCH 0015/1863] auto, off as options --- src/vs/platform/audioCues/browser/audioCueService.ts | 2 +- .../accessibility/browser/accessibility.contribution.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0ad2662570f1d..668ebf9ed8362 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -69,7 +69,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { if (typeof alertConfig === 'string') { alert = alertConfig; } else { - alert = alertConfig === true && this.accessibilityService.isScreenReaderOptimized() ? 'on' : 'off'; + alert = alertConfig === false ? 'off' : 'auto'; } this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); } else if (!c.alertSettingsKey && audioCue !== undefined) { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 1999a608d2b6b..3fc5ccd2918bf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -41,16 +41,16 @@ const signalFeatureBase: IConfigurationPropertySchema = { additionalProperties: false, default: { audioCue: 'auto', - alert: 'on' + alert: 'auto' } }; export const alertFeatureBase: IConfigurationPropertySchema = { 'type': 'string', - 'enum': ['on', 'off'], - 'default': 'on', + 'enum': ['auto', 'off'], + 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.on', "Enable alert, will only apply when in screen reader optimized mode."), + localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), localize('audioCues.enabled.off', "Disable alert.") ], tags: ['accessibility'], From b65b69e5dd59ff9528fdbc6df11ae893a69d78a4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:19:51 -0600 Subject: [PATCH 0016/1863] move to within accessibility configuration --- .../browser/accessibility.contribution.ts | 347 ------------------ .../browser/accessibilityConfiguration.ts | 338 ++++++++++++++++- 2 files changed, 337 insertions(+), 348 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 3fc5ccd2918bf..884163dd8e212 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -15,9 +15,6 @@ import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/ import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp'; import { SaveAudioCueContribution } from 'vs/workbench/contrib/accessibility/browser/saveAudioCue'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; -import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { localize } from 'vs/nls'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -34,347 +31,3 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InlineCompletionsAc workbenchContributionsRegistry.registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchContributionInstantiation.BlockRestore); workbenchContributionsRegistry.registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchContributionInstantiation.BlockRestore); - -const signalFeatureBase: IConfigurationPropertySchema = { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: false, - default: { - audioCue: 'auto', - alert: 'auto' - } -}; - -export const alertFeatureBase: IConfigurationPropertySchema = { - 'type': 'string', - 'enum': ['auto', 'off'], - 'default': 'auto', - 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable alert.") - ], - tags: ['accessibility'], -}; - -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'signals', - order: 100, - title: localize('signals', "Signals"), - type: 'object', - 'properties': { - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), - 'type': 'boolean', - 'default': false, - tags: ['accessibility'] - }, - 'signals.lineHasBreakpoint': { - ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasInlineSuggestion.audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase - }, - 'signals.lineHasError': { - ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), - ...alertFeatureBase - }, - }, - }, - 'signals.lineHasFoldedArea': { - ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), - ...alertFeatureBase - }, - } - }, - 'signals.lineHasWarning': { - ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), - 'properties': { - 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), - ...alertFeatureBase - }, - }, - }, - 'signals.onDebugBreak': { - ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), - 'properties': { - 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), - ...alertFeatureBase - }, - } - }, - 'signals.noInlayHints': { - ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), - 'properties': { - 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase - }, - } - }, - 'signals.taskCompleted': { - ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), - ...alertFeatureBase - }, - } - }, - 'signals.taskFailed': { - ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalCommandFailed': { - ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalQuickFix': { - ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), - ...alertFeatureBase - }, - } - }, - 'signals.terminalBell': { - ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), - 'properties': { - 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), - ...alertFeatureBase - }, - } - }, - 'signals.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - }, - 'signals.notebookCellCompleted': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), - ...alertFeatureBase - }, - } - }, - 'signals.notebookCellFailed': { - ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), - 'properties': { - 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), - ...alertFeatureBase - }, - } - }, - 'signals.chatRequestSent': { - ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), - ...alertFeatureBase - }, - } - }, - 'signals.chatResponsePending': { - ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), - 'properties': { - 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase - }, - }, - }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase - }, - 'signals.clear': { - ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - 'properties': { - 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase - }, - 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), - ...alertFeatureBase - }, - }, - }, - 'signals.save': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), - 'properties': { - 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") - ], - }, - }, - default: { - 'audioCue': 'never', - 'alert': 'never' - } - }, - 'signals.format': { - 'type': 'object', - 'tags': ['accessibility'], - additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), - 'properties': { - 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") - ], - }, - 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), - 'type': 'string', - 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', - 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") - ], - }, - } - }, - } -}); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a73686b7b484a..79a8445191a49 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -13,6 +13,7 @@ import { ISpeechService } 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'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -83,13 +84,34 @@ const baseAlertProperty: IConfigurationPropertySchema = { markdownDeprecationMessage }; - export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', title: localize('accessibilityConfigurationTitle', "Accessibility"), type: 'object' }); + +const signalFeatureBase: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: false, + default: { + audioCue: 'auto', + alert: 'auto' + } +}; + +export const alertFeatureBase: IConfigurationPropertySchema = { + 'type': 'string', + 'enum': ['auto', 'off'], + 'default': 'auto', + 'enumDescriptions': [ + localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable alert.") + ], + tags: ['accessibility'], +}; + const configuration: IConfigurationNode = { ...accessibilityConfigurationNodeBase, properties: { @@ -235,6 +257,320 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, + 'signals.debouncePositionChanges': { + 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'type': 'boolean', + 'default': false, + tags: ['accessibility'] + }, + 'signals.lineHasBreakpoint': { + ...signalFeatureBase, + 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasInlineSuggestion.audioCue': { + 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + }, + 'signals.lineHasError': { + ...signalFeatureBase, + 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + ...alertFeatureBase + }, + }, + }, + 'signals.lineHasFoldedArea': { + ...signalFeatureBase, + 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + ...alertFeatureBase + }, + } + }, + 'signals.lineHasWarning': { + ...signalFeatureBase, + 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'properties': { + 'audioCue': { + 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + ...alertFeatureBase + }, + }, + }, + 'signals.onDebugBreak': { + ...signalFeatureBase, + 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'properties': { + 'audioCue': { + 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + ...alertFeatureBase + }, + } + }, + 'signals.noInlayHints': { + ...signalFeatureBase, + 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'properties': { + 'audioCue': { + 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + ...alertFeatureBase + }, + } + }, + 'signals.taskCompleted': { + ...signalFeatureBase, + 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + ...alertFeatureBase + }, + } + }, + 'signals.taskFailed': { + ...signalFeatureBase, + 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalCommandFailed': { + ...signalFeatureBase, + 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalQuickFix': { + ...signalFeatureBase, + 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + ...alertFeatureBase + }, + } + }, + 'signals.terminalBell': { + ...signalFeatureBase, + 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'properties': { + 'audioCue': { + 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + ...alertFeatureBase + }, + } + }, + 'signals.diffLineInserted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineDeleted.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.diffLineModified.audioCue': { + ...audioCueFeatureBase, + 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + }, + 'signals.notebookCellCompleted': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + ...alertFeatureBase + }, + } + }, + 'signals.notebookCellFailed': { + ...signalFeatureBase, + 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'properties': { + 'audioCue': { + 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + ...alertFeatureBase + }, + } + }, + 'signals.chatRequestSent': { + ...signalFeatureBase, + 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + ...alertFeatureBase + }, + } + }, + 'signals.chatResponsePending': { + ...signalFeatureBase, + 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'properties': { + 'audioCue': { + 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + ...alertFeatureBase + }, + }, + }, + 'signals.chatResponseReceived.audioCue': { + 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + 'signals.clear': { + ...signalFeatureBase, + 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'properties': { + 'audioCue': { + 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + ...audioCueFeatureBase + }, + 'alert': { + 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + ...alertFeatureBase + }, + }, + }, + 'signals.save': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'properties': { + 'audioCue': { + 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('signals.save.audioCue.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('signals.save.alert.never', "Never plays the audio cue.") + ], + }, + }, + default: { + 'audioCue': 'never', + 'alert': 'never' + } + }, + 'signals.format': { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'properties': { + 'audioCue': { + 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.never', "Never plays the audio cue.") + ], + }, + 'alert': { + 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('signals.format.alert.never', "Never plays the alert.") + ], + }, + } + }, } }; From f81165f9ff7bc7a7ba9850145ce5487e86086d86 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:23:08 -0600 Subject: [PATCH 0017/1863] signals -> accessibility.statusIndicators --- .../browser/accessibilityConfiguration.ts | 192 +++++++++--------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 79a8445191a49..6f1d07717ed1f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -257,282 +257,282 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, - 'signals.debouncePositionChanges': { - 'description': localize('signals.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'accessibility.statusIndicators.debouncePositionChanges': { + 'description': localize('accessibility.statusIndicators.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, tags: ['accessibility'] }, - 'signals.lineHasBreakpoint': { + 'accessibility.statusIndicators.lineHasBreakpoint': { ...signalFeatureBase, - 'description': localize('signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), ...alertFeatureBase }, }, }, - 'signals.lineHasInlineSuggestion.audioCue': { - 'description': localize('signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + 'accessibility.statusIndicators.lineHasInlineSuggestion.audioCue': { + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), ...audioCueFeatureBase }, - 'signals.lineHasError': { + 'accessibility.statusIndicators.lineHasError': { ...signalFeatureBase, - 'description': localize('signals.lineHasError', "Plays a signal when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError', "Plays a signal when the active line has an error."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasError.alert', "Alerts when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Alerts when the active line has an error."), ...alertFeatureBase }, }, }, - 'signals.lineHasFoldedArea': { + 'accessibility.statusIndicators.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('signals.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } }, - 'signals.lineHasWarning': { + 'accessibility.statusIndicators.lineHasWarning': { ...signalFeatureBase, - 'description': localize('signals.lineHasWarning', "Plays a signal when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { - 'description': localize('signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.lineHasWarning.alert', "Alerts when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Alerts when the active line has a warning."), ...alertFeatureBase }, }, }, - 'signals.onDebugBreak': { + 'accessibility.statusIndicators.onDebugBreak': { ...signalFeatureBase, - 'description': localize('signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } }, - 'signals.noInlayHints': { + 'accessibility.statusIndicators.noInlayHints': { ...signalFeatureBase, - 'description': localize('signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { - 'description': localize('signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } }, - 'signals.taskCompleted': { + 'accessibility.statusIndicators.taskCompleted': { ...signalFeatureBase, - 'description': localize('signals.taskCompleted', "Plays a signal when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { - 'description': localize('signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.taskCompleted.alert', "Alerts when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Alerts when a task is completed."), ...alertFeatureBase }, } }, - 'signals.taskFailed': { + 'accessibility.statusIndicators.taskFailed': { ...signalFeatureBase, - 'description': localize('signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'signals.terminalCommandFailed': { + 'accessibility.statusIndicators.terminalCommandFailed': { ...signalFeatureBase, - 'description': localize('signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'signals.terminalQuickFix': { + 'accessibility.statusIndicators.terminalQuickFix': { ...signalFeatureBase, - 'description': localize('signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), ...alertFeatureBase }, } }, - 'signals.terminalBell': { + 'accessibility.statusIndicators.terminalBell': { ...signalFeatureBase, - 'description': localize('signals.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { - 'description': localize('signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.terminalBell.alert', "Alerts when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Alerts when the terminal bell is ringing."), ...alertFeatureBase }, } }, - 'signals.diffLineInserted.audioCue': { + 'accessibility.statusIndicators.diffLineInserted.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineDeleted.audioCue': { + 'accessibility.statusIndicators.diffLineDeleted.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.diffLineModified.audioCue': { + 'accessibility.statusIndicators.diffLineModified.audioCue': { ...audioCueFeatureBase, - 'description': localize('signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), }, - 'signals.notebookCellCompleted': { + 'accessibility.statusIndicators.notebookCellCompleted': { ...signalFeatureBase, - 'description': localize('signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { - 'description': localize('signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } }, - 'signals.notebookCellFailed': { + 'accessibility.statusIndicators.notebookCellFailed': { ...signalFeatureBase, - 'description': localize('signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { - 'description': localize('signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), ...alertFeatureBase }, } }, - 'signals.chatRequestSent': { + 'accessibility.statusIndicators.chatRequestSent': { ...signalFeatureBase, - 'description': localize('signals.chatRequestSent', "Plays a signal when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { 'audioCue': { - 'description': localize('signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.chatRequestSent.alert', "Alerts when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Alerts when a chat request is made."), ...alertFeatureBase }, } }, - 'signals.chatResponsePending': { + 'accessibility.statusIndicators.chatResponsePending': { ...signalFeatureBase, - 'description': localize('signals.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { - 'description': localize('signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.statusIndicators.chatResponsePending.alert', "Alerts on loop while the response is pending."), ...alertFeatureBase }, }, }, - 'signals.chatResponseReceived.audioCue': { - 'description': localize('signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + 'accessibility.statusIndicators.chatResponseReceived.audioCue': { + 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, - 'signals.clear': { + 'accessibility.statusIndicators.clear': { ...signalFeatureBase, - 'description': localize('signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.statusIndicators.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { - 'description': localize('signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('signals.clear.alert', "Alerts when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.alert', "Alerts when a feature is cleared."), ...alertFeatureBase }, }, }, - 'signals.save': { + 'accessibility.statusIndicators.save': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('signals.save', "Plays a signal when a file is saved."), + 'markdownDescription': localize('accessibility.statusIndicators.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { - 'description': localize('signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.audioCue', "Plays an audio cue when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('signals.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.statusIndicators.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.statusIndicators.save.audioCue.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('signals.save.alert', "Alerts when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.alert', "Alerts when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('signals.save.alert.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('accessibility.statusIndicators.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('accessibility.statusIndicators.save.alert.never', "Never plays the audio cue.") ], }, }, @@ -541,32 +541,32 @@ const configuration: IConfigurationNode = { 'alert': 'never' } }, - 'signals.format': { + 'accessibility.statusIndicators.format': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('signals.format', "Plays a signal when a file or notebook is formatted."), + 'markdownDescription': localize('accessibility.statusIndicators.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { - 'description': localize('signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.never', "Never plays the audio cue.") + localize('accessibility.statusIndicators.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('accessibility.statusIndicators.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.statusIndicators.format.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('signals.format.alert', "Alerts when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.alert', "Alerts when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('signals.format.alert.never', "Never plays the alert.") + localize('accessibility.statusIndicators.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('accessibility.statusIndicators.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.statusIndicators.format.alert.never', "Never plays the alert.") ], }, } From 7dcb9b754e58cd2fdfec5190b1d26c4549f02bdd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:26:55 -0600 Subject: [PATCH 0018/1863] tweak more words --- .../audioCues/browser/audioCueService.ts | 36 ++++----- .../browser/accessibilityConfiguration.ts | 80 +++++++++---------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 668ebf9ed8362..c231512c5dc25 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -408,28 +408,28 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('audioCues.lineHasError.alertMessage', 'Error') + alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error') }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('audioCues.lineHasWarning.alertMessage', 'Warning') + alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning') }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('audioCues.lineHasFoldedArea.alertMessage', 'Folded') + alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded') }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('audioCues.lineHasBreakpoint.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint') }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), @@ -442,7 +442,7 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('audioCues.terminalQuickFix.alertMessage', 'Quick Fix') + alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix') }); public static readonly onDebugBreak = AudioCue.register({ @@ -450,7 +450,7 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('audioCues.onDebugBreak.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint') }); public static readonly noInlayHints = AudioCue.register({ @@ -458,7 +458,7 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('audioCues.noInlayHints.alertMessage', 'No Inlay Hints') + alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints') }); public static readonly taskCompleted = AudioCue.register({ @@ -466,7 +466,7 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('audioCues.taskCompleted.alertMessage', 'Task Completed') + alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed') }); public static readonly taskFailed = AudioCue.register({ @@ -474,7 +474,7 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('audioCues.taskFailed.alertMessage', 'Task Failed') + alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed') }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -482,7 +482,7 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('audioCues.terminalCommandFailed.alertMessage', 'Command Failed') + alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed') }); public static readonly terminalBell = AudioCue.register({ @@ -490,7 +490,7 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('audioCues.terminalBell.alertMessage', 'Terminal Bell') + alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell') }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -498,7 +498,7 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('audioCues.notebookCellCompleted.alertMessage', 'Notebook Cell Completed') + alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed') }); public static readonly notebookCellFailed = AudioCue.register({ @@ -506,7 +506,7 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('audioCues.notebookCellFailed.alertMessage', 'Notebook Cell Failed') + alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed') }); public static readonly diffLineInserted = AudioCue.register({ @@ -532,7 +532,7 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('audioCues.chatRequestSent.alertMessage', 'Chat Request Sent') + alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent') }); public static readonly chatResponseReceived = AudioCue.register({ @@ -553,7 +553,7 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('audioCues.chatResponsePending.alertMessage', 'Chat Response Pending') + alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending') }); public static readonly clear = AudioCue.register({ @@ -561,7 +561,7 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('audioCues.clear.alertMessage', 'Clear') + alertMessage: localize('accessibility.statusIndicators.clear', 'Clear') }); public static readonly save = AudioCue.register({ @@ -569,7 +569,7 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('audioCues.save.alertMessage', 'Save') + alertMessage: localize('accessibility.statusIndicators.save', 'Save') }); public static readonly format = AudioCue.register({ @@ -577,7 +577,7 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('audioCues.format.alertMessage', 'Format') + alertMessage: localize('accessibility.statusIndicators.format', 'Format') }); private constructor( diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 6f1d07717ed1f..a1b8884d31ac3 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -160,96 +160,96 @@ const configuration: IConfigurationNode = { ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "Alerts when a file is saved. Also see {0}.", '`#audioCues.save#`'), + 'markdownDescription': localize('alert.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.save.userGesture', "Alerts when a file is saved via user gesture."), - localize('alert.save.always', "Alerts whenever is a file is saved, including auto save."), + localize('alert.save.userGesture', "Indicates when a file is saved via user gesture."), + localize('alert.save.always', "Indicates whenever is a file is saved, including auto save."), localize('alert.save.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { - 'markdownDescription': localize('alert.clear', "Alerts when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'markdownDescription': localize('alert.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "Alerts when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), + 'markdownDescription': localize('alert.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.format.userGesture', "Alerts when a file is formatted via user gesture."), - localize('alert.format.always', "Alerts whenever is a file is formatted, including auto save, on cell execution, and more."), + localize('alert.format.userGesture', "Indicates when a file is formatted via user gesture."), + localize('alert.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), localize('alert.format.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "Alerts when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('alert.breakpoint', "Indicates when the active line has a breakpoint. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "Alerts when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), + 'markdownDescription': localize('alert.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "Alerts when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), + 'markdownDescription': localize('alert.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "Alerts when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), + 'markdownDescription': localize('alert.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { - 'markdownDescription': localize('alert.terminalQuickFix', "Alerts when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), + 'markdownDescription': localize('alert.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { - 'markdownDescription': localize('alert.terminalBell', "Alerts when the terminal bell is activated."), + 'markdownDescription': localize('alert.terminalBell', "Indicates when the terminal bell is activated."), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { - 'markdownDescription': localize('alert.terminalCommandFailed', "Alerts when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), + 'markdownDescription': localize('alert.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { - 'markdownDescription': localize('alert.taskFailed', "Alerts when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), + 'markdownDescription': localize('alert.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { - 'markdownDescription': localize('alert.taskCompleted', "Alerts when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), + 'markdownDescription': localize('alert.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { - 'markdownDescription': localize('alert.chatRequestSent', "Alerts when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), + 'markdownDescription': localize('alert.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { - 'markdownDescription': localize('alert.chatResponsePending', "Alerts when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), + 'markdownDescription': localize('alert.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { - 'markdownDescription': localize('alert.noInlayHints', "Alerts when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), + 'markdownDescription': localize('alert.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { - 'markdownDescription': localize('alert.lineHasBreakpoint', "Alerts when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), + 'markdownDescription': localize('alert.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { - 'markdownDescription': localize('alert.notebookCellCompleted', "Alerts when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'markdownDescription': localize('alert.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { - 'markdownDescription': localize('alert.notebookCellFailed', "Alerts when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'markdownDescription': localize('alert.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { - 'markdownDescription': localize('alert.onDebugBreak', "Alerts when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('alert.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { @@ -272,7 +272,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Alerts when the active line has a breakpoint."), + 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), ...alertFeatureBase }, }, @@ -290,7 +290,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Alerts when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Indicates when the active line has an error."), ...alertFeatureBase }, }, @@ -304,7 +304,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Alerts when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } @@ -318,7 +318,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Alerts when the active line has a warning."), + 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Indicates when the active line has a warning."), ...alertFeatureBase }, }, @@ -332,7 +332,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Alerts when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } @@ -346,7 +346,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Alerts when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } @@ -360,7 +360,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Alerts when a task is completed."), + 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Indicates when a task is completed."), ...alertFeatureBase }, } @@ -374,7 +374,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Alerts when a task fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), ...alertFeatureBase }, } @@ -388,7 +388,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Alerts when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } @@ -402,7 +402,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Alerts when terminal Quick Fixes are available."), + 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), ...alertFeatureBase }, } @@ -416,7 +416,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Alerts when the terminal bell is ringing."), + 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Indicates when the terminal bell is ringing."), ...alertFeatureBase }, } @@ -442,7 +442,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Alerts when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } @@ -456,7 +456,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Alerts when a notebook cell execution fails."), + 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), ...alertFeatureBase }, } @@ -470,7 +470,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Alerts when a chat request is made."), + 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Indicates when a chat request is made."), ...alertFeatureBase }, } @@ -502,7 +502,7 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.clear.alert', "Alerts when a feature is cleared."), + 'description': localize('accessibility.statusIndicators.clear.alert', "Indicates when a feature is cleared."), ...alertFeatureBase }, }, @@ -525,7 +525,7 @@ const configuration: IConfigurationNode = { ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.save.alert', "Alerts when a file is saved."), + 'description': localize('accessibility.statusIndicators.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', @@ -559,7 +559,7 @@ const configuration: IConfigurationNode = { ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.format.alert', "Alerts when a file or notebook is formatted."), + 'description': localize('accessibility.statusIndicators.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', From b642d20936aa17fa1d316eb83b807d67c5266169 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 15:34:52 -0600 Subject: [PATCH 0019/1863] fix issues --- .../workbench/contrib/preferences/browser/settingsLayout.ts | 6 +++--- .../services/configuration/common/configurationEditing.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index f10aaea48ebe9..97a13dca2f9d1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -227,9 +227,9 @@ export const tocData: ITOCEntry = { settings: ['audioCues.*'] }, { - id: 'features/signals', - label: localize('signals', 'Signals'), - settings: ['signals.*'] + id: 'features/accessibilityStatusIndicators', + label: localize('accessibility.statusIndicators', 'Accessibility Status Indicators'), + settings: ['accessibility.statusIndicators.*'] }, { id: 'features/mergeEditor', diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 0c88c9fc11e83..721f789dadbe5 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -471,7 +471,7 @@ export class ConfigurationEditing { } return this.textModelResolverService.createModelReference(resource); } - // + private hasParseErrors(content: string, operation: IConfigurationEditOperation): boolean { // If we write to a workspace standalone file and replace the entire contents (no key provided) // we can return here because any parse errors can safely be ignored since all contents are replaced From a780b235dde376ff32118386f32e6b2e9128fae1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 1 Feb 2024 16:35:40 -0600 Subject: [PATCH 0020/1863] Fix another bug --- .../audioCues/browser/audioCueService.ts | 77 ++++++++++++------- .../browser/accessibilityConfiguration.ts | 73 ++++++++++++++---- .../contrib/audioCues/browser/commands.ts | 35 ++++++--- 3 files changed, 130 insertions(+), 55 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index c231512c5dc25..69e3551c77842 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,12 +58,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - this.configurationService.updateValue('signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + this.configurationService.updateValue('accessibility.statusIndicators.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'signals.'); + const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.statusIndicators.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; if (typeof alertConfig === 'string') { @@ -200,9 +200,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.settingsKey) + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.accessibilityStatusIndicatorSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -231,9 +231,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.accessibilityStatusIndicatorSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -390,11 +390,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; + accessibilityStatusIndicatorSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.accessibilityStatusIndicatorSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -408,33 +409,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error') + alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning') + alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded') + alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -442,7 +448,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix') + alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -450,7 +457,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint') + alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -458,7 +466,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints') + alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -466,7 +475,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed') + alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -474,7 +484,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed') + alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -482,7 +493,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed') + alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -490,7 +502,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell') + alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -498,7 +511,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed') + alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -506,25 +520,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed') + alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -532,7 +550,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent') + alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -546,6 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -553,7 +573,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending') + alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -561,7 +582,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.statusIndicators.clear', 'Clear') + alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.clear' }); public static readonly save = AudioCue.register({ @@ -569,7 +591,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.statusIndicators.save', 'Save') + alertMessage: localize('accessibility.statusIndicators.save', 'Save'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.save' }); public static readonly format = AudioCue.register({ @@ -577,14 +600,16 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.statusIndicators.format', 'Format') + alertMessage: localize('accessibility.statusIndicators.format', 'Format'), + accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, + public readonly accessibilityStatusIndicatorSettingsKey: string, public readonly alertSettingsKey?: string, - public readonly alertMessage?: string + public readonly alertMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index a1b8884d31ac3..ca6fb5c8d2e2a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -112,6 +112,15 @@ export const alertFeatureBase: IConfigurationPropertySchema = { tags: ['accessibility'], }; +const defaultNoAlert: IConfigurationPropertySchema = { + 'type': 'object', + 'tags': ['accessibility'], + additionalProperties: true, + 'default': { + 'audioCue': 'auto', + } +}; + const configuration: IConfigurationNode = { ...accessibilityConfigurationNodeBase, properties: { @@ -277,13 +286,19 @@ const configuration: IConfigurationNode = { }, }, }, - 'accessibility.statusIndicators.lineHasInlineSuggestion.audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase + 'accessibility.statusIndicators.lineHasInlineSuggestion': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + ...audioCueFeatureBase + } + } }, 'accessibility.statusIndicators.lineHasError': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasError', "Plays a signal when the active line has an error."), + 'description': localize('accessibility.statusIndicators.lineHasError', "Indicates when the active line has an error."), 'properties': { 'audioCue': { 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), @@ -297,7 +312,7 @@ const configuration: IConfigurationNode = { }, 'accessibility.statusIndicators.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Plays a signal when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), @@ -421,17 +436,35 @@ const configuration: IConfigurationNode = { }, } }, - 'accessibility.statusIndicators.diffLineInserted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineInserted.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineInserted': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, - 'accessibility.statusIndicators.diffLineDeleted.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineModified': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, - 'accessibility.statusIndicators.diffLineModified.audioCue': { - ...audioCueFeatureBase, - 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'accessibility.statusIndicators.diffLineDeleted': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...audioCueFeatureBase + } + } }, 'accessibility.statusIndicators.notebookCellCompleted': { ...signalFeatureBase, @@ -489,9 +522,15 @@ const configuration: IConfigurationNode = { }, }, }, - 'accessibility.statusIndicators.chatResponseReceived.audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase + 'accessibility.statusIndicators.chatResponseReceived': { + ...defaultNoAlert, + 'description': localize('accessibility.statusIndicators.chatResponseReceived', "Indicates when the response has been received."), + 'properties': { + 'audioCue': { + 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + ...audioCueFeatureBase + }, + } }, 'accessibility.statusIndicators.clear': { ...signalFeatureBase, diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index dbaac656df64f..6a2e3e52786c1 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.settingsKey)})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,14 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + } else { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + } } } for (const cue of disabledCues) { - if (userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, 'never'); + const alert = cue.alertMessage ? configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert') : undefined; + const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.settingsKey, 'off'); + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); } } qp.hide(); @@ -83,9 +91,10 @@ export class ShowAccessibilityAlertHelp extends Action2 { const audioCueService = accessor.get(IAudioCueService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); + const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) && cue.alertSettingsKey ? `${cue.name} (${configurationService.getValue(cue.alertSettingsKey)})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -101,15 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, true); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (alert) { + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + } } } for (const cue of disabledAlerts) { - if (userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, 'never'); - } else { - configurationService.updateValue(cue.alertSettingsKey!, false); - } + const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const audioCue = configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue'); + configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); } qp.hide(); }); From b92e9b2b73c280f47d993f68b6a581d51b7cfa32 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 10:13:02 -0600 Subject: [PATCH 0021/1863] statusIndicators->signals --- .../audioCues/browser/audioCueService.ts | 60 +++--- .../browser/accessibilityConfiguration.ts | 202 +++++++++--------- .../contrib/audioCues/browser/commands.ts | 24 +-- 3 files changed, 143 insertions(+), 143 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 69e3551c77842..b9a6d92c5c139 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -200,9 +200,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.accessibilityStatusIndicatorSettingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -231,9 +231,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.accessibilityStatusIndicatorSettingsKey) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.accessibilityStatusIndicatorSettingsKey + '.alert') : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -390,12 +390,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; - accessibilityStatusIndicatorSettingsKey: string; + signalSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.accessibilityStatusIndicatorSettingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -410,7 +410,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasError' + signalSettingsKey: 'accessibility.statusIndicators.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), @@ -418,7 +418,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasWarning' + signalSettingsKey: 'accessibility.statusIndicators.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), @@ -426,7 +426,7 @@ export class AudioCue { settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' + signalSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), @@ -434,13 +434,13 @@ export class AudioCue { settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' + signalSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' + signalSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -449,7 +449,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' + signalSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -458,7 +458,7 @@ export class AudioCue { settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.onDebugBreak' + signalSettingsKey: 'accessibility.statusIndicators.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -467,7 +467,7 @@ export class AudioCue { settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.noInlayHints' + signalSettingsKey: 'accessibility.statusIndicators.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -476,7 +476,7 @@ export class AudioCue { settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskCompleted' + signalSettingsKey: 'accessibility.statusIndicators.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -485,7 +485,7 @@ export class AudioCue { settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.taskFailed' + signalSettingsKey: 'accessibility.statusIndicators.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -494,7 +494,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' + signalSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -503,7 +503,7 @@ export class AudioCue { settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.terminalBell' + signalSettingsKey: 'accessibility.statusIndicators.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -512,7 +512,7 @@ export class AudioCue { settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' + signalSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -521,28 +521,28 @@ export class AudioCue { settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' + signalSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineInserted' + signalSettingsKey: 'accessibility.statusIndicators.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' + signalSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.diffLineModified' + signalSettingsKey: 'accessibility.statusIndicators.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -551,7 +551,7 @@ export class AudioCue { settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatRequestSent' + signalSettingsKey: 'accessibility.statusIndicators.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -565,7 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' + signalSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -574,7 +574,7 @@ export class AudioCue { settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.chatResponsePending' + signalSettingsKey: 'accessibility.statusIndicators.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -583,7 +583,7 @@ export class AudioCue { settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.clear' + signalSettingsKey: 'accessibility.statusIndicators.clear' }); public static readonly save = AudioCue.register({ @@ -592,7 +592,7 @@ export class AudioCue { settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, alertMessage: localize('accessibility.statusIndicators.save', 'Save'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.save' + signalSettingsKey: 'accessibility.statusIndicators.save' }); public static readonly format = AudioCue.register({ @@ -601,14 +601,14 @@ export class AudioCue { settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, alertMessage: localize('accessibility.statusIndicators.format', 'Format'), - accessibilityStatusIndicatorSettingsKey: 'accessibility.statusIndicators.format' + signalSettingsKey: 'accessibility.statusIndicators.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, - public readonly accessibilityStatusIndicatorSettingsKey: string, + public readonly signalSettingsKey: string, public readonly alertSettingsKey?: string, public readonly alertMessage?: string, ) { } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index ca6fb5c8d2e2a..7a16cca773968 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -266,312 +266,312 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, - 'accessibility.statusIndicators.debouncePositionChanges': { - 'description': localize('accessibility.statusIndicators.debouncePositionChanges', "Whether or not position changes should be debounced"), + 'accessibility.signals.debouncePositionChanges': { + 'description': localize('accessibility.signals.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, tags: ['accessibility'] }, - 'accessibility.statusIndicators.lineHasBreakpoint': { + 'accessibility.signals.lineHasBreakpoint': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), + 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.lineHasInlineSuggestion': { + 'accessibility.signals.lineHasInlineSuggestion': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), + 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), + 'description': localize('accessibility.signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.lineHasError': { + 'accessibility.signals.lineHasError': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasError', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError', "Indicates when the active line has an error."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasError.alert', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.lineHasFoldedArea': { + 'accessibility.signals.lineHasFoldedArea': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.lineHasWarning': { + 'accessibility.signals.lineHasWarning': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.lineHasWarning', "Plays a signal when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.lineHasWarning.alert', "Indicates when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.onDebugBreak': { + 'accessibility.signals.onDebugBreak': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.noInlayHints': { + 'accessibility.signals.noInlayHints': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.taskCompleted': { + 'accessibility.signals.taskCompleted': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.taskCompleted', "Plays a signal when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskCompleted.alert', "Indicates when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.taskFailed': { + 'accessibility.signals.taskFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalCommandFailed': { + 'accessibility.signals.terminalCommandFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalQuickFix': { + 'accessibility.signals.terminalQuickFix': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.terminalBell': { + 'accessibility.signals.terminalBell': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.terminalBell', "Plays a signal when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.terminalBell.alert', "Indicates when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.diffLineInserted': { + 'accessibility.signals.diffLineInserted': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.diffLineModified': { + 'accessibility.signals.diffLineModified': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.diffLineDeleted': { + 'accessibility.signals.diffLineDeleted': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + 'description': localize('accessibility.signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase } } }, - 'accessibility.statusIndicators.notebookCellCompleted': { + 'accessibility.signals.notebookCellCompleted': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.notebookCellFailed': { + 'accessibility.signals.notebookCellFailed': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.chatRequestSent': { + 'accessibility.signals.chatRequestSent': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.chatRequestSent', "Plays a signal when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatRequestSent.alert', "Indicates when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), ...alertFeatureBase }, } }, - 'accessibility.statusIndicators.chatResponsePending': { + 'accessibility.signals.chatResponsePending': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.chatResponsePending', "Plays a signal on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.chatResponseReceived': { + 'accessibility.signals.chatResponseReceived': { ...defaultNoAlert, - 'description': localize('accessibility.statusIndicators.chatResponseReceived', "Indicates when the response has been received."), + 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), + 'description': localize('accessibility.signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), ...audioCueFeatureBase }, } }, - 'accessibility.statusIndicators.clear': { + 'accessibility.signals.clear': { ...signalFeatureBase, - 'description': localize('accessibility.statusIndicators.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.clear.audioCue', "Plays an audio cue when a feature is cleared."), + 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, 'alert': { - 'description': localize('accessibility.statusIndicators.clear.alert', "Indicates when a feature is cleared."), + 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), ...alertFeatureBase }, }, }, - 'accessibility.statusIndicators.save': { + 'accessibility.signals.save': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('accessibility.statusIndicators.save', "Plays a signal when a file is saved."), + 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.save.audioCue', "Plays an audio cue when a file is saved."), + 'description': localize('accessibility.signals.save.audioCue', "Plays an audio cue when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('accessibility.statusIndicators.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('accessibility.statusIndicators.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.save.alert', "Indicates when a file is saved."), + 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('accessibility.statusIndicators.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('accessibility.statusIndicators.save.alert.never', "Never plays the audio cue.") + localize('accessibility.signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), + localize('accessibility.signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), + localize('accessibility.signals.save.alert.never', "Never plays the audio cue.") ], }, }, @@ -580,32 +580,32 @@ const configuration: IConfigurationNode = { 'alert': 'never' } }, - 'accessibility.statusIndicators.format': { + 'accessibility.signals.format': { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, - 'markdownDescription': localize('accessibility.statusIndicators.format', "Plays a signal when a file or notebook is formatted."), + 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'audioCue': { - 'description': localize('accessibility.statusIndicators.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), - localize('accessibility.statusIndicators.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.statusIndicators.format.never', "Never plays the audio cue.") + localize('accessibility.signals.format.userGesture', "Plays the audio cue when a user explicitly formats a file."), + localize('accessibility.signals.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.never', "Never plays the audio cue.") ], }, 'alert': { - 'description': localize('accessibility.statusIndicators.format.alert', "Indicates when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.statusIndicators.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('accessibility.statusIndicators.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.statusIndicators.format.alert.never', "Never plays the alert.") + localize('accessibility.signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), + localize('accessibility.signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.alert.never', "Never plays the alert.") ], }, } diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index 6a2e3e52786c1..624c67985db52 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue')})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,22 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } } for (const cue of disabledCues) { - const alert = cue.alertMessage ? configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert') : undefined; + const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } qp.hide(); @@ -94,7 +94,7 @@ export class ShowAccessibilityAlertHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.alert')})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -110,17 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.accessibilityStatusIndicatorSettingsKey); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; if (alert) { - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } } } for (const cue of disabledAlerts) { const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; - const audioCue = configurationService.getValue(cue.accessibilityStatusIndicatorSettingsKey + '.audioCue'); - configurationService.updateValue(cue.accessibilityStatusIndicatorSettingsKey, { audioCue, alert }); + const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } qp.hide(); }); From f97da26259ee6f7fec61cfc36249baaa55b54af9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 11:29:35 -0600 Subject: [PATCH 0022/1863] set default to false for inline alerts --- .../browser/accessibilityConfiguration.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index e8270539962cd..7f7aac2224a5b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -282,7 +282,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, @@ -306,7 +307,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, @@ -316,7 +318,8 @@ const configuration: IConfigurationNode = { 'properties': { 'audioCue': { 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase + ...audioCueFeatureBase, + default: 'off' }, 'alert': { 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), @@ -334,7 +337,8 @@ const configuration: IConfigurationNode = { }, 'alert': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), - ...alertFeatureBase + ...alertFeatureBase, + default: 'off' }, }, }, From 373768ecdfc97397167caaeccd287773b7db6a42 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 11:35:40 -0600 Subject: [PATCH 0023/1863] fix issue --- .../audioCues/browser/audioCueService.ts | 88 +++++++++---------- .../preferences/browser/settingsLayout.ts | 6 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index b9a6d92c5c139..0da53fe2963de 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -58,12 +58,12 @@ export class AudioCueService extends Disposable implements IAudioCueService { } private _migrateSettings(): void { - this.configurationService.updateValue('accessibility.statusIndicators.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); + this.configurationService.updateValue('accessibility.signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); const config = AudioCue.allAudioCues; for (const c of config) { const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.statusIndicators.'); + const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.signals.'); if (alertConfig !== undefined && audioCue !== undefined) { let alert; if (typeof alertConfig === 'string') { @@ -73,7 +73,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { } this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); } else if (!c.alertSettingsKey && audioCue !== undefined) { - this.configurationService.updateValue(newSettingsKey + '.audioCue', audioCue); + this.configurationService.updateValue(newSettingsKey, { audioCue }); } } } @@ -409,38 +409,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.statusIndicators.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasError' + alertMessage: localize('accessibility.signals.lineHasError', 'Error'), + signalSettingsKey: 'accessibility.signals.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.statusIndicators.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasWarning' + alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + signalSettingsKey: 'accessibility.signals.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.statusIndicators.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasFoldedArea' + alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.statusIndicators.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.statusIndicators.lineHasBreakpoint' + alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.statusIndicators.lineHasInlineSuggestion' + signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -448,8 +448,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.statusIndicators.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.statusIndicators.terminalQuickFix' + alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + signalSettingsKey: 'accessibility.signals.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -457,8 +457,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.statusIndicators.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.statusIndicators.onDebugBreak' + alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -466,8 +466,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.statusIndicators.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.statusIndicators.noInlayHints' + alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + signalSettingsKey: 'accessibility.signals.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -475,8 +475,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.statusIndicators.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.statusIndicators.taskCompleted' + alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + signalSettingsKey: 'accessibility.signals.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -484,8 +484,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.statusIndicators.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.statusIndicators.taskFailed' + alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + signalSettingsKey: 'accessibility.signals.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -493,8 +493,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.statusIndicators.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.statusIndicators.terminalCommandFailed' + alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + signalSettingsKey: 'accessibility.signals.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -502,8 +502,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.statusIndicators.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.statusIndicators.terminalBell' + alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + signalSettingsKey: 'accessibility.signals.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -511,8 +511,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.statusIndicators.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.statusIndicators.notebookCellCompleted' + alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + signalSettingsKey: 'accessibility.signals.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -520,29 +520,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.statusIndicators.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.statusIndicators.notebookCellFailed' + alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + signalSettingsKey: 'accessibility.signals.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.statusIndicators.diffLineInserted' + signalSettingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.statusIndicators.diffLineDeleted' + signalSettingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.statusIndicators.diffLineModified' + signalSettingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -550,8 +550,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.statusIndicators.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.statusIndicators.chatRequestSent' + alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + signalSettingsKey: 'accessibility.signals.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -565,7 +565,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, - signalSettingsKey: 'accessibility.statusIndicators.chatResponseReceived' + signalSettingsKey: 'accessibility.signals.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -573,8 +573,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.statusIndicators.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.statusIndicators.chatResponsePending' + alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + signalSettingsKey: 'accessibility.signals.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -582,8 +582,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.statusIndicators.clear', 'Clear'), - signalSettingsKey: 'accessibility.statusIndicators.clear' + alertMessage: localize('accessibility.signals.clear', 'Clear'), + signalSettingsKey: 'accessibility.signals.clear' }); public static readonly save = AudioCue.register({ @@ -591,8 +591,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.statusIndicators.save', 'Save'), - signalSettingsKey: 'accessibility.statusIndicators.save' + alertMessage: localize('accessibility.signals.save', 'Save'), + signalSettingsKey: 'accessibility.signals.save' }); public static readonly format = AudioCue.register({ @@ -600,8 +600,8 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.statusIndicators.format', 'Format'), - signalSettingsKey: 'accessibility.statusIndicators.format' + alertMessage: localize('accessibility.signals.format', 'Format'), + signalSettingsKey: 'accessibility.signals.format' }); private constructor( diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 97a13dca2f9d1..54ac57ce22444 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -227,9 +227,9 @@ export const tocData: ITOCEntry = { settings: ['audioCues.*'] }, { - id: 'features/accessibilityStatusIndicators', - label: localize('accessibility.statusIndicators', 'Accessibility Status Indicators'), - settings: ['accessibility.statusIndicators.*'] + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] }, { id: 'features/mergeEditor', From e3de24d808d56666dd93b7796cb54bf4fd2fe5af Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 12:13:14 -0600 Subject: [PATCH 0024/1863] try to migrate a different way --- .../audioCues/browser/audioCueService.ts | 22 ---------- .../browser/accessibilityConfiguration.ts | 44 ++++++++++++++++++- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0da53fe2963de..7c7dfbe056719 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -54,28 +54,6 @@ export class AudioCueService extends Disposable implements IAudioCueService { @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); - this._migrateSettings(); - } - - private _migrateSettings(): void { - this.configurationService.updateValue('accessibility.signals.debouncePositionChanges', this.configurationService.getValue('audioCues.debouncePositionChanges')); - const config = AudioCue.allAudioCues; - for (const c of config) { - const alertConfig = c.alertSettingsKey ? this.configurationService.getValue(c.alertSettingsKey) : undefined; - const audioCue = c.settingsKey ? this.configurationService.getValue(c.settingsKey) : undefined; - const newSettingsKey = c.settingsKey.replace('audioCues.', 'accessibility.signals.'); - if (alertConfig !== undefined && audioCue !== undefined) { - let alert; - if (typeof alertConfig === 'string') { - alert = alertConfig; - } else { - alert = alertConfig === false ? 'off' : 'auto'; - } - this.configurationService.updateValue(newSettingsKey, { alert, audioCue }); - } else if (!c.alertSettingsKey && audioCue !== undefined) { - this.configurationService.updateValue(newSettingsKey, { audioCue }); - } - } } public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 7f7aac2224a5b..0f9f0990ab180 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -7,8 +7,8 @@ import { localize } from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; +import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; +import { AccessibilityAlertSettingId, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -687,3 +687,43 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen }); } } + + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.debouncePositionChanges', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.debouncePositionChanges', { value }], + ['audioCues.debouncePositionChangess', { value: undefined }] + ]; + } + }]); + + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + key: item.settingsKey, + migrateFn: (value: 'on' | 'off' | 'auto', accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + configurationKeyValuePairs.push([`${item.signalSettingsKey}.audioCue`, { value }]); + return configurationKeyValuePairs; + } + }))); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map(item => ({ + key: item.alertSettingsKey!, + migrateFn: (value, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + if (typeof value !== 'string') { + if (value === true) { + value = 'auto'; + } else { + value = 'off'; + } + } + configurationKeyValuePairs.push([`${item.signalSettingsKey}.alert`, { value }]); + return configurationKeyValuePairs; + } + }))); From 533da382079efc3a565cae64519421dacd3b6090 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 10:32:23 -0800 Subject: [PATCH 0025/1863] Use `InEmbeddedEditor` context for chat code blocks (#204641) This als: - Renames the variable to match the context key name - Uses this context key to disable peek call hierarchy - Enable the normal go to def commands in these contexts (not the peek versions) --- .../editor/browser/widget/codeEditorWidget.ts | 8 ++--- src/vs/editor/common/editorContextKeys.ts | 2 +- .../gotoSymbol/browser/goToCommands.ts | 30 ++++++++----------- .../browser/callHierarchy.contribution.ts | 3 +- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 1255cd1a598fb..63be27baefbb6 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -2095,7 +2095,7 @@ export class EditorModeContext extends Disposable { private readonly _hasMultipleDocumentSelectionFormattingProvider: IContextKey; private readonly _hasSignatureHelpProvider: IContextKey; private readonly _hasInlayHintsProvider: IContextKey; - private readonly _isInWalkThrough: IContextKey; + private readonly _isInEmbeddedEditor: IContextKey; constructor( private readonly _editor: CodeEditorWidget, @@ -2123,7 +2123,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentSelectionFormattingProvider = EditorContextKeys.hasDocumentSelectionFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentFormattingProvider = EditorContextKeys.hasMultipleDocumentFormattingProvider.bindTo(_contextKeyService); this._hasMultipleDocumentSelectionFormattingProvider = EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider.bindTo(_contextKeyService); - this._isInWalkThrough = EditorContextKeys.isInWalkThroughSnippet.bindTo(_contextKeyService); + this._isInEmbeddedEditor = EditorContextKeys.isInEmbeddedEditor.bindTo(_contextKeyService); const update = () => this._update(); @@ -2174,7 +2174,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentFormattingProvider.reset(); this._hasDocumentSelectionFormattingProvider.reset(); this._hasSignatureHelpProvider.reset(); - this._isInWalkThrough.reset(); + this._isInEmbeddedEditor.reset(); }); } @@ -2204,7 +2204,7 @@ export class EditorModeContext extends Disposable { this._hasDocumentSelectionFormattingProvider.set(this._languageFeaturesService.documentRangeFormattingEditProvider.has(model)); this._hasMultipleDocumentFormattingProvider.set(this._languageFeaturesService.documentFormattingEditProvider.all(model).length + this._languageFeaturesService.documentRangeFormattingEditProvider.all(model).length > 1); this._hasMultipleDocumentSelectionFormattingProvider.set(this._languageFeaturesService.documentRangeFormattingEditProvider.all(model).length > 1); - this._isInWalkThrough.set(model.uri.scheme === Schemas.walkThroughSnippet); + this._isInEmbeddedEditor.set(model.uri.scheme === Schemas.walkThroughSnippet || model.uri.scheme === Schemas.vscodeChatCodeBlock); }); } } diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 4cc1b78c7e47b..a38c21c4717e5 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -42,7 +42,7 @@ export namespace EditorContextKeys { export const hasSingleSelection = hasMultipleSelections.toNegated(); export const tabMovesFocus = new RawContextKey('editorTabMovesFocus', false, nls.localize('editorTabMovesFocus', "Whether `Tab` will move focus out of the editor")); export const tabDoesNotMoveFocus = tabMovesFocus.toNegated(); - export const isInWalkThroughSnippet = new RawContextKey('isInEmbeddedEditor', false, true); + export const isInEmbeddedEditor = new RawContextKey('isInEmbeddedEditor', false, true); export const canUndo = new RawContextKey('canUndo', false, true); export const canRedo = new RawContextKey('canRedo', false, true); diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index c579e25acc4b0..b968f2489049f 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -286,9 +286,7 @@ registerAction2(class GoToDefinitionAction extends DefinitionAction { ...nls.localize2('actions.goToDecl.label', "Go to Definition"), mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasDefinitionProvider, keybinding: [{ when: EditorContextKeys.editorTextFocus, primary: KeyCode.F12, @@ -327,7 +325,7 @@ registerAction2(class OpenDefinitionToSideAction extends DefinitionAction { title: nls.localize2('actions.goToDeclToSide.label', "Open Definition to the Side"), precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + EditorContextKeys.isInEmbeddedEditor.toNegated()), keybinding: [{ when: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.F12), @@ -357,7 +355,7 @@ registerAction2(class PeekDefinitionAction extends DefinitionAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -417,7 +415,7 @@ registerAction2(class GoToDeclarationAction extends DeclarationAction { }, precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: [{ id: MenuId.EditorContext, @@ -451,7 +449,7 @@ registerAction2(class PeekDeclarationAction extends DeclarationAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasDeclarationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -502,9 +500,7 @@ registerAction2(class GoToTypeDefinitionAction extends TypeDefinitionAction { ...nls.localize2('actions.goToTypeDefinition.label', "Go to Type Definition"), mnemonicTitle: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasTypeDefinitionProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasTypeDefinitionProvider, keybinding: { when: EditorContextKeys.editorTextFocus, primary: 0, @@ -539,7 +535,7 @@ registerAction2(class PeekTypeDefinitionAction extends TypeDefinitionAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasTypeDefinitionProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -590,9 +586,7 @@ registerAction2(class GoToImplementationAction extends ImplementationAction { ...nls.localize2('actions.goToImplementation.label', "Go to Implementations"), mnemonicTitle: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementations"), }, - precondition: ContextKeyExpr.and( - EditorContextKeys.hasImplementationProvider, - EditorContextKeys.isInWalkThroughSnippet.toNegated()), + precondition: EditorContextKeys.hasImplementationProvider, keybinding: { when: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyCode.F12, @@ -627,7 +621,7 @@ registerAction2(class PeekImplementationAction extends ImplementationAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasImplementationProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -680,7 +674,7 @@ registerAction2(class GoToReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), keybinding: { when: EditorContextKeys.editorTextFocus, @@ -718,7 +712,7 @@ registerAction2(class PeekReferencesAction extends ReferencesAction { precondition: ContextKeyExpr.and( EditorContextKeys.hasReferenceProvider, PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), menu: { id: MenuId.EditorContextPeek, @@ -750,7 +744,7 @@ class GenericGoToLocationAction extends SymbolNavigationAction { title: nls.localize2('label.generic', "Go to Any Symbol"), precondition: ContextKeyExpr.and( PeekContext.notInPeekEditor, - EditorContextKeys.isInWalkThroughSnippet.toNegated() + EditorContextKeys.isInEmbeddedEditor.toNegated() ), }); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 8dcb48da8a28f..a68eaeed3c3ca 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -186,7 +186,8 @@ registerAction2(class PeekCallHierarchyAction extends EditorAction2 { order: 1000, when: ContextKeyExpr.and( _ctxHasCallHierarchyProvider, - PeekContext.notInPeekEditor + PeekContext.notInPeekEditor, + EditorContextKeys.isInEmbeddedEditor.toNegated(), ), }, keybinding: { From 08beef7828374d84c956efc51477845a22149162 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 13:06:54 -0600 Subject: [PATCH 0026/1863] use accessor --- .../browser/accessibilityConfiguration.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 0f9f0990ab180..43abaabadeff1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -700,30 +700,20 @@ Registry.as(WorkbenchExtensions.ConfigurationMi } }]); - Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ key: item.settingsKey, - migrateFn: (value: 'on' | 'off' | 'auto', accessor) => { - const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - configurationKeyValuePairs.push([`${item.signalSettingsKey}.audioCue`, { value }]); - return configurationKeyValuePairs; - } - }))); - -Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map(item => ({ - key: item.alertSettingsKey!, - migrateFn: (value, accessor) => { + migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - if (typeof value !== 'string') { - if (value === true) { - value = 'auto'; - } else { - value = 'off'; + const alertSettingsKey = item.alertSettingsKey; + let alert: string | undefined; + if (alertSettingsKey) { + alert = accessor(alertSettingsKey) ?? undefined; + if (typeof alert !== 'string') { + alert = alert ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}.alert`, { value }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, alert ? { alert, audioCue } as any : { audioCue }]); return configurationKeyValuePairs; } }))); From fe6db4073fdf00c683b7738e9790b9dd83e66207 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 7 Feb 2024 11:08:03 -0800 Subject: [PATCH 0027/1863] allow time to scroll (#204646) --- .../singlefolder-tests/interactiveWindow.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index c85fdcb4c816c..a3d4036e5a2db 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -99,8 +99,13 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { } // Verify visible range has the last cell - assert.strictEqual(notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end, notebookEditor.notebook.cellCount, `Last cell is not visible`); - + if (!lastCellIsVisible(notebookEditor)) { + // scroll happens async, so give it some time to scroll + await new Promise((resolve) => setTimeout(() => { + assert.ok(lastCellIsVisible(notebookEditor), `Last cell is not visible`); + resolve(); + }, 1000)); + } }); test('Interactive window has the correct kernel', async () => { @@ -120,3 +125,8 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { }); }); + +function lastCellIsVisible(notebookEditor: vscode.NotebookEditor) { + const lastVisibleCell = notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end; + return notebookEditor.notebook.cellCount === lastVisibleCell; +} From 5551a8e28c3718c1d419fe49114739d40cb4e510 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 7 Feb 2024 13:09:43 -0600 Subject: [PATCH 0028/1863] finally migrate settings correctly --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 43abaabadeff1..3f601b3b408e2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -713,7 +713,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi alert = alert ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, alert ? { alert, audioCue } as any : { audioCue }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); return configurationKeyValuePairs; } }))); From 45a573ed9999db7d4637c6ead2628723d4e2ae98 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Feb 2024 16:11:03 -0300 Subject: [PATCH 0029/1863] Don't wait for variable resolvers before rendering request (#204650) Fix #204409 --- src/vs/workbench/contrib/chat/common/chatModel.ts | 13 +++++++++++-- .../contrib/chat/common/chatParserTypes.ts | 5 +++++ .../contrib/chat/common/chatServiceImpl.ts | 7 +++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 235c547b2e46b..951b8a1900187 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -92,10 +92,18 @@ export class ChatRequestModel implements IChatRequestModel { return this.session.requesterAvatarIconUri; } + public get variableData(): IChatRequestVariableData { + return this._variableData; + } + + public set variableData(v: IChatRequestVariableData) { + this._variableData = v; + } + constructor( public readonly session: ChatModel, public readonly message: IParsedChatRequest, - public readonly variableData: IChatRequestVariableData) { + private _variableData: IChatRequestVariableData) { this._id = 'request_' + ChatRequestModel.nextId++; } } @@ -353,7 +361,8 @@ export type ISerializableChatAgentData = UriDto; export interface ISerializableChatRequestData { message: string | IParsedChatRequest; // string => old format - variableData: IChatRequestVariableData; // make optional + /** Is really like "prompt data". This is the message in the format in which the agent gets it + variable values. */ + variableData: IChatRequestVariableData; response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; slashCommand?: IChatAgentCommand; diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 7363f5cffcbb2..65e4cf2a8fb61 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -22,9 +22,14 @@ export interface IParsedChatRequestPart { readonly range: IOffsetRange; readonly editorRange: IRange; readonly text: string; + /** How this part is represented in the prompt going to the agent */ readonly promptText: string; } +export function getPromptText(request: ReadonlyArray): string { + return request.map(r => r.promptText).join(''); +} + export class ChatRequestTextPart implements IParsedChatRequestPart { static readonly Kind = 'text'; readonly kind = ChatRequestTextPart.Kind; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index c55c438f22858..caab172b0f488 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,7 +23,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, 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 } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -539,8 +539,11 @@ export class ChatService extends Disposable implements IChatService { history.push({ request: historyRequest, response: request.response.response.value, result: { errorDetails: request.response.errorDetails } }); } + 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); - request = model.addRequest(parsedRequest, variableData, agent, agentSlashCommandPart?.command); + request.variableData = variableData; + const requestProps: IChatAgentRequest = { sessionId, requestId: request.id, From c4a1ed80ec72e5001b1b5ee14d2ee24a1ca6db97 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 7 Feb 2024 16:27:33 -0300 Subject: [PATCH 0030/1863] Delete agent, slash command, variable more easily (#204651) --- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index beeb2bdeeab90..e5e44863dfd53 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -646,8 +646,8 @@ class ChatTokenDeleter extends Disposable { const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart); deletableTokens.forEach(token => { const deletedRangeOfToken = Range.intersectRanges(token.editorRange, change.range); - // Part of this token was deleted, and the deletion range doesn't go off the front of the token, for simpler math - if ((deletedRangeOfToken && !deletedRangeOfToken.isEmpty()) && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { + // Part of this token was deleted, or the space after it was deleted, and the deletion range doesn't go off the front of the token, for simpler math + if (deletedRangeOfToken && Range.compareRangesUsingStarts(token.editorRange, change.range) < 0) { // Assume single line tokens const length = deletedRangeOfToken.endColumn - deletedRangeOfToken.startColumn; const rangeToDelete = new Range(token.editorRange.startLineNumber, token.editorRange.startColumn, token.editorRange.endLineNumber, token.editorRange.endColumn - length); From b05778eb907c25ed7546dd73511f2aa14683af84 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:41:17 +0100 Subject: [PATCH 0031/1863] Workbench - ability to contribute window title variables (#204538) --------- Co-authored-by: Benjamin Pasero --- .../src/settingsDocumentHelper.ts | 2 + .../browser/parts/titlebar/titlebarPart.ts | 36 ++++++++++++++ .../browser/parts/titlebar/windowTitle.ts | 34 ++++++++++++- .../browser/workbench.contribution.ts | 2 + .../workbench/contrib/scm/browser/activity.ts | 49 ++++++++++++++++--- 5 files changed, 115 insertions(+), 8 deletions(-) diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 110494fdb3e69..bbf77e6017e13 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -120,6 +120,8 @@ export class SettingsDocument { completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryName'), range, vscode.l10n.t("the name of the active repository (e.g. vscode)"))); + completions.push(this.newSimpleCompletionItem(getText('activeRepositoryBranchName'), range, vscode.l10n.t("the name of the active branch in the active repository (e.g. main)"))); return completions; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 7e481cf928d85..cbe98d2e85228 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -55,6 +55,11 @@ 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'; +export interface ITitleVariable { + readonly name: string; + readonly contextKey: string; +} + export interface ITitleProperties { isPure?: boolean; isAdmin?: boolean; @@ -72,6 +77,11 @@ export interface ITitlebarPart extends IDisposable { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; + + /** + * Adds variables to be supported in the window title. + */ + registerVariables(variables: ITitleVariable[]): void; } export class BrowserTitleService extends MultiWindowParts implements ITitleService { @@ -134,6 +144,14 @@ export class BrowserTitleService extends MultiWindowParts i disposables.add(Event.runAndSubscribe(titlebarPart.onDidChange, () => titlebarPartContainer.style.height = `${titlebarPart.height}px`)); titlebarPart.create(titlebarPartContainer); + if (this.properties) { + titlebarPart.updateProperties(this.properties); + } + + if (this.variables.length) { + titlebarPart.registerVariables(this.variables); + } + Event.once(titlebarPart.onWillDispose)(() => disposables.dispose()); return titlebarPart; @@ -150,12 +168,26 @@ export class BrowserTitleService extends MultiWindowParts i readonly onMenubarVisibilityChange = this.mainPart.onMenubarVisibilityChange; + private properties: ITitleProperties | undefined = undefined; + updateProperties(properties: ITitleProperties): void { + this.properties = properties; + for (const part of this.parts) { part.updateProperties(properties); } } + private variables: ITitleVariable[] = []; + + registerVariables(variables: ITitleVariable[]): void { + this.variables.push(...variables); + + for (const part of this.parts) { + part.registerVariables(variables); + } + } + //#endregion } @@ -379,6 +411,10 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle.updateProperties(properties); } + registerVariables(variables: ITitleVariable[]): void { + this.windowTitle.registerVariables(variables); + } + protected override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; this.rootContainer = append(parent, $('.titlebar-container')); diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index fffceb67bafcb..0628c153307c0 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { dirname, basename } from 'vs/base/common/resources'; -import { ITitleProperties } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import { ITitleProperties, ITitleVariable } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -26,6 +26,7 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', @@ -39,6 +40,8 @@ export class WindowTitle extends Disposable { private static readonly TITLE_DIRTY = '\u25cf '; private readonly properties: ITitleProperties = { isPure: true, isAdmin: false, prefix: undefined }; + private readonly variables = new Map(); + private readonly activeEditorListeners = this._register(new DisposableStore()); private readonly titleUpdater = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); @@ -66,6 +69,7 @@ export class WindowTitle extends Disposable { private readonly targetWindow: Window, editorGroupsContainer: IEditorGroupsContainer | 'main', @IConfigurationService protected readonly configurationService: IConfigurationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorService editorService: IEditorService, @IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -95,6 +99,11 @@ export class WindowTitle extends Disposable { this.titleUpdater.schedule(); } })); + this._register(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(this.variables)) { + this.titleUpdater.schedule(); + } + })); } private onConfigurationChanged(event: IConfigurationChangeEvent): void { @@ -223,6 +232,22 @@ export class WindowTitle extends Disposable { } } + registerVariables(variables: ITitleVariable[]): void { + let changed = false; + + for (const { name, contextKey } of variables) { + if (!this.variables.has(contextKey)) { + this.variables.set(contextKey, name); + + changed = true; + } + } + + if (changed) { + this.titleUpdater.schedule(); + } + } + /** * Possible template values: * @@ -303,6 +328,12 @@ export class WindowTitle extends Disposable { const titleTemplate = this.configurationService.getValue(WindowSettingNames.title); const focusedView: string = this.viewsService.getFocusedViewName(); + // Variables (contributed) + const contributedVariables: { [key: string]: string } = {}; + for (const [contextKey, name] of this.variables) { + contributedVariables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + } + return template(titleTemplate, { activeEditorShort, activeEditorLong, @@ -320,6 +351,7 @@ export class WindowTitle extends Disposable { remoteName, profileName, focusedView, + ...contributedVariables, separator: { label: separator } }); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index d9cfbe2465444..f288862112044 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -611,6 +611,8 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('remoteName', "`${remoteName}`: e.g. SSH"), localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."), localize('focusedView', "`${focusedView}`: the name of the view that is currently focused."), + localize('activeRepositoryName', "`${activeRepositoryName}`: the name of the active repository (e.g. vscode)."), + localize('activeRepositoryBranchName', "`${activeRepositoryBranchName}`: the name of the active branch in the active repository (e.g. main)."), localize('separator', "`${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 68a280a56bca3..402df518c9940 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event'; import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStatusbarEntry, IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -18,6 +18,7 @@ import { EditorResourceAccessor } from 'vs/workbench/common/editor'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; +import { ITitleService } from 'vs/workbench/services/title/browser/titleService'; function getCount(repository: ISCMRepository): number { if (typeof repository.provider.count === 'number') { @@ -27,10 +28,18 @@ function getCount(repository: ISCMRepository): number { } } +const ContextKeys = { + ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), + ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), +}; + export class SCMStatusController implements IWorkbenchContribution { + private activeRepositoryNameContextKey: IContextKey; + private activeRepositoryBranchNameContextKey: IContextKey; + private statusBarDisposable: IDisposable = Disposable.None; - private focusDisposable: IDisposable = Disposable.None; + private focusDisposables = new DisposableStore(); private focusedRepository: ISCMRepository | undefined = undefined; private readonly badgeDisposable = new MutableDisposable(); private readonly disposables = new DisposableStore(); @@ -43,7 +52,9 @@ export class SCMStatusController implements IWorkbenchContribution { @IActivityService private readonly activityService: IActivityService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IContextKeyService contextKeyService: IContextKeyService, + @ITitleService titleService: ITitleService ) { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -55,6 +66,14 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } + this.activeRepositoryNameContextKey = ContextKeys.ActiveRepositoryName.bindTo(contextKeyService); + this.activeRepositoryBranchNameContextKey = ContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); + + titleService.registerVariables([ + { name: 'activeRepositoryName', contextKey: ContextKeys.ActiveRepositoryName.key }, + { name: 'activeRepositoryBranchName', contextKey: ContextKeys.ActiveRepositoryBranchName.key, } + ]); + this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables); this.focusRepository(this.scmViewService.focusedRepository); @@ -125,17 +144,33 @@ export class SCMStatusController implements IWorkbenchContribution { return; } - this.focusDisposable.dispose(); + this.focusDisposables.clear(); this.focusedRepository = repository; - if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); + if (repository) { + if (repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposables.add(repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository))); + } + + this.focusDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { + if (repository.provider.historyProvider) { + this.focusDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); + } + + this.updateContextKeys(repository); + })); } + this.updateContextKeys(repository); this.renderStatusBar(repository); this.renderActivityCount(); } + private updateContextKeys(repository: ISCMRepository | undefined): void { + this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); + this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); + } + private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); @@ -204,7 +239,7 @@ export class SCMStatusController implements IWorkbenchContribution { } dispose(): void { - this.focusDisposable.dispose(); + this.focusDisposables.dispose(); this.statusBarDisposable.dispose(); this.badgeDisposable.dispose(); this.disposables.dispose(); From 6c7362fe4f46ca0335b0dc82d331be9b63c31134 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 7 Feb 2024 20:42:45 +0000 Subject: [PATCH 0032/1863] Reinstate command items when filtering checkout quickpick (fix #202870) (#204107) --------- Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- extensions/git/src/commands.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 979daf3d2ba21..3f46f698ce22f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -23,7 +23,7 @@ import { RemoteSourceAction } from './api/git-base'; abstract class CheckoutCommandItem implements QuickPickItem { abstract get label(): string; get description(): string { return ''; } - get alwaysShow(): boolean { return false; } + get alwaysShow(): boolean { return true; } } class CreateBranchItem extends CheckoutCommandItem { @@ -2461,9 +2461,10 @@ export class CommandCenter { const createBranchFrom = new CreateBranchFromItem(); const checkoutDetached = new CheckoutDetachedItem(); const picks: QuickPickItem[] = []; + const commands: QuickPickItem[] = []; if (!opts?.detached) { - picks.push(createBranch, createBranchFrom, checkoutDetached); + commands.push(createBranch, createBranchFrom, checkoutDetached); } const disposables: Disposable[] = []; @@ -2477,7 +2478,7 @@ export class CommandCenter { quickPick.show(); picks.push(... await createCheckoutItems(repository, opts?.detached)); - quickPick.items = picks; + quickPick.items = [...commands, ...picks]; quickPick.busy = false; const choice = await new Promise(c => { @@ -2492,6 +2493,22 @@ export class CommandCenter { c(undefined); }))); + disposables.push(quickPick.onDidChangeValue(value => { + switch (true) { + case value === '': + quickPick.items = [...commands, ...picks]; + break; + case commands.length === 0: + quickPick.items = picks; + break; + case picks.length === 0: + quickPick.items = commands; + break; + default: + quickPick.items = [...picks, { label: '', kind: QuickPickItemKind.Separator }, ...commands]; + break; + } + })); }); dispose(disposables); From 20d18171b31d408986f527f3fadecfd9841539b4 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 7 Feb 2024 16:23:46 -0600 Subject: [PATCH 0033/1863] introduce findFiles2 API (#203844) * introduce first version of FindFiles2 API --- extensions/vscode-api-tests/package.json | 1 + .../src/singlefolder-tests/workspace.test.ts | 39 ++++ .../api/browser/mainThreadWorkspace.ts | 15 +- .../workbench/api/common/extHost.api.impl.ts | 3 + .../workbench/api/common/extHost.protocol.ts | 4 +- .../workbench/api/common/extHostWorkspace.ts | 70 +++++-- .../api/test/browser/extHostWorkspace.test.ts | 189 ++++++++++++++++-- .../test/browser/mainThreadWorkspace.test.ts | 32 ++- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.findFiles2.d.ts | 81 ++++++++ 10 files changed, 391 insertions(+), 44 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.findFiles2.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 97de1b94d0504..a35e4bc08ccab 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -22,6 +22,7 @@ "extensionsAny", "externalUriOpener", "fileSearchProvider", + "findFiles2", "findTextInFiles", "fsChunks", "interactive", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index e69eecff5d132..b867766d027d4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -597,6 +597,45 @@ suite('vscode API - workspace', () => { }); }); + test('`findFiles2`', () => { + return vscode.workspace.findFiles2('*image.png').then((res) => { + assert.strictEqual(res.length, 4); + // TODO: see why this is fuzzy matching + }); + }); + + test('findFiles2 - null exclude', async () => { + await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => { + // file.exclude folder is still searched, search.exclude folder is not + assert.strictEqual(res.length, 1); + assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); + }); + + await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => { + // search.exclude and files.exclude folders are both searched + assert.strictEqual(res.length, 2); + assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); + }); + }); + + test('findFiles2, exclude', () => { + return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => { + assert.strictEqual(res.length, 3); + // TODO: see why this is fuzzy matching + }); + }); + + test('findFiles2, cancellation', () => { + + const source = new vscode.CancellationTokenSource(); + const token = source.token; // just to get an instance first + source.cancel(); + + return vscode.workspace.findFiles2('*.js', {}, token).then((res) => { + assert.deepStrictEqual(res, []); + }); + }); + test('findTextInFiles', async () => { const options: vscode.FindTextInFilesOptions = { include: '*.ts', diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index e04ee2837d95d..180932b6d6d26 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -19,7 +19,7 @@ import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorksp import { IWorkspace, IWorkspaceContextService, WorkbenchState, isUntitledWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { checkGlobFileExists } from 'vs/workbench/services/extensions/common/workspaceContains'; -import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { IEditorService, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -140,21 +140,14 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { // --- search --- - $startFileSearch(includePattern: string | null, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise { + $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { const includeFolder = URI.revive(_includeFolder); const workspace = this._contextService.getWorkspace(); const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - { - maxResults: maxResults ?? undefined, - disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, - disregardSearchExcludeSettings: true, - disregardIgnoreFiles: true, - includePattern: includePattern ?? undefined, - excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined, - _reason: 'startFileSearch' - }); + options + ); return this._searchService.fileSearch(query, token).then(result => { return result.results.map(m => m.resource); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index dd4a277260676..5b7786330ac15 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -938,6 +938,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Note, undefined/null have different meanings on "exclude" return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, + findFiles2: (filePattern, options?, token?) => { + return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); + }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8826cef84333a..676cf530dcf2d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -78,7 +78,7 @@ import { Dto, IRPCProtocol, SerializableObjectWithBuffers, createProxyIdentifier import { ILanguageStatus } from 'vs/workbench/services/languageStatus/common/languageStatusService'; import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output'; import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; @@ -1341,7 +1341,7 @@ export interface ITextSearchComplete { } export interface MainThreadWorkspaceShape extends IDisposable { - $startFileSearch(includePattern: string | null, includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false | null, maxResults: number | null, token: CancellationToken): Promise; + $startFileSearch(includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise; $startTextSearch(query: search.IPatternInfo, folder: UriComponents | null, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(folders: readonly UriComponents[], includes: string[], token: CancellationToken): Promise; $save(uri: UriComponents, options: { saveAs: boolean }): Promise; diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 95a60bcba8bb9..dcb32598916b2 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -28,7 +28,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { GlobPattern } from 'vs/workbench/api/common/extHostTypeConverters'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import { IRawFileMatch2, ITextSearchResult, resultIsMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; @@ -446,27 +446,73 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac findFiles(include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | null | undefined, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); - let excludePatternOrDisregardExcludes: string | false | undefined = undefined; + let excludeString: string = ''; + let useFileExcludes = true; if (exclude === null) { - excludePatternOrDisregardExcludes = false; - } else if (exclude) { + useFileExcludes = false; + } else if (exclude !== undefined) { if (typeof exclude === 'string') { - excludePatternOrDisregardExcludes = exclude; + excludeString = exclude; } else { - excludePatternOrDisregardExcludes = exclude.pattern; + excludeString = exclude.pattern; } } - + return this._findFilesImpl(include, undefined, { + exclude: excludeString, + maxResults, + useDefaultExcludes: useFileExcludes, + useDefaultSearchExcludes: false, + useIgnoreFiles: true + }, token); + } + + findFiles2(filePattern: vscode.GlobPattern | undefined, + options: vscode.FindFiles2Options = {}, + extensionId: ExtensionIdentifier, + token: vscode.CancellationToken = CancellationToken.None): Promise { + this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`); + return this._findFilesImpl(undefined, filePattern, options, token); + } + + private async _findFilesImpl( + // the old `findFiles` used `include` to query, but the new `findFiles2` uses `filePattern` to query. + // `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files. + include: vscode.GlobPattern | undefined, + filePattern: vscode.GlobPattern | undefined, + options: vscode.FindFiles2Options, + token: vscode.CancellationToken = CancellationToken.None): Promise { if (token && token.isCancellationRequested) { return Promise.resolve([]); } - const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); + const excludePattern = (typeof options.exclude === 'string') ? options.exclude : + options.exclude ? options.exclude.pattern : undefined; + + const fileQueries = { + ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined, + disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined, + disregardGlobalIgnoreFiles: typeof options.useGlobalIgnoreFiles === 'boolean' ? !options.useGlobalIgnoreFiles : undefined, + disregardParentIgnoreFiles: typeof options.useParentIgnoreFiles === 'boolean' ? !options.useParentIgnoreFiles : undefined, + disregardExcludeSettings: typeof options.useDefaultExcludes === 'boolean' ? !options.useDefaultExcludes : false, + disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false, + maxResults: options.maxResults, + excludePattern: excludePattern, + _reason: 'startFileSearch' + }; + let folderToUse: URI | undefined; + if (include) { + const { includePattern, folder } = parseSearchInclude(GlobPattern.from(include)); + folderToUse = folder; + fileQueries.includePattern = includePattern; + } else { + const { includePattern, folder } = parseSearchInclude(GlobPattern.from(filePattern)); + folderToUse = folder; + fileQueries.filePattern = includePattern; + } + return this._proxy.$startFileSearch( - includePattern ?? null, - folder ?? null, - excludePatternOrDisregardExcludes ?? null, - maxResults ?? null, + folderToUse ?? null, + fileQueries, token ) .then(data => Array.isArray(data) ? data.map(d => URI.revive(d)) : []); diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index da33adc7de709..6abca46d3ff11 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -18,7 +18,7 @@ import { mock } from 'vs/base/test/common/mock'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; +import { IFileQueryBuilderOptions, ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import { IPatternInfo } from 'vs/workbench/services/search/common/search'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; @@ -583,12 +583,13 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'foo'); + assert.strictEqual(options.includePattern, 'foo'); assert.strictEqual(_includeFolder, null); - assert.strictEqual(excludePatternOrDisregardExcludes, null); - assert.strictEqual(maxResults, 10); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.maxResults, 10); return Promise.resolve(null); } }); @@ -605,11 +606,12 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'glob/**'); + assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); - assert.strictEqual(excludePatternOrDisregardExcludes, null); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, false); return Promise.resolve(null); } }); @@ -634,11 +636,12 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert.strictEqual(includePattern, 'glob/**'); + assert.strictEqual(options.includePattern, 'glob/**'); assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); - assert.strictEqual(excludePatternOrDisregardExcludes, false); + assert.strictEqual(options.excludePattern, ''); + assert.strictEqual(options.disregardExcludeSettings, true); return Promise.resolve(null); } }); @@ -655,7 +658,7 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; return Promise.resolve(null); } @@ -675,9 +678,10 @@ suite('ExtHostWorkspace', function () { let mainThreadCalled = false; rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(includePattern: string, _includeFolder: UriComponents | null, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { mainThreadCalled = true; - assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651 + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 return Promise.resolve(null); } }); @@ -687,6 +691,163 @@ suite('ExtHostWorkspace', function () { assert(mainThreadCalled, 'mainThreadCalled'); }); }); + test('findFiles2 - string include', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'foo'); + assert.strictEqual(options.includePattern, undefined); + assert.strictEqual(_includeFolder, null); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.maxResults, 10); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('foo', { maxResults: 10, useDefaultExcludes: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + function testFindFiles2Include(pattern: RelativePattern) { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'glob/**'); + assert.strictEqual(options.includePattern, undefined); + assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2(pattern, { maxResults: 10 }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + } + + test('findFiles2 - RelativePattern include (string)', () => { + return testFindFiles2Include(new RelativePattern('/other/folder', 'glob/**')); + }); + + test('findFiles2 - RelativePattern include (URI)', () => { + return testFindFiles2Include(new RelativePattern(URI.file('/other/folder'), 'glob/**')); + }); + + test('findFiles2 - no excludes', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.filePattern, 'glob/**'); + assert.strictEqual(options.includePattern, undefined); + assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); + assert.strictEqual(options.excludePattern, undefined); + assert.strictEqual(options.disregardExcludeSettings, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles2 - with cancelled token', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + + const token = CancellationToken.Cancelled; + return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test'), token).then(() => { + assert(!mainThreadCalled, '!mainThreadCalled'); + }); + }); + + test('findFiles2 - RelativePattern exclude', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.excludePattern, 'glob/**'); // Note that the base portion is ignored, see #52651 + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { exclude: new RelativePattern(root, 'glob/**') }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + test('findFiles2 - useIgnoreFiles', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.disregardExcludeSettings, false); + assert.strictEqual(options.disregardIgnoreFiles, false); + assert.strictEqual(options.disregardGlobalIgnoreFiles, false); + assert.strictEqual(options.disregardParentIgnoreFiles, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { useIgnoreFiles: true, useParentIgnoreFiles: true, useGlobalIgnoreFiles: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles2 - use symlinks', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.strictEqual(options.ignoreSymlinks, false); + return Promise.resolve(null); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); + return ws.findFiles2('', { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); test('findTextInFiles - no include', async () => { const root = '/project/foo'; diff --git a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts index 7000d9edb5b4e..2cc5a637a6a2a 100644 --- a/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadWorkspace.test.ts @@ -41,7 +41,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('foo', null, null, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: 'foo', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); test('exclude defaults', () => { @@ -63,7 +63,7 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, null, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); test('disregard excludes', () => { @@ -76,7 +76,7 @@ suite('MainThreadWorkspace', () => { instantiationService.stub(ISearchService, { fileSearch(query: IFileQuery) { - assert.strictEqual(query.folderQueries[0].excludePattern, undefined); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); assert.deepStrictEqual(query.excludePattern, undefined); return Promise.resolve({ results: [], messages: [] }); @@ -84,7 +84,29 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, false, 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardSearchExcludeSettings: true, disregardExcludeSettings: true }, new CancellationTokenSource().token); + }); + + test('do not disregard anything if disregardExcludeSettings is true', () => { + configService.setUserConfiguration('search', { + 'exclude': { 'searchExclude': true } + }); + configService.setUserConfiguration('files', { + 'exclude': { 'filesExclude': true } + }); + + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.strictEqual(query.folderQueries.length, 1); + assert.strictEqual(query.folderQueries[0].disregardIgnoreFiles, true); + assert.deepStrictEqual(query.folderQueries[0].excludePattern, undefined); + + return Promise.resolve({ results: [], messages: [] }); + } + }); + + const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', disregardExcludeSettings: true, disregardSearchExcludeSettings: false }, new CancellationTokenSource().token); }); test('exclude string', () => { @@ -98,6 +120,6 @@ suite('MainThreadWorkspace', () => { }); const mtw = disposables.add(instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } }))); - return mtw.$startFileSearch('', null, 'exclude/**', 10, new CancellationTokenSource().token); + return mtw.$startFileSearch(null, { maxResults: 10, includePattern: '', excludePattern: 'exclude/**', disregardSearchExcludeSettings: true }, new CancellationTokenSource().token); }); }); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 47486e60790b3..15df9887466ac 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -58,6 +58,7 @@ export const allApiProposals = Object.freeze({ externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', fileComments: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileComments.d.ts', fileSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', + findFiles2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts', diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts new file mode 100644 index 0000000000000..45d734153a20a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * 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 FindFiles2Options { + // note: this is just FindTextInFilesOptions without select properties (include, previewOptions, beforeContext, afterContext) + + /** + * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. + */ + exclude?: GlobPattern; + + /** + * Whether to use the values for files.exclude. Defaults to true. + */ + useDefaultExcludes?: boolean; + + /** + * Whether to use the values for search.exclude. Defaults to true. Will not be followed if `useDefaultExcludes` is set to `false`. + */ + useDefaultSearchExcludes?: boolean; + + /** + * The maximum number of results to search for + */ + maxResults?: number; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles?: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * Must set `useIgnoreFiles` to `true` to use this. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles?: boolean; + + /** + * Whether files in parent directories that exclude files, like .gitignore, should be respected. + * Must set `useIgnoreFiles` to `true` to use this. + * See the vscode setting `"search.useParentIgnoreFiles"`. + */ + useParentIgnoreFiles?: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks?: boolean; + } + + /** + * Represents a session of a currently logged in user. + */ + export namespace workspace { + /** + * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. + * + * @example + * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true}, 10) + * + * @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} + * to restrict the search results to a {@link WorkspaceFolder workspace folder}. + * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). + * If enabled, any ignore files will take prescendence over any files found in the `filePattern` parameter. + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no + * {@link workspace.workspaceFolders workspace folders} are opened. + */ + export function findFiles2(filePattern: GlobPattern, options?: FindFiles2Options, token?: CancellationToken): Thenable; + } +} From a1fb0dcd2e2c90916d3754adbeb881312f78cdaa Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 14:47:44 -0800 Subject: [PATCH 0034/1863] Put all chat code blocks in same implicit JS/TS project (#204655) --- .../src/configuration/fileSchemes.ts | 1 + .../src/tsServer/bufferSyncSupport.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/configuration/fileSchemes.ts b/extensions/typescript-language-features/src/configuration/fileSchemes.ts index 9fb572c717522..07dd38a53798c 100644 --- a/extensions/typescript-language-features/src/configuration/fileSchemes.ts +++ b/extensions/typescript-language-features/src/configuration/fileSchemes.ts @@ -19,6 +19,7 @@ export const vscodeNotebookCell = 'vscode-notebook-cell'; export const memFs = 'memfs'; export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; +export const chatCodeBlock = 'vscode-chat-code-block'; export function getSemanticSupportedSchemes() { if (isWeb() && vscode.workspace.workspaceFolders) { diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index 90151ea6a084c..c95b2e473e336 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { officeScript, vscodeNotebookCell } from '../configuration/fileSchemes'; +import * as fileSchemes from '../configuration/fileSchemes'; import * as languageModeIds from '../configuration/languageIds'; import * as typeConverters from '../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService'; @@ -227,7 +227,7 @@ class SyncedBuffer { return tsRoot?.startsWith(inMemoryResourcePrefix) ? undefined : tsRoot; } - return resource.scheme === officeScript ? '/' : undefined; + return resource.scheme === fileSchemes.officeScript || resource.scheme === fileSchemes.chatCodeBlock ? '/' : undefined; } public get resource(): vscode.Uri { @@ -395,7 +395,7 @@ class TabResourceTracker extends Disposable { } public has(resource: vscode.Uri): boolean { - if (resource.scheme === vscodeNotebookCell) { + if (resource.scheme === fileSchemes.vscodeNotebookCell) { const notebook = vscode.workspace.notebookDocuments.find(doc => doc.getCells().some(cell => cell.document.uri.toString() === resource.toString())); From fdbf304519ed209b0fabbcae67ac90c02117d356 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 8 Feb 2024 08:19:53 +0900 Subject: [PATCH 0035/1863] fix: requirements detection for alpine (#204660) --- .../bin/helpers/check-requirements-linux.sh | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 1b9199fd03930..8c7d1523e2183 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -19,16 +19,17 @@ if [ -f "/tmp/vscode-skip-server-requirements-check" ]; then exit 0 fi -BITNESS=$(getconf LONG_BIT) ARCH=$(uname -m) found_required_glibc=0 found_required_glibcxx=0 # Extract the ID value from /etc/os-release -OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed -n '1s/ID=//p')" -if [ "$OS_ID" = "nixos" ]; then - echo "Warning: NixOS detected, skipping GLIBC check" - exit 0 +if [ -f /etc/os-release ]; then + OS_ID="$(cat /etc/os-release | grep -Eo 'ID=([^"]+)' | sed -n '1s/ID=//p')" + if [ "$OS_ID" = "nixos" ]; then + echo "Warning: NixOS detected, skipping GLIBC check" + exit 0 + fi fi # Based on https://github.com/bminor/glibc/blob/520b1df08de68a3de328b65a25b86300a7ddf512/elf/cache.c#L162-L245 @@ -36,6 +37,7 @@ case $ARCH in x86_64) LDCONFIG_ARCH="x86-64";; armv7l | armv8l) LDCONFIG_ARCH="hard-float";; arm64 | aarch64) + BITNESS=$(getconf LONG_BIT) if [ "$BITNESS" = "32" ]; then # Can have 32-bit userland on 64-bit kernel LDCONFIG_ARCH="hard-float" @@ -45,23 +47,29 @@ case $ARCH in ;; esac -if [ -f /usr/lib64/libstdc++.so.6 ]; then - # Typical path - libstdcpp_path='/usr/lib64/libstdc++.so.6' -elif [ -f /usr/lib/libstdc++.so.6 ]; then - # Typical path - libstdcpp_path='/usr/lib/libstdc++.so.6' -elif [ -f /sbin/ldconfig ]; then - # Look up path - libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') +if [ "$OS_ID" != "alpine" ]; then + if [ -f /sbin/ldconfig ]; then + # Look up path + libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') + + if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + else + libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') + fi + fi +fi - if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then - libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) +if [ -z "$libstdcpp_path" ]; then + if [ -f /usr/lib/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib/libstdc++.so.6' + elif [ -f /usr/lib64/libstdc++.so.6 ]; then + # Typical path + libstdcpp_path='/usr/lib64/libstdc++.so.6' else - libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') + echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi -else - echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi if [ -n "$libstdcpp_path" ]; then @@ -78,14 +86,21 @@ if [ -n "$libstdcpp_path" ]; then fi fi -if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then - if [ -f /usr/lib64/libc.so.6 ]; then - # Typical path - libc_path='/usr/lib64/libc.so.6' - elif [ -f /usr/lib/libc.so.6 ]; then - # Typical path - libc_path='/usr/lib/libc.so.6' - elif [ -f /sbin/ldconfig ]; then +if [ "$OS_ID" = "alpine" ]; then + MUSL_RTLDLIST="/lib/ld-musl-aarch64.so.1 /lib/ld-musl-x86_64.so.1" + for rtld in ${MUSL_RTLDLIST}; do + if [ -x $rtld ]; then + musl_version=$("$rtld" --version 2>&1 | grep "Version" | awk '{print $NF}') + break + fi + done + if [ "$(printf '%s\n' "1.2.3" "$musl_version" | sort -V | head -n1)" != "1.2.3" ]; then + echo "Error: Unsupported alpine distribution. Please refer to our supported distro section https://aka.ms/vscode-remote/linux for additional information." + exit 99 + fi + found_required_glibc=1 +elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then + if [ -f /sbin/ldconfig ]; then # Look up path libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') @@ -94,6 +109,12 @@ if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then else libc_path=$(echo "$libc_paths" | awk '{print $NF}') fi + elif [ -f /usr/lib/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib/libc.so.6' + elif [ -f /usr/lib64/libc.so.6 ]; then + # Typical path + libc_path='/usr/lib64/libc.so.6' else echo "Warning: Can't find libc.so or ldconfig, can't verify libc version" fi @@ -110,16 +131,7 @@ if [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then fi fi else - if [ "$OS_ID" = "alpine" ]; then - musl_version=$(ldd --version 2>&1 | grep "Version" | awk '{print $NF}') - if [ "$(printf '%s\n' "1.2.3" "$musl_version" | sort -V | head -n1)" != "1.2.3" ]; then - echo "Error: Unsupported alpine distribution. Please refer to our supported distro section https://aka.ms/vscode-remote/linux for additional information." - exit 99 - fi - else - echo "Warning: musl detected, skipping GLIBC check" - fi - + echo "Warning: musl detected, skipping GLIBC check" found_required_glibc=1 fi From 428dd56479f4f9987fe0b0a444c11c46e84b2525 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 15:21:42 -0800 Subject: [PATCH 0036/1863] Try to observe TS usage of insert/replace (#204661) Fixes #204037 - Replace span on item == use for both insert and replace - Optional replacement span == use only for replace --- .../src/languageFeatures/completions.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index 817f614a6c5aa..a46835aa90773 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -39,6 +39,7 @@ interface CompletionContext { readonly wordRange: vscode.Range | undefined; readonly line: string; + readonly optionalReplacementRange: vscode.Range | undefined; } type ResolvedCompletionItem = { @@ -363,18 +364,25 @@ class MyCompletionItem extends vscode.CompletionItem { private getRangeFromReplacementSpan(tsEntry: Proto.CompletionEntry, completionContext: CompletionContext) { if (!tsEntry.replacementSpan) { - return; + if (completionContext.optionalReplacementRange) { + return { + inserting: new vscode.Range(completionContext.optionalReplacementRange.start, this.position), + replacing: completionContext.optionalReplacementRange, + }; + } + + return undefined; } - let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); + // If TS returns an explicit replacement range on this item, we should use it for both types of completion + // Make sure we only replace a single line at most + let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); if (!replaceRange.isSingleLine) { replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, completionContext.line.length); } - - // If TS returns an explicit replacement range, we should use it for both types of completion return { - inserting: new vscode.Range(replaceRange.start, this.position), + inserting: replaceRange, replacing: replaceRange, }; } @@ -735,6 +743,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< let metadata: any | undefined; let response: ServerResponse.Response | undefined; let duration: number | undefined; + let optionalReplacementRange: vscode.Range | undefined; if (this.client.apiVersion.gte(API.v300)) { const startTime = Date.now(); try { @@ -762,9 +771,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< metadata = response.metadata; if (response.body.optionalReplacementSpan) { - for (const entry of entries) { - entry.replacementSpan ??= response.body.optionalReplacementSpan; - } + optionalReplacementRange = typeConverters.Range.fromTextSpan(response.body.optionalReplacementSpan); } } else { const response = await this.client.interruptGetErr(() => this.client.execute('completions', args, token)); @@ -784,6 +791,7 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider< wordRange, line: line.text, completeFunctionCalls: completionConfiguration.completeFunctionCalls, + optionalReplacementRange, }; let includesPackageJsonImport = false; From 06eee91ac73111061c40dc3496ed645cc08d4968 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 7 Feb 2024 15:37:58 -0800 Subject: [PATCH 0037/1863] eng: update CLI and a fix for extension test coverage (#204662) --- build/gulpfile.extensions.js | 3 +- package.json | 2 +- yarn.lock | 128 +++++++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index bcdb206606b24..579a62dbbbbe6 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -134,7 +134,8 @@ const tasks = compilations.map(function (tsconfigFile) { sourceMappingURL: !build ? null : f => `${baseUrl}/${f.relative}.map`, addComment: !!build, includeContent: !!build, - sourceRoot: '../src' + // note: trailing slash is important, else the source URLs in V8's file coverage are incorrect + sourceRoot: '../src/', })) .pipe(tsFilter.restore) .pipe(build ? nlsDev.bundleMetaDataFiles(headerId, headerOut) : es.through()) diff --git a/package.json b/package.json index c94ab6966c44f..51d5b93310abb 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@vscode/gulp-electron": "^1.36.0", "@vscode/l10n-dev": "0.0.30", "@vscode/telemetry-extractor": "^1.10.2", - "@vscode/test-cli": "^0.0.3", + "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.3.8", "@vscode/test-web": "^0.0.50", "@vscode/v8-heap-parser": "^0.1.0", diff --git a/yarn.lock b/yarn.lock index 1f21b0273baac..49eb5f91f6e06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -370,6 +370,11 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -498,6 +503,11 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -548,6 +558,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12": + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.17": version "0.3.19" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" @@ -1062,6 +1080,11 @@ dependencies: "@types/node" "*" +"@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + "@types/json-schema@*": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -1440,13 +1463,15 @@ command-line-args "^5.2.1" ts-morph "^19.0.0" -"@vscode/test-cli@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@vscode/test-cli/-/test-cli-0.0.3.tgz#9b02943713652e84a675894ffa4a6fe5375496ab" - integrity sha512-Gk2Vo5OOoJ3bFChW+THN5/gVz7qsGfZUsTgMgQtpx39Z2NqyddONM4MDVGM83Hgjlr+4rCP9RUS5C0WL3ERtdw== +"@vscode/test-cli@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@vscode/test-cli/-/test-cli-0.0.6.tgz#13fe86902b2e8af3f9b67ff7cf66453ea0c24999" + integrity sha512-4i61OUv5PQr3GxhHOuUgHdgBDfIO/kXTPCsEyFiMaY4SOqQTgkTmyZLagHehjOgCfsXdcrJa3zgQ7zoc+Dh6hQ== dependencies: "@types/mocha" "^10.0.2" + c8 "^9.1.0" chokidar "^3.5.3" + enhanced-resolve "^5.15.0" glob "^10.3.10" minimatch "^9.0.3" mocha "^10.2.0" @@ -2381,6 +2406,23 @@ bytes@^3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +c8@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/c8/-/c8-9.1.0.tgz#0e57ba3ab9e5960ab1d650b4a86f71e53cb68112" + integrity sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@istanbuljs/schema" "^0.1.3" + find-up "^5.0.0" + foreground-child "^3.1.1" + istanbul-lib-coverage "^3.2.0" + istanbul-lib-report "^3.0.1" + istanbul-reports "^3.1.6" + test-exclude "^6.0.0" + v8-to-istanbul "^9.0.0" + yargs "^17.7.2" + yargs-parser "^21.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -2902,6 +2944,11 @@ convert-source-map@^1.0.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" @@ -3566,6 +3613,14 @@ enhanced-resolve@^5.10.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" @@ -4340,7 +4395,7 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -foreground-child@^3.1.0: +foreground-child@^3.1.0, foreground-child@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== @@ -4627,6 +4682,18 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-agent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" @@ -5716,6 +5783,15 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" +istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + istanbul-lib-source-maps@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" @@ -5733,6 +5809,14 @@ istanbul-reports@^3.1.5: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +istanbul-reports@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + istextorbinary@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-1.0.2.tgz#ace19354d1a9a0173efeb1084ce0f87b0ad7decf" @@ -6248,6 +6332,13 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -6502,7 +6593,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: +"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -8411,6 +8502,13 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semve dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -9215,6 +9313,15 @@ terser@^5.7.0: source-map "~0.7.2" source-map-support "~0.5.19" +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -9690,6 +9797,15 @@ v8-inspect-profiler@^0.1.0: dependencies: chrome-remote-interface "0.28.2" +v8-to-istanbul@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + v8flags@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" From f6a5654dbf9811b94dd8848571a84b73a7a34813 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 7 Feb 2024 16:11:14 -0800 Subject: [PATCH 0038/1863] Pass the check if any one of the library (of the arch) satisfies the requirement. (#204221) * Update check-requirements-linux.sh Pass the check if one of the library (of the arch) satisfies the requirement. * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo * Update resources/server/bin/helpers/check-requirements-linux.sh Co-authored-by: Robo --------- Co-authored-by: Robo --- .../bin/helpers/check-requirements-linux.sh | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 8c7d1523e2183..9888bbe0831e4 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -53,7 +53,7 @@ if [ "$OS_ID" != "alpine" ]; then libstdcpp_paths=$(/sbin/ldconfig -p | grep 'libstdc++.so.6') if [ "$(echo "$libstdcpp_paths" | wc -l)" -gt 1 ]; then - libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + libstdcpp_path=$(echo "$libstdcpp_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') else libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') fi @@ -72,18 +72,22 @@ if [ -z "$libstdcpp_path" ]; then fi fi -if [ -n "$libstdcpp_path" ]; then +while [ -n "$libstdcpp_path" ]; do # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 # which is then compared based on the fact that release versioning and symbol versioning # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. - libstdcpp_real_path=$(readlink -f "$libstdcpp_path") + libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) + libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") libstdcpp_version=$(echo "$libstdcpp_real_path" | awk -F'\\.so\\.' '{print $NF}') if [ "$(printf '%s\n' "6.0.25" "$libstdcpp_version" | sort -V | head -n1)" = "6.0.25" ]; then found_required_glibcxx=1 - else - echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" + break fi + libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line +done +if [ "$found_required_glibcxx" = "0" ]; then + echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" fi if [ "$OS_ID" = "alpine" ]; then @@ -105,7 +109,7 @@ elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then libc_paths=$(/sbin/ldconfig -p | grep 'libc.so.6') if [ "$(echo "$libc_paths" | wc -l)" -gt 1 ]; then - libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}' | head -n1) + libc_path=$(echo "$libc_paths" | grep "$LDCONFIG_ARCH" | awk '{print $NF}') else libc_path=$(echo "$libc_paths" | awk '{print $NF}') fi @@ -119,16 +123,20 @@ elif [ -z "$(ldd --version 2>&1 | grep 'musl libc')" ]; then echo "Warning: Can't find libc.so or ldconfig, can't verify libc version" fi - if [ -n "$libc_path" ]; then + while [ -n "$libc_path" ]; do # Rather than trusting the output of ldd --version (which is not always accurate) # we instead use the version of the cached libc.so.6 file itself. - libc_real_path=$(readlink -f "$libc_path") + libc_path_line=$(echo "$libc_path" | head -n1) + libc_real_path=$(readlink -f "$libc_path_line") libc_version=$(cat "$libc_real_path" | sed -n 's/.*release version \([0-9]\+\.[0-9]\+\).*/\1/p') if [ "$(printf '%s\n' "2.28" "$libc_version" | sort -V | head -n1)" = "2.28" ]; then found_required_glibc=1 - else - echo "Warning: Missing GLIBC >= 2.28! from $libc_real_path" + break fi + libc_path=$(echo "$libc_path" | tail -n +2) # remove first line + done + if [ "$found_required_glibc" = "0" ]; then + echo "Warning: Missing GLIBC >= 2.28! from $libc_real_path" fi else echo "Warning: musl detected, skipping GLIBC check" From 2eb66826bdec26623ddc88aecc2043993db8878e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 7 Feb 2024 16:24:33 -0800 Subject: [PATCH 0039/1863] testing: rename FunctionCoverage -> DeclarationCoverage for finalization (#204667) --- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostTypeConverters.ts | 4 +- src/vs/workbench/api/common/extHostTypes.ts | 14 ++-- .../browser/codeCoverageDecorations.ts | 6 +- .../testing/browser/testCoverageBars.ts | 10 +-- .../testing/browser/testCoverageView.ts | 76 +++++++++---------- .../contrib/testing/common/testCoverage.ts | 8 +- .../contrib/testing/common/testTypes.ts | 30 ++++---- .../vscode.proposed.testCoverage.d.ts | 37 +++++---- 9 files changed, 94 insertions(+), 95 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5b7786330ac15..f86fd099ef426 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1615,7 +1615,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestResultState: extHostTypes.TestResultState, TestRunRequest: extHostTypes.TestRunRequest, TestMessage: extHostTypes.TestMessage, - TestMessage2: extHostTypes.TestMessage, // back compat for Oct 2023 TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, @@ -1625,7 +1624,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I FileCoverage: extHostTypes.FileCoverage, StatementCoverage: extHostTypes.StatementCoverage, BranchCoverage: extHostTypes.BranchCoverage, - FunctionCoverage: extHostTypes.FunctionCoverage, + DeclarationCoverage: extHostTypes.DeclarationCoverage, + FunctionCoverage: extHostTypes.DeclarationCoverage, // back compat for Feb 2024 WorkspaceTrustState: extHostTypes.WorkspaceTrustState, LanguageStatusSeverity: extHostTypes.LanguageStatusSeverity, QuickPickItemKind: extHostTypes.QuickPickItemKind, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 42a57e47c136e..6237bc4482349 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2021,7 +2021,7 @@ export namespace TestCoverage { }; } else { return { - type: DetailType.Function, + type: DetailType.Declaration, name: coverage.name, count: coverage.executed, location: fromLocation(coverage.location), @@ -2034,7 +2034,7 @@ export namespace TestCoverage { uri: coverage.uri, statement: fromCoveredCount(coverage.statementCoverage), branch: coverage.branchCoverage && fromCoveredCount(coverage.branchCoverage), - function: coverage.functionCoverage && fromCoveredCount(coverage.functionCoverage), + declaration: coverage.declarationCoverage && fromCoveredCount(coverage.declarationCoverage), details: coverage.detailedCoverage?.map(fromDetailed), }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b0269443d64d0..300a1a4c54946 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3970,7 +3970,7 @@ export class FileCoverage implements vscode.FileCoverage { public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage { const statements = new CoveredCount(0, 0); const branches = new CoveredCount(0, 0); - const fn = new CoveredCount(0, 0); + const decl = new CoveredCount(0, 0); for (const detail of details) { if ('branches' in detail) { @@ -3982,8 +3982,8 @@ export class FileCoverage implements vscode.FileCoverage { branches.covered += branch.executed ? 1 : 0; } } else { - fn.total += 1; - fn.covered += detail.executed ? 1 : 0; + decl.total += 1; + decl.covered += detail.executed ? 1 : 0; } } @@ -3991,7 +3991,7 @@ export class FileCoverage implements vscode.FileCoverage { uri, statements, branches.total > 0 ? branches : undefined, - fn.total > 0 ? fn : undefined, + decl.total > 0 ? decl : undefined, ); coverage.detailedCoverage = details; @@ -4005,11 +4005,11 @@ export class FileCoverage implements vscode.FileCoverage { public readonly uri: vscode.Uri, public statementCoverage: vscode.CoveredCount, public branchCoverage?: vscode.CoveredCount, - public functionCoverage?: vscode.CoveredCount, + public declarationCoverage?: vscode.CoveredCount, ) { validateCC(statementCoverage); validateCC(branchCoverage); - validateCC(functionCoverage); + validateCC(declarationCoverage); } } @@ -4037,7 +4037,7 @@ export class BranchCoverage implements vscode.BranchCoverage { ) { } } -export class FunctionCoverage implements vscode.FunctionCoverage { +export class DeclarationCoverage implements vscode.DeclarationCoverage { // back compat until finalization: get executionCount() { return +this.executed; } set executionCount(n: number) { this.executed = n; } diff --git a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts index 0c556853d8c5f..d96710fbd0d00 100644 --- a/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/codeCoverageDecorations.ts @@ -366,7 +366,7 @@ export class CoverageDetailsModel { //#region decoration generation // Coverage from a provider can have a range that contains smaller ranges, - // such as a function declarationt that has nested statements. In this we + // such as a function declaration that has nested statements. In this we // make sequential, non-overlapping ranges for each detail for display in // the editor without ugly overlaps. const detailRanges: DetailRange[] = details.map(detail => ({ @@ -445,8 +445,8 @@ export class CoverageDetailsModel { /** Gets the markdown description for the given detail */ public describe(detail: CoverageDetailsWithBranch, model: ITextModel): IMarkdownString | undefined { - if (detail.type === DetailType.Function) { - return new MarkdownString().appendMarkdown(localize('coverage.fnExecutedCount', 'Function `{0}` was executed {1} time(s).', detail.name, detail.count)); + if (detail.type === DetailType.Declaration) { + return new MarkdownString().appendMarkdown(localize('coverage.declExecutedCount', '`{0}` was executed {1} time(s).', detail.name, detail.count)); } else if (detail.type === DetailType.Statement) { const text = wrapName(model.getValueInRange(tidyLocation(detail.location)).trim() || ``); const str = new MarkdownString(); diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 9a1c2987b9d3e..08cb528de32ab 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -35,7 +35,7 @@ export interface TestCoverageBarsOptions { } /** Type that can be used to render coverage bars */ -export type CoverageBarSource = Pick; +export type CoverageBarSource = Pick; export class ManagedTestCoverageBars extends Disposable { private _coverage?: CoverageBarSource; @@ -142,7 +142,7 @@ export class ManagedTestCoverageBars extends Disposable { renderBar(el.tpcBar, overallStat, false, thresholds); } else { renderBar(el.statement, percent(coverage.statement), coverage.statement.total === 0, thresholds); - renderBar(el.function, coverage.function && percent(coverage.function), coverage.function?.total === 0, thresholds); + renderBar(el.function, coverage.declaration && percent(coverage.declaration), coverage.declaration?.total === 0, thresholds); renderBar(el.branch, coverage.branch && percent(coverage.branch), coverage.branch?.total === 0, thresholds); } } @@ -196,11 +196,11 @@ const calculateDisplayedStat = (coverage: CoverageBarSource, method: TestingDisp case TestingDisplayedCoveragePercent.Minimum: { let value = percent(coverage.statement); if (coverage.branch) { value = Math.min(value, percent(coverage.branch)); } - if (coverage.function) { value = Math.min(value, percent(coverage.function)); } + if (coverage.declaration) { value = Math.min(value, percent(coverage.declaration)); } return value; } case TestingDisplayedCoveragePercent.TotalCoverage: - return getTotalCoveragePercent(coverage.statement, coverage.branch, coverage.function); + return getTotalCoveragePercent(coverage.statement, coverage.branch, coverage.declaration); default: assertNever(method); } @@ -219,7 +219,7 @@ const displayPercent = (value: number, precision = 2) => { }; const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); -const fnCoverageText = (coverage: CoverageBarSource) => coverage.function && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.function.covered, coverage.function.total, displayPercent(percent(coverage.function))); +const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.declaration.covered, coverage.declaration.total, displayPercent(percent(coverage.declaration))); const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); const getOverallHoverText = (coverage: CoverageBarSource) => new MarkdownString([ diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts index 5b583bf832236..2451613400fa4 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageView.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageView.ts @@ -44,7 +44,7 @@ import { CoverageBarSource, ManagedTestCoverageBars } from 'vs/workbench/contrib import { TestCommandId, Testing } from 'vs/workbench/contrib/testing/common/constants'; import { ComputedFileCoverage, FileCoverage, TestCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; -import { CoverageDetails, DetailType, ICoveredCount, IFunctionCoverage, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; +import { CoverageDetails, DetailType, ICoveredCount, IDeclarationCoverage, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; const enum CoverageSortOrder { @@ -97,10 +97,10 @@ export class TestCoverageView extends ViewPane { let fnNodeId = 0; -class FunctionCoverageNode { +class DeclarationCoverageNode { public readonly id = String(fnNodeId++); public readonly containedDetails = new Set(); - public readonly children: FunctionCoverageNode[] = []; + public readonly children: DeclarationCoverageNode[] = []; public get hits() { return this.data.count; @@ -121,7 +121,7 @@ class FunctionCoverageNode { constructor( public readonly uri: URI, - private readonly data: IFunctionCoverage, + private readonly data: IDeclarationCoverage, details: readonly CoverageDetails[], ) { if (data.location instanceof Range) { @@ -172,11 +172,11 @@ class FunctionCoverageNode { } } -class RevealUncoveredFunctions { +class RevealUncoveredDeclarations { public readonly id = String(fnNodeId++); public get label() { - return localize('functionsWithoutCoverage', "{0} functions without coverage...", this.n); + return localize('functionsWithoutCoverage', "{0} declarations without coverage...", this.n); } constructor(public readonly n: number) { } @@ -189,12 +189,12 @@ class LoadingDetails { /** Type of nodes returned from {@link TestCoverage}. Note: value is *always* defined. */ type TestCoverageFileNode = IPrefixTreeNode; -type CoverageTreeElement = TestCoverageFileNode | FunctionCoverageNode | LoadingDetails | RevealUncoveredFunctions; +type CoverageTreeElement = TestCoverageFileNode | DeclarationCoverageNode | LoadingDetails | RevealUncoveredDeclarations; const isFileCoverage = (c: CoverageTreeElement): c is TestCoverageFileNode => typeof c === 'object' && 'value' in c; -const isFunctionCoverage = (c: CoverageTreeElement): c is FunctionCoverageNode => c instanceof FunctionCoverageNode; -const shouldShowFunctionDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => - isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.function?.total; +const isDeclarationCoverage = (c: CoverageTreeElement): c is DeclarationCoverageNode => c instanceof DeclarationCoverageNode; +const shouldShowDeclDetailsOnExpand = (c: CoverageTreeElement): c is IPrefixTreeNode => + isFileCoverage(c) && c.value instanceof FileCoverage && !!c.value.declaration?.total; class TestCoverageTree extends Disposable { private readonly tree: WorkbenchCompressibleObjectTree; @@ -215,7 +215,7 @@ class TestCoverageTree extends Disposable { new TestCoverageTreeListDelegate(), [ instantiationService.createInstance(FileCoverageRenderer, labels), - instantiationService.createInstance(FunctionCoverageRenderer), + instantiationService.createInstance(DeclarationCoverageRenderer), instantiationService.createInstance(BasicRenderer), ], { @@ -256,7 +256,7 @@ class TestCoverageTree extends Disposable { this._register(this.tree); this._register(this.tree.onDidChangeCollapseState(e => { const el = e.node.element; - if (!e.node.collapsed && !e.node.children.length && el && shouldShowFunctionDetailsOnExpand(el)) { + if (!e.node.collapsed && !e.node.children.length && el && shouldShowDeclDetailsOnExpand(el)) { if (el.value!.hasSynchronousDetails) { this.tree.setChildren(el, [{ element: new LoadingDetails(), incompressible: true }]); } @@ -270,7 +270,7 @@ class TestCoverageTree extends Disposable { if (e.element) { if (isFileCoverage(e.element) && !e.element.children?.size) { resource = e.element.value!.uri; - } else if (isFunctionCoverage(e.element)) { + } else if (isDeclarationCoverage(e.element)) { resource = e.element.uri; selection = e.element.location; } @@ -310,7 +310,7 @@ class TestCoverageTree extends Disposable { incompressible: isFile, collapsed: isFile, // directories can be expanded, and items with function info can be expanded - collapsible: !isFile || !!file.value?.function?.total, + collapsible: !isFile || !!file.value?.declaration?.total, children: file.children && Iterable.map(file.children?.values(), toChild) }; }; @@ -327,13 +327,13 @@ class TestCoverageTree extends Disposable { return; // avoid any issues if the tree changes in the meanwhile } - const functions: FunctionCoverageNode[] = []; + const decl: DeclarationCoverageNode[] = []; for (const fn of details) { - if (fn.type !== DetailType.Function) { + if (fn.type !== DetailType.Declaration) { continue; } - let arr = functions; + let arr = decl; while (true) { const parent = arr.find(p => p.containedDetails.has(fn)); if (parent) { @@ -343,10 +343,10 @@ class TestCoverageTree extends Disposable { } } - arr.push(new FunctionCoverageNode(el.value!.uri, fn, details)); + arr.push(new DeclarationCoverageNode(el.value!.uri, fn, details)); } - const makeChild = (fn: FunctionCoverageNode): ICompressedTreeElement => ({ + const makeChild = (fn: DeclarationCoverageNode): ICompressedTreeElement => ({ element: fn, incompressible: true, collapsed: true, @@ -354,7 +354,7 @@ class TestCoverageTree extends Disposable { children: fn.children.map(makeChild) }); - this.tree.setChildren(el, functions.map(makeChild)); + this.tree.setChildren(el, decl.map(makeChild)); } } @@ -367,10 +367,10 @@ class TestCoverageTreeListDelegate implements IListVirtualDelegate { case CoverageSortOrder.Coverage: return b.value!.tpc - a.value!.tpc; } - } else if (isFunctionCoverage(a) && isFunctionCoverage(b)) { + } else if (isDeclarationCoverage(a) && isDeclarationCoverage(b)) { switch (order) { case CoverageSortOrder.Location: return Position.compare( @@ -474,7 +474,7 @@ class FileCoverageRenderer implements ICompressibleTreeRenderer { +class DeclarationCoverageRenderer implements ICompressibleTreeRenderer { public static readonly ID = 'N'; - public readonly templateId = FunctionCoverageRenderer.ID; + public readonly templateId = DeclarationCoverageRenderer.ID; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, ) { } /** @inheritdoc */ - public renderTemplate(container: HTMLElement): FunctionTemplateData { + public renderTemplate(container: HTMLElement): DeclarationTemplateData { const templateDisposables = new DisposableStore(); container.classList.add('test-coverage-list-item'); const icon = dom.append(container, dom.$('.state')); @@ -507,21 +507,21 @@ class FunctionCoverageRenderer implements ICompressibleTreeRenderer, _index: number, templateData: FunctionTemplateData): void { - this.doRender(node.element as FunctionCoverageNode, templateData, node.filterData); + public renderElement(node: ITreeNode, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element as DeclarationCoverageNode, templateData, node.filterData); } /** @inheritdoc */ - public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: FunctionTemplateData): void { - this.doRender(node.element.elements[node.element.elements.length - 1] as FunctionCoverageNode, templateData, node.filterData); + public renderCompressedElements(node: ITreeNode, FuzzyScore>, _index: number, templateData: DeclarationTemplateData): void { + this.doRender(node.element.elements[node.element.elements.length - 1] as DeclarationCoverageNode, templateData, node.filterData); } - public disposeTemplate(templateData: FunctionTemplateData) { + public disposeTemplate(templateData: DeclarationTemplateData) { templateData.templateDisposables.dispose(); } /** @inheritdoc */ - private doRender(element: FunctionCoverageNode, templateData: FunctionTemplateData, _filterData: FuzzyScore | undefined) { + private doRender(element: DeclarationCoverageNode, templateData: DeclarationTemplateData, _filterData: FuzzyScore | undefined) { const covered = !!element.hits; const icon = covered ? testingWasCovered : testingStatesToIcons.get(TestResultState.Unset); templateData.container.classList.toggle('not-covered', !covered); @@ -552,7 +552,7 @@ class BasicRenderer implements ICompressibleTreeRenderer()); const items: Item[] = [ - { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, functions are sorted by position') }, - { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and functions are sorted by total coverage') }, - { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and functions are sorted alphabetically') }, + { label: localize('testing.coverageSortByLocation', 'Sort by Location'), value: CoverageSortOrder.Location, description: localize('testing.coverageSortByLocationDescription', 'Files are sorted alphabetically, declarations are sorted by position') }, + { label: localize('testing.coverageSortByCoverage', 'Sort by Coverage'), value: CoverageSortOrder.Coverage, description: localize('testing.coverageSortByCoverageDescription', 'Files and declarations are sorted by total coverage') }, + { label: localize('testing.coverageSortByName', 'Sort by Name'), value: CoverageSortOrder.Name, description: localize('testing.coverageSortByNameDescription', 'Files and declarations are sorted alphabetically') }, ]; quickInput.placeholder = localize('testing.coverageSortPlaceholder', 'Sort the Test Coverage view...'); diff --git a/src/vs/workbench/contrib/testing/common/testCoverage.ts b/src/vs/workbench/contrib/testing/common/testCoverage.ts index 7545c7c3da668..05d532263da7d 100644 --- a/src/vs/workbench/contrib/testing/common/testCoverage.ts +++ b/src/vs/workbench/contrib/testing/common/testCoverage.ts @@ -98,7 +98,7 @@ export class TestCoverage { ICoveredCount.sum(fileCoverage.statement, v.statement); if (v.branch) { ICoveredCount.sum(fileCoverage.branch ??= ICoveredCount.empty(), v.branch); } - if (v.function) { ICoveredCount.sum(fileCoverage.function ??= ICoveredCount.empty(), v.function); } + if (v.declaration) { ICoveredCount.sum(fileCoverage.declaration ??= ICoveredCount.empty(), v.declaration); } } } @@ -146,21 +146,21 @@ export abstract class AbstractFileCoverage { public readonly uri: URI; public readonly statement: ICoveredCount; public readonly branch?: ICoveredCount; - public readonly function?: ICoveredCount; + public readonly declaration?: ICoveredCount; /** * Gets the total coverage percent based on information provided. * This is based on the Clover total coverage formula */ public get tpc() { - return getTotalCoveragePercent(this.statement, this.branch, this.function); + return getTotalCoveragePercent(this.statement, this.branch, this.declaration); } constructor(coverage: IFileCoverage) { this.uri = coverage.uri; this.statement = coverage.statement; this.branch = coverage.branch; - this.function = coverage.function; + this.declaration = coverage.declaration; } } diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts index ed4ff1c93ea34..7737ef7802057 100644 --- a/src/vs/workbench/contrib/testing/common/testTypes.ts +++ b/src/vs/workbench/contrib/testing/common/testTypes.ts @@ -549,7 +549,7 @@ export interface IFileCoverage { uri: URI; statement: ICoveredCount; branch?: ICoveredCount; - function?: ICoveredCount; + declaration?: ICoveredCount; details?: CoverageDetails[]; } @@ -559,14 +559,14 @@ export namespace IFileCoverage { uri: UriComponents; statement: ICoveredCount; branch?: ICoveredCount; - function?: ICoveredCount; + declaration?: ICoveredCount; details?: CoverageDetails.Serialized[]; } export const serialize = (original: Readonly): Serialized => ({ statement: original.statement, branch: original.branch, - function: original.function, + declaration: original.declaration, details: original.details?.map(CoverageDetails.serialize), uri: original.uri.toJSON(), }); @@ -574,7 +574,7 @@ export namespace IFileCoverage { export const deserialize = (uriIdentity: ITestUriCanonicalizer, serialized: Serialized): IFileCoverage => ({ statement: serialized.statement, branch: serialized.branch, - function: serialized.function, + declaration: serialized.declaration, details: serialized.details?.map(CoverageDetails.deserialize), uri: uriIdentity.asCanonicalUri(URI.revive(serialized.uri)), }); @@ -596,21 +596,21 @@ function deserializeThingWithLocation): Serialized => - original.type === DetailType.Function ? IFunctionCoverage.serialize(original) : IStatementCoverage.serialize(original); + original.type === DetailType.Declaration ? IDeclarationCoverage.serialize(original) : IStatementCoverage.serialize(original); export const deserialize = (serialized: Serialized): CoverageDetails => - serialized.type === DetailType.Function ? IFunctionCoverage.deserialize(serialized) : IStatementCoverage.deserialize(serialized); + serialized.type === DetailType.Declaration ? IDeclarationCoverage.deserialize(serialized) : IStatementCoverage.deserialize(serialized); } export interface IBranchCoverage { @@ -630,23 +630,23 @@ export namespace IBranchCoverage { export const deserialize: (original: Serialized) => IBranchCoverage = deserializeThingWithLocation; } -export interface IFunctionCoverage { - type: DetailType.Function; +export interface IDeclarationCoverage { + type: DetailType.Declaration; name: string; count: number | boolean; location: Range | Position; } -export namespace IFunctionCoverage { +export namespace IDeclarationCoverage { export interface Serialized { - type: DetailType.Function; + type: DetailType.Declaration; name: string; count: number | boolean; location: IRange | IPosition; } - export const serialize: (original: IFunctionCoverage) => Serialized = serializeThingWithLocation; - export const deserialize: (original: Serialized) => IFunctionCoverage = deserializeThingWithLocation; + export const serialize: (original: IDeclarationCoverage) => Serialized = serializeThingWithLocation; + export const deserialize: (original: Serialized) => IDeclarationCoverage = deserializeThingWithLocation; } export interface IStatementCoverage { diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index aa0b1fee9ac23..614792e0d9cf2 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -47,7 +47,7 @@ declare module 'vscode' { /** * A class that contains information about a covered resource. A count can - * be give for lines, branches, and functions in a file. + * be give for lines, branches, and declarations in a file. */ export class CoveredCount { /** @@ -87,9 +87,10 @@ declare module 'vscode' { branchCoverage?: CoveredCount; /** - * Function coverage information. + * Declaration coverage information. Depending on the reporter and + * language, this may be types such as functions, methods, or namespaces. */ - functionCoverage?: CoveredCount; + declarationCoverage?: CoveredCount; /** * Detailed, per-statement coverage. If this is undefined, the editor will @@ -111,19 +112,16 @@ declare module 'vscode' { * does not provide statement coverage information, this can instead be * used to represent line coverage. * @param branchCoverage Branch coverage information - * @param functionCoverage Function coverage information + * @param declarationCoverage Declaration coverage information */ constructor( uri: Uri, statementCoverage: CoveredCount, branchCoverage?: CoveredCount, - functionCoverage?: CoveredCount, + declarationCoverage?: CoveredCount, ); } - // @API are StatementCoverage and BranchCoverage etc really needed - // or is a generic type with a kind-property enough - /** * Contains coverage information for a single statement or line. */ @@ -189,35 +187,36 @@ declare module 'vscode' { } /** - * Contains coverage information for a function or method. + * Contains coverage information for a declaration. Depending on the reporter + * and language, this may be types such as functions, methods, or namespaces. */ - export class FunctionCoverage { + export class DeclarationCoverage { /** - * Name of the function or method. + * Name of the declaration. */ name: string; /** - * The number of times this function was executed, or a boolean indicating - * whether it was executed if the exact count is unknown. If zero or false, - * the function will be marked as un-covered. + * The number of times this declaration was executed, or a boolean + * indicating whether it was executed if the exact count is unknown. If + * zero or false, the declaration will be marked as un-covered. */ executed: number | boolean; /** - * Function location. + * Declaration location. */ location: Position | Range; /** - * @param executed The number of times this function was executed, or a + * @param executed The number of times this declaration was executed, or a * boolean indicating whether it was executed if the exact count is - * unknown. If zero or false, the function will be marked as un-covered. - * @param location The function position. + * unknown. If zero or false, the declaration will be marked as un-covered. + * @param location The declaration position. */ constructor(name: string, executed: number | boolean, location: Position | Range); } - export type DetailedCoverage = StatementCoverage | FunctionCoverage; + export type DetailedCoverage = StatementCoverage | DeclarationCoverage; } From da36e0eba18babc8eb3e18f5a9b30e1dd7e083d4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 7 Feb 2024 18:01:32 -0800 Subject: [PATCH 0040/1863] Disable url smart paste in autolinks (#204673) Fixes #188859 --- .../src/languageFeatures/copyFiles/pasteUrlProvider.ts | 1 + .../markdown-language-features/src/test/pasteUrl.test.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 4968f3cb22733..a57f0d3900500 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -89,6 +89,7 @@ const smartPasteLineRegexes = [ { regex: /\$\$[\s\S]*?\$\$/gm }, // In a fenced math block { regex: /`[^`]*`/g }, // In inline code { regex: /\$[^$]*\$/g }, // In inline math + { regex: /<[^<>\s]*>/g }, // Autolink { regex: /^[ ]{0,3}\[\w+\]:\s.*$/g, isWholeLine: true }, // Block link definition (needed as tokens are not generated for these) ]; diff --git a/extensions/markdown-language-features/src/test/pasteUrl.test.ts b/extensions/markdown-language-features/src/test/pasteUrl.test.ts index 88b996de37ddc..df863a2565263 100644 --- a/extensions/markdown-language-features/src/test/pasteUrl.test.ts +++ b/extensions/markdown-language-features/src/test/pasteUrl.test.ts @@ -300,5 +300,11 @@ suite('createEditAddingLinksForUriList', () => { await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc(' \r\n\r\n'), PasteUrlAsMarkdownLink.SmartWithSelection, [new vscode.Range(0, 0, 0, 7)], noopToken), false); }); + + test('Smart should be disabled inside of autolinks', async () => { + assert.strictEqual( + await shouldInsertMarkdownLinkByDefault(createNewMarkdownEngine(), makeTestDoc('<>'), PasteUrlAsMarkdownLink.Smart, [new vscode.Range(0, 1, 0, 1)], noopToken), + false); + }); }); }); From 3b5844353f73230abaa22b6c87755bddbb0aeb78 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 8 Feb 2024 08:50:47 +0100 Subject: [PATCH 0041/1863] window - guard against invalid title settings (#204683) --- .../browser/parts/titlebar/windowTitle.ts | 38 ++++++++++++++----- .../browser/workbench.contribution.ts | 18 ++------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 0628c153307c0..3ec39eeafc26d 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -12,7 +12,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { isWindows, isWeb, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, isWeb, isMacintosh, isNative } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { trim } from 'vs/base/common/strings'; import { IEditorGroupsContainer } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -30,9 +30,23 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', - title = 'window.title', + title = 'window.title' } +export const defaultWindowTitle = (() => { + if (isMacintosh && isNative) { + return '${activeEditorShort}${separator}${rootName}${separator}${profileName}'; // macOS has native dirty indicator + } + + const base = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}${appName}'; + if (isWeb) { + return base + '${separator}${remoteName}'; // Web: always show remote name + } + + return base; +})(); +export const defaultWindowTitleSeparator = isMacintosh ? ' \u2014 ' : ' - '; + export class WindowTitle extends Disposable { private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]"); @@ -324,17 +338,24 @@ export class WindowTitle extends Disposable { const dirty = editor?.isDirty() && !editor.isSaving() ? WindowTitle.TITLE_DIRTY : ''; const appName = this.productService.nameLong; const profileName = this.userDataProfileService.currentProfile.isDefault ? '' : this.userDataProfileService.currentProfile.name; - const separator = this.configurationService.getValue(WindowSettingNames.titleSeparator); - const titleTemplate = this.configurationService.getValue(WindowSettingNames.title); const focusedView: string = this.viewsService.getFocusedViewName(); - - // Variables (contributed) - const contributedVariables: { [key: string]: string } = {}; + const variables: Record = {}; for (const [contextKey, name] of this.variables) { - contributedVariables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + variables[name] = this.contextKeyService.getContextKeyValue(contextKey) ?? ''; + } + + let titleTemplate = this.configurationService.getValue(WindowSettingNames.title); + if (typeof titleTemplate !== 'string') { + titleTemplate = defaultWindowTitle; + } + + let separator = this.configurationService.getValue(WindowSettingNames.titleSeparator); + if (typeof separator !== 'string') { + separator = defaultWindowTitleSeparator; } return template(titleTemplate, { + ...variables, activeEditorShort, activeEditorLong, activeEditorMedium, @@ -351,7 +372,6 @@ export class WindowTitle extends Disposable { remoteName, profileName, focusedView, - ...contributedVariables, separator: { label: separator } }); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index f288862112044..b715436c0d466 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -6,11 +6,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchSecurityConfiguration, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs, problemsConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { defaultWindowTitle, defaultWindowTitleSeparator } from 'vs/workbench/browser/parts/titlebar/windowTitle'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -624,23 +625,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'properties': { 'window.title': { 'type': 'string', - 'default': (() => { - if (isMacintosh && isNative) { - return '${activeEditorShort}${separator}${rootName}${separator}${profileName}'; // macOS has native dirty indicator - } - - const base = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}${appName}'; - if (isWeb) { - return base + '${separator}${remoteName}'; // Web: always show remote name - } - - return base; - })(), + 'default': defaultWindowTitle, 'markdownDescription': windowTitleDescription }, 'window.titleSeparator': { 'type': 'string', - 'default': isMacintosh ? ' \u2014 ' : ' - ', + 'default': defaultWindowTitleSeparator, 'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`') }, [LayoutSettings.COMMAND_CENTER]: { From d25c50303e44695a5e37738c2330b5cebdee5e88 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:02:47 +0100 Subject: [PATCH 0042/1863] Git - Add more telemetry to history provider to investigate issue (#204689) --- extensions/git/src/historyProvider.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 0063f45cad31d..dbb7b8d8a36e6 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -34,19 +34,24 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private disposables: Disposable[] = []; constructor(protected readonly repository: Repository, private readonly logger: LogOutputChannel) { - this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this)); - this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this._onDidChangeCurrentHistoryItemGroup.fire())); + this.disposables.push(repository.onDidRunGitStatus(() => this.onDidRunGitStatus(), this)); + this.disposables.push(filterEvent(repository.onDidRunOperation, e => e.operation === Operation.Refresh)(() => this.onDidRunGitStatus(true), this)); this.disposables.push(window.registerFileDecorationProvider(this)); } - private async onDidRunGitStatus(): Promise { + private async onDidRunGitStatus(force = false): Promise { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD:', JSON.stringify(this._HEAD)); + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - repository.HEAD:', JSON.stringify(this.repository.HEAD)); + // Check if HEAD has changed - if (this._HEAD?.name === this.repository.HEAD?.name && + if (!force && + this._HEAD?.name === this.repository.HEAD?.name && this._HEAD?.commit === this.repository.HEAD?.commit && this._HEAD?.upstream?.name === this.repository.HEAD?.upstream?.name && this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote && this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD has not changed'); return; } @@ -55,6 +60,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Check if HEAD does not support incoming/outgoing (detached commit, tag) if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { this.currentHistoryItemGroup = undefined; + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); return; } @@ -67,6 +73,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, } : undefined }; + + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(this.currentHistoryItemGroup)); } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { From 768c8e3e93aee95c4fd47711cfc1e213771d6833 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 10:09:42 +0100 Subject: [PATCH 0043/1863] add some API todos (#204695) --- src/vscode-dts/vscode.proposed.chat.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts index 170a995e56d7f..1e27e2e5ab9a1 100644 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -9,6 +9,7 @@ declare module 'vscode' { export enum ChatMessageRole { System = 0, User = 1, + // TODO@API name: align with ChatAgent (or whatever we'll rename that to) Assistant = 2, } @@ -16,6 +17,8 @@ declare module 'vscode' { export class ChatMessage { role: ChatMessageRole; content: string; + + // TODO@API is this a leftover from Role.Function? Should message just support a catch-all signature? name?: string; constructor(role: ChatMessageRole, content: string); From 3a00525d5c24d3439c351deaaa883af33b9e9264 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 01:24:20 -0800 Subject: [PATCH 0044/1863] remove user consent for chat (#204698) --- .../contrib/chat/browser/chat.contribution.ts | 3 +-- .../extensionFeaturesManagemetService.ts | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f334bfc59fa13..cc1983fb53b29 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -338,8 +338,7 @@ Registry.as(ExtensionFeaturesExtensions.ExtensionFea label: nls.localize('chat', "Chat"), description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), access: { - canToggle: true, - requireUserConsent: true, + canToggle: false, }, renderer: new SyncDescriptor(ChatFeatureMarkdowneRenderer), }); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index 460eaafc515f0..fe6bee617fbfc 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -104,19 +104,20 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio } if (featureState.disabled === undefined) { - const extensionDescription = this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension)); - const confirmationResult = await this.dialogService.confirm({ - title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), - message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), - detail: justification ?? feature.description, - custom: true, - primaryButton: localize('allow', "Allow"), - cancelButton: localize('disallow', "Don't Allow"), - }); - this.setEnablement(extension, featureId, confirmationResult.confirmed); - if (!confirmationResult.confirmed) { - return false; + let enabled = true; + if (feature.access.requireUserConsent) { + const extensionDescription = this.extensionService.extensions.find(e => ExtensionIdentifier.equals(e.identifier, extension)); + const confirmationResult = await this.dialogService.confirm({ + title: localize('accessExtensionFeature', "Access '{0}' Feature", feature.label), + message: localize('accessExtensionFeatureMessage', "'{0}' extension would like to access the '{1}' feature.", extensionDescription?.displayName ?? extension.value, feature.label), + detail: justification ?? feature.description, + custom: true, + primaryButton: localize('allow', "Allow"), + cancelButton: localize('disallow', "Don't Allow"), + }); + enabled = confirmationResult.confirmed; } + this.setEnablement(extension, featureId, enabled); } featureState.accessData.current = { From cccd228808c9fee13286d7158441861d7b324457 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 10:47:54 +0100 Subject: [PATCH 0045/1863] disable extension mangling, (#204700) workaround for https://github.com/microsoft/vscode/issues/204692 --- extensions/mangle-loader.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/mangle-loader.js b/extensions/mangle-loader.js index b6b22ce3f1aae..016d0f6903310 100644 --- a/extensions/mangle-loader.js +++ b/extensions/mangle-loader.js @@ -41,6 +41,10 @@ module.exports = async function (source, sourceMap, meta) { // Only enable mangling in production builds return source; } + if (true) { + // disable mangling for now, SEE https://github.com/microsoft/vscode/issues/204692 + return source; + } const options = this.getOptions(); if (options.disabled) { // Dynamically disabled From 7b0e5303dd6ed7865921e0783c775ba9467dc6aa Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 11:41:49 +0100 Subject: [PATCH 0046/1863] add proposed API check (#204705) fyi @andreamah --- src/vs/workbench/api/common/extHost.api.impl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f86fd099ef426..c0569540c2e58 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -939,6 +939,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, findFiles2: (filePattern, options?, token?) => { + checkProposedApiEnabled(extension, 'findFiles2'); return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { From 9d7e3d9cc150625c2e24def0058d7e3030b426a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 11:58:32 +0100 Subject: [PATCH 0047/1863] make `live` the default mode for inline chat, remove the `livePreview` setting but not (yet) the implementation (#204706) https://github.com/microsoft/vscode/issues/204368 --- .../contrib/inlineChat/browser/inlineChatController.ts | 8 ++++---- .../workbench/contrib/inlineChat/common/inlineChat.ts | 10 +++++----- .../browser/view/cellParts/chat/cellChatController.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d22229e3528c4..781e4477dc966 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -347,16 +347,16 @@ export class InlineChatController implements IEditorContribution { // create a new strategy switch (session.editMode) { - case EditMode.Live: - this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); - break; case EditMode.Preview: this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value); break; case EditMode.LivePreview: - default: this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); break; + case EditMode.Live: + default: + this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); + break; } this._session = session; diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 0794a0f8a1512..4a4717a88ba7e 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -193,8 +193,9 @@ export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewR export const enum EditMode { Live = 'live', + Preview = 'preview', + /** @deprecated */ LivePreview = 'livePreview', - Preview = 'preview' } Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( @@ -217,13 +218,12 @@ Registry.as(Extensions.Configuration).registerConfigurat properties: { [InlineChatConfigKeys.Mode]: { description: localize('mode', "Configure if changes crafted with inline chat are applied directly to the document or are previewed first."), - default: EditMode.LivePreview, + default: EditMode.Live, type: 'string', - enum: [EditMode.LivePreview, EditMode.Preview, EditMode.Live], + enum: [EditMode.Live, EditMode.Preview], markdownEnumDescriptions: [ - localize('mode.livePreview', "Changes are applied directly to the document and are highlighted visually via inline or side-by-side diffs. Ending a session will keep the changes."), - localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), localize('mode.live', "Changes are applied directly to the document, can be highlighted via inline diffs, and accepted/discarded by hunks. Ending a session will keep the changes."), + localize('mode.preview', "Changes are previewed only and need to be accepted via the apply button. Ending a session will discard the changes."), ], tags: ['experimental'] }, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts index 829d4d0c4a5f8..0e05e8cae1436 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts @@ -270,7 +270,7 @@ export class NotebookCellChatController extends Disposable { const session = await this._inlineChatSessionService.createSession( editor, - { editMode: EditMode.LivePreview }, + { editMode: EditMode.Live }, token ); From 14770d1197cc70b4ebca0885d147bcf0aaacc24b Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 5 Feb 2024 16:06:50 +0100 Subject: [PATCH 0048/1863] feat: add proposed API `newSymbolNamesProvider` that allows extensions to suggest new names for symbols that are being renamed --- src/vs/editor/common/languages.ts | 4 ++ .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 3 +- src/vs/monaco.d.ts | 4 ++ .../api/browser/mainThreadLanguageFeatures.ts | 10 +++- .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../workbench/api/common/extHost.protocol.ts | 2 + .../api/common/extHostLanguageFeatures.ts | 50 ++++++++++++++++++- .../common/extensionsApiProposals.ts | 1 + ...scode.proposed.newSymbolNamesProvider.d.ts | 25 ++++++++++ 10 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 43d17fb060973..1f0a242b31f19 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1652,6 +1652,10 @@ export interface RenameProvider { resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +export interface NewSymbolNamesProvider { + provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; +} + export interface Command { id: string; title: string; diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 760e6e6e22adf..92f7e271f1ab1 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -29,6 +29,8 @@ export interface ILanguageFeaturesService { readonly renameProvider: LanguageFeatureRegistry; + readonly newSymbolNamesProvider: LanguageFeatureRegistry; + readonly documentFormattingEditProvider: LanguageFeatureRegistry; readonly documentRangeFormattingEditProvider: LanguageFeatureRegistry; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index c6504614ce396..4e414b34b3699 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -15,6 +15,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly referenceProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly renameProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly newSymbolNamesProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly codeActionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly definitionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly typeDefinitionProvider = new LanguageFeatureRegistry(this._score.bind(this)); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ed1b548900fe5..dc6e5fb74e9e7 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7766,6 +7766,10 @@ declare namespace monaco.languages { resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + export interface NewSymbolNamesProvider { + provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + } + export interface Command { id: string; title: string; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 497fc46663b71..b9d86e64edf91 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -479,7 +479,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- rename $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void { - this._registrations.set(handle, this._languageFeaturesService.renameProvider.register(selector, { + this._registrations.set(handle, this._languageFeaturesService.renameProvider.register(selector, { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken) => { return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(data => reviveWorkspaceEditDto(data, this._uriIdentService)); }, @@ -489,6 +489,14 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread })); } + $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void { + this._registrations.set(handle, this._languageFeaturesService.newSymbolNamesProvider.register(selector, { + provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { + return this._proxy.$provideNewSymbolNames(handle, model.uri, range, token); + } + } satisfies languages.NewSymbolNamesProvider)); + } + // --- semantic tokens $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c0569540c2e58..db680538d6b24 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -582,6 +582,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { return extHostLanguageFeatures.registerRenameProvider(extension, checkSelector(selector), provider); }, + registerNewSymbolNamesProvider(selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'newSymbolNamesProvider'); + return extHostLanguageFeatures.registerNewSymbolNamesProvider(extension, checkSelector(selector), provider); + }, registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider, metadata?: vscode.DocumentSymbolProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerDocumentSymbolProvider(extension, checkSelector(selector), provider, metadata); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 676cf530dcf2d..e8b75cbcebab8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -416,6 +416,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number, supportsResolve: boolean): void; $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void; $emitDocumentSemanticTokensEvent(eventHandle: number): void; $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend): void; @@ -2103,6 +2104,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise; $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b0a8742c349da..90bf9b204692a 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -816,6 +816,44 @@ class RenameAdapter { } } +class NewSymbolNamesAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.NewSymbolNamesProvider, + private readonly _logService: ILogService + ) { } + + async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { + + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Range.to(range); + + try { + const value = await this._provider.provideNewSymbolNames(doc, pos, token); + if (!value) { + return undefined; + } + return value; + + } catch (err: unknown) { + this._logService.error(NewSymbolNamesAdapter._asMessage(err) ?? JSON.stringify(err, null, '\t') /* @ulugbekna: assuming `err` doesn't have circular references that could result in an exception when converting to JSON */); + return undefined; + } + } + + // @ulugbekna: this method is also defined in RenameAdapter but seems OK to be duplicated + private static _asMessage(err: any): string | undefined { + if (typeof err === 'string') { + return err; + } else if (err instanceof Error && typeof err.message === 'string') { + return err.message; + } else { + return undefined; + } + } +} + class SemanticTokensPreviousResult { constructor( readonly resultId: string | undefined, @@ -1905,7 +1943,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter - | DocumentOnDropEditAdapter | MappedEditsAdapter; + | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter; class AdapterData { constructor( @@ -2287,6 +2325,16 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined, token); } + registerNewSymbolNamesProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable { + const handle = this._addNewAdapter(new NewSymbolNamesAdapter(this._documents, provider, this._logService), extension); + this._proxy.$registerNewSymbolNamesProvider(handle, this._transformDocumentSelector(selector, extension)); + return this._createDisposable(handle); + } + + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { + return this._withAdapter(handle, NewSymbolNamesAdapter, adapter => adapter.provideNewSymbolNames(URI.revive(resource), range, token), undefined, token); + } + //#region semantic coloring registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 15df9887466ac..0edc713a74c07 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -70,6 +70,7 @@ export const allApiProposals = Object.freeze({ languageStatusText: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', multiDocumentHighlightProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', + newSymbolNamesProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', diff --git a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts new file mode 100644 index 0000000000000..5f2c994d2831d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/204345 @ulugbekna + +declare module 'vscode' { + + export interface NewSymbolNamesProvider { + /** + * Provide possible new names for the symbol at the given range. + * + * @param document The document in which the symbol is defined. + * @param range The range that spans the symbol being renamed. + * @param token A cancellation token. + * @return A list of new symbol names. + */ + provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } + + export namespace languages { + export function registerNewSymbolNamesProvider(selector: DocumentSelector, provider: NewSymbolNamesProvider): Disposable; + } +} From bd1536407a815b0f0d715bb6728080f5772f6a11 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 5 Feb 2024 19:48:00 +0100 Subject: [PATCH 0049/1863] rename: show list of rename candidate names, allow tabbing through them and selecting one by pressing 'enter' --- .../editor/contrib/rename/browser/rename.ts | 9 ++- .../rename/browser/renameInputField.css | 13 ++++ .../rename/browser/renameInputField.ts | 76 +++++++++++++++++-- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index cd88b21da7ea2..16229d323e963 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -208,6 +208,13 @@ class RenameController implements IEditorContribution { // part 2 - do rename at location const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); + const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled + const newNameCandidates = Promise.all( + this._languageFeaturesService.newSymbolNamesProvider + .all(model) + .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able + ).then((candidates) => candidates.filter((c): c is string[] => !!c).flat()); + const selection = this.editor.getSelection(); let selectionStart = 0; let selectionEnd = loc.text.length; @@ -218,7 +225,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, cts2.token); + const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newNameCandidates, cts2.token); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index 1b5de07fab00f..9f73a682823e3 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -23,6 +23,19 @@ opacity: .8; } +.monaco-editor .rename-box .new-name-candidates-container { + margin-top: 4px; +} + +.monaco-editor .rename-box .new-name-candidate { + /* FIXME@ulugbekna: adapt colors to be nice */ + background-color: rgb(2, 96, 190); + color: white; + + margin: 2px; + padding: 2px; +} + .monaco-editor .rename-box.preview .rename-label { display: inherit; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index e00d68cd368b8..6e6b4b1b3e48d 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -30,6 +31,7 @@ export class RenameInputField implements IContentWidget { private _position?: Position; private _domNode?: HTMLElement; private _input?: HTMLInputElement; + private _newNameCandidates?: NewSymbolNameCandidates; private _label?: HTMLDivElement; private _visible?: boolean; private readonly _visibleContextKey: IContextKey; @@ -77,6 +79,11 @@ export class RenameInputField implements IContentWidget { this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode.appendChild(this._input); + // TODO@ulugbekna: add keyboard support for cycling through the candidates + this._newNameCandidates = new NewSymbolNameCandidates(); + this._domNode.appendChild(this._newNameCandidates!.domNode); + this._disposables.add(this._newNameCandidates); + this._label = document.createElement('div'); this._label.className = 'rename-label'; this._domNode.appendChild(this._label); @@ -108,7 +115,7 @@ export class RenameInputField implements IContentWidget { } private _updateFont(): void { - if (!this._input || !this._label) { + if (!this._input || !this._label || !this._newNameCandidates) { return; } @@ -117,6 +124,10 @@ export class RenameInputField implements IContentWidget { this._input.style.fontWeight = fontInfo.fontWeight; this._input.style.fontSize = `${fontInfo.fontSize}px`; + this._newNameCandidates.domNode.style.fontFamily = fontInfo.fontFamily; + this._newNameCandidates.domNode.style.fontWeight = fontInfo.fontWeight; + this._newNameCandidates.domNode.style.fontSize = `${fontInfo.fontSize}px`; + this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`; } @@ -155,7 +166,7 @@ export class RenameInputField implements IContentWidget { this._currentCancelInput?.(focusEditor); } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, newNameCandidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -167,26 +178,39 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); + newNameCandidates.then(candidates => { + this._newNameCandidates!.setCandidates(candidates); + }); + return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { this._currentAcceptInput = undefined; this._currentCancelInput = undefined; + this._newNameCandidates?.clearCandidates(); resolve(focusEditor); return true; }; this._currentAcceptInput = (wantsPreview) => { - if (this._input!.value.trim().length === 0 || this._input!.value === value) { + if (this._input!.value.trim().length === 0) { // empty or whitespace only or not changed this.cancelInput(true); return; } + const selectedCandidate = this._newNameCandidates?.selectedCandidate; + if ((selectedCandidate === undefined && this._input!.value === value) || selectedCandidate === value) { + this.cancelInput(true); + return; + } + this._currentAcceptInput = undefined; this._currentCancelInput = undefined; + this._newNameCandidates?.clearCandidates(); + resolve({ - newName: this._input!.value, + newName: selectedCandidate ?? this._input!.value, wantsPreview: supportPreview && wantsPreview }); }; @@ -222,3 +246,45 @@ export class RenameInputField implements IContentWidget { this._editor.layoutContentWidget(this); } } + +class NewSymbolNameCandidates implements IDisposable { + + public readonly domNode: HTMLDivElement; + + private _candidates: HTMLSpanElement[] = []; + private _disposables = new DisposableStore(); + + constructor() { + this.domNode = document.createElement('div'); + this.domNode.className = 'new-name-candidates-container'; + this.domNode.tabIndex = -1; // Make the div unfocusable + } + + get selectedCandidate(): string | undefined { + const activeDocument = dom.getActiveDocument(); + const activeElement = activeDocument.activeElement; + const index = this._candidates.indexOf(activeElement as HTMLSpanElement); + return index !== -1 ? this._candidates[index].innerText : undefined; + } + + setCandidates(candidates: string[]): void { + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const candidateElt = document.createElement('span'); + candidateElt.className = 'new-name-candidate'; + candidateElt.innerText = candidate; + candidateElt.tabIndex = 0; + this.domNode.appendChild(candidateElt); + this._candidates.push(candidateElt); + } + } + + clearCandidates(): void { + this.domNode.innerText = ''; // TODO@ulugbekna: make sure this is the right way to clean up children + this._candidates = []; + } + + dispose(): void { + this._disposables.dispose(); + } +} From 9b2e567ee2a13ff5ccad58897343cef0aebfc1df Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 6 Feb 2024 16:53:44 +0100 Subject: [PATCH 0050/1863] rename: re-use Button class & styling --- src/vs/base/browser/ui/button/button.ts | 4 ++ .../rename/browser/renameInputField.css | 15 ++--- .../rename/browser/renameInputField.ts | 63 ++++++++++++------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 2e845b684f69a..3b1ad7915bb20 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -78,6 +78,9 @@ export class Button extends Disposable implements IButton { private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } + private _onDidEscape = this._register(new Emitter()); + get onDidEscape(): BaseEvent { return this._onDidEscape.event; } + private focusTracker: IFocusTracker; constructor(container: HTMLElement, options: IButtonOptions) { @@ -134,6 +137,7 @@ export class Button extends Disposable implements IButton { this._onDidClick.fire(e); eventHandled = true; } else if (event.equals(KeyCode.Escape)) { + this._onDidEscape.fire(e); this._element.blur(); eventHandled = true; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index 9f73a682823e3..d02e7d5afcabc 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -16,6 +16,7 @@ .monaco-editor .rename-box .rename-input { padding: 3px; border-radius: 2px; + width: calc(100% - 8px); /* 4px padding on each side */ } .monaco-editor .rename-box .rename-label { @@ -23,15 +24,15 @@ opacity: .8; } -.monaco-editor .rename-box .new-name-candidates-container { - margin-top: 4px; +.rename-box .new-name-candidates-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-top: 5px; } -.monaco-editor .rename-box .new-name-candidate { - /* FIXME@ulugbekna: adapt colors to be nice */ - background-color: rgb(2, 96, 190); - color: white; - +.rename-box .new-name-candidates-container > .monaco-text-button { + width: auto; margin: 2px; padding: 2px; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 6e6b4b1b3e48d..0f8a2dc7b26a9 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Emitter } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -16,6 +18,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { editorWidgetBackground, inputBackground, inputBorder, inputForeground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -79,10 +82,11 @@ export class RenameInputField implements IContentWidget { this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode.appendChild(this._input); - // TODO@ulugbekna: add keyboard support for cycling through the candidates + // TODO@ulugbekna: support accept/escape corresponding to the keybindings this._newNameCandidates = new NewSymbolNameCandidates(); + this._newNameCandidates.onAccept(() => this.acceptInput(false)); // FIXME@ulugbekna: need to handle preview + this._newNameCandidates.onEscape(() => this._input!.focus()); this._domNode.appendChild(this._newNameCandidates!.domNode); - this._disposables.add(this._newNameCandidates); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -144,6 +148,10 @@ export class RenameInputField implements IContentWidget { beforeRender(): IDimension | null { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); + // TODO@ulugbekna: elements larger than maxWidth shouldn't overflow + const maxWidth = Math.ceil(this._editor.getLayoutInfo().contentWidth / 4); + this._domNode!.style.maxWidth = `${maxWidth}px`; + this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in return null; } @@ -179,7 +187,9 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); newNameCandidates.then(candidates => { - this._newNameCandidates!.setCandidates(candidates); + if (!token.isCancellationRequested) { // TODO@ulugbekna: make sure this's the correct token to check + this._newNameCandidates!.setCandidates(candidates); + } }); return new Promise(resolve => { @@ -247,44 +257,49 @@ export class RenameInputField implements IContentWidget { } } -class NewSymbolNameCandidates implements IDisposable { +export class NewSymbolNameCandidates { + + public readonly domNode: HTMLElement; - public readonly domNode: HTMLDivElement; + private _onAcceptEmitter = new Emitter(); + public readonly onAccept = this._onAcceptEmitter.event; + private _onEscapeEmitter = new Emitter(); + public readonly onEscape = this._onEscapeEmitter.event; - private _candidates: HTMLSpanElement[] = []; - private _disposables = new DisposableStore(); + private _candidates: Button[] = []; + private _candidateDisposables: DisposableStore | undefined; + + // TODO@ulugbekna: pressing escape when focus is on a candidate should return the focus to the input field constructor() { this.domNode = document.createElement('div'); - this.domNode.className = 'new-name-candidates-container'; + this.domNode.className = 'rename-box new-name-candidates-container'; this.domNode.tabIndex = -1; // Make the div unfocusable } get selectedCandidate(): string | undefined { - const activeDocument = dom.getActiveDocument(); - const activeElement = activeDocument.activeElement; - const index = this._candidates.indexOf(activeElement as HTMLSpanElement); - return index !== -1 ? this._candidates[index].innerText : undefined; + const selected = this._candidates.find(c => c.hasFocus()); + return selected === undefined ? undefined : ( + assertType(typeof selected.label === 'string', 'string'), + selected.label + ); } setCandidates(candidates: string[]): void { + this._candidateDisposables = new DisposableStore(); for (let i = 0; i < candidates.length; i++) { const candidate = candidates[i]; - const candidateElt = document.createElement('span'); - candidateElt.className = 'new-name-candidate'; - candidateElt.innerText = candidate; - candidateElt.tabIndex = 0; - this.domNode.appendChild(candidateElt); + const candidateElt = new Button(this.domNode, defaultButtonStyles); + this._candidateDisposables.add(candidateElt.onDidClick(() => this._onAcceptEmitter.fire(candidate))); + this._candidateDisposables.add(candidateElt.onDidEscape(() => this._onEscapeEmitter.fire())); + candidateElt.label = candidate; this._candidates.push(candidateElt); } } clearCandidates(): void { - this.domNode.innerText = ''; // TODO@ulugbekna: make sure this is the right way to clean up children + this._candidateDisposables?.dispose(); + this.domNode.innerText = ''; this._candidates = []; } - - dispose(): void { - this._disposables.dispose(); - } } From a12d9f44fc5f89cb5db5d3e50a5869f2ba5515f0 Mon Sep 17 00:00:00 2001 From: Yifei Yang Date: Thu, 8 Feb 2024 19:43:01 +0800 Subject: [PATCH 0051/1863] Fix: GLIBCXX version detection bug in check-requirements-linux.sh (issue #204186) (#204635) * Fix: Fixed glibc version detection bug in check-requirements-linux.sh - Existing detection scripts simply use the `awk` command to record the version number in the `libstdc++.so` filename, - but in some specific versions of glibc++, the detailed version number is not indicated in the filename, - so we need to use a script to read the current version of GLIBCXX in the environment to see if it meets the expectations. Co-authored-by: chengy-sysu <939416532@qq.com> * Update check-requirements-linux.sh fix Indent * fix: Using grep and sed to replace strings command Since some Linux distributions do not come with GNU binutils pre-installed, the `strings` command does not fit on all platforms, so we use `grep` and `sed` instead of `strings`. Co-authored-by: chengy-sysu <939416532@qq.com> * fix: boundary case of check-requirements-linux.sh Co-authored-by: chengy-sysu <939416532@qq.com> * fix: Using grep and sed to replace strings command Since some Linux distributions do not come with GNU binutils pre-installed, the `strings` command does not fit on all platforms, so we use `grep` and `sed` instead of `strings`. Co-authored-by: chengy-sysu <939416532@qq.com> * fix: skip glibcxx check on alpine --------- Co-authored-by: chengy-sysu <939416532@qq.com> Co-authored-by: deepak1556 --- .../bin/helpers/check-requirements-linux.sh | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/resources/server/bin/helpers/check-requirements-linux.sh b/resources/server/bin/helpers/check-requirements-linux.sh index 9888bbe0831e4..be5207c2f0517 100644 --- a/resources/server/bin/helpers/check-requirements-linux.sh +++ b/resources/server/bin/helpers/check-requirements-linux.sh @@ -57,11 +57,7 @@ if [ "$OS_ID" != "alpine" ]; then else libstdcpp_path=$(echo "$libstdcpp_paths" | awk '{print $NF}') fi - fi -fi - -if [ -z "$libstdcpp_path" ]; then - if [ -f /usr/lib/libstdc++.so.6 ]; then + elif [ -f /usr/lib/libstdc++.so.6 ]; then # Typical path libstdcpp_path='/usr/lib/libstdc++.so.6' elif [ -f /usr/lib64/libstdc++.so.6 ]; then @@ -70,22 +66,26 @@ if [ -z "$libstdcpp_path" ]; then else echo "Warning: Can't find libstdc++.so or ldconfig, can't verify libstdc++ version" fi -fi -while [ -n "$libstdcpp_path" ]; do - # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 - # which is then compared based on the fact that release versioning and symbol versioning - # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html - # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. - libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) - libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") - libstdcpp_version=$(echo "$libstdcpp_real_path" | awk -F'\\.so\\.' '{print $NF}') - if [ "$(printf '%s\n' "6.0.25" "$libstdcpp_version" | sort -V | head -n1)" = "6.0.25" ]; then - found_required_glibcxx=1 - break - fi - libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line -done + while [ -n "$libstdcpp_path" ]; do + # Extracts the version number from the path, e.g. libstdc++.so.6.0.22 -> 6.0.22 + # which is then compared based on the fact that release versioning and symbol versioning + # are aligned for libstdc++. Refs https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html + # (i-e) GLIBCXX_3.4. is provided by libstdc++.so.6.y. + libstdcpp_path_line=$(echo "$libstdcpp_path" | head -n1) + libstdcpp_real_path=$(readlink -f "$libstdcpp_path_line") + libstdcpp_version=$(grep -ao 'GLIBCXX_[0-9]*\.[0-9]*\.[0-9]*' "$libstdcpp_real_path" | sort -V | tail -1) + libstdcpp_version_number=$(echo "$libstdcpp_version" | sed 's/GLIBCXX_//') + if [ "$(printf '%s\n' "3.4.24" "$libstdcpp_version_number" | sort -V | head -n1)" = "3.4.24" ]; then + found_required_glibcxx=1 + break + fi + libstdcpp_path=$(echo "$libstdcpp_path" | tail -n +2) # remove first line + done +else + echo "Warning: alpine distro detected, skipping GLIBCXX check" + found_required_glibcxx=1 +fi if [ "$found_required_glibcxx" = "0" ]; then echo "Warning: Missing GLIBCXX >= 3.4.25! from $libstdcpp_real_path" fi From e33834f54d5aff7146c36f632e9422d141f7d7d0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 03:58:58 -0800 Subject: [PATCH 0052/1863] return if not enabled (#204714) --- .../common/extensionFeaturesManagemetService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts index fe6bee617fbfc..0e60b30cd138c 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeaturesManagemetService.ts @@ -118,6 +118,9 @@ class ExtensionFeaturesManagementService extends Disposable implements IExtensio enabled = confirmationResult.confirmed; } this.setEnablement(extension, featureId, enabled); + if (!enabled) { + return false; + } } featureState.accessData.current = { From 11b38898343ad3fd53328fc3caadef942ba43d1f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 8 Feb 2024 14:33:41 +0100 Subject: [PATCH 0053/1863] Revisit need for workbench contribution that block editor restore (#203947) (#204710) --- .../contrib/accessibility/browser/accessibility.contribution.ts | 2 +- src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 4ec8d2e9b16d2..fb097ebecaac7 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -29,5 +29,5 @@ workbenchRegistry.registerWorkbenchContribution(NotificationAccessibleViewContri workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually); registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore); -registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(SaveAudioCueContribution.ID, SaveAudioCueContribution, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(DynamicSpeechAccessibilityConfiguration.ID, DynamicSpeechAccessibilityConfiguration, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index c83ebc45f77dc..7cdd7d910ad4c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -314,7 +314,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo } } -registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchPhase.BlockRestore); +registerWorkbenchContribution2(EditorWordWrapContextKeyTracker.ID, EditorWordWrapContextKeyTracker, WorkbenchPhase.AfterRestored); registerEditorContribution(ToggleWordWrapController.ID, ToggleWordWrapController, EditorContributionInstantiation.Eager); // eager because it needs to change the editor word wrap configuration registerDiffEditorContribution(DiffToggleWordWrapController.ID, DiffToggleWordWrapController); From 867f35c00dc0bcd34bbbdcc8fdf6472b460fc37a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:03:56 +0100 Subject: [PATCH 0054/1863] wip --- extensions/git/src/commands.ts | 1 + .../standalone/browser/standaloneEditor.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 1 + .../bulkEdit/browser/preview/bulkEditPane.ts | 44 ++++++++++++++++--- .../browser/preview/bulkEditPreview.ts | 1 + 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3f46f698ce22f..9c6c2e1991b83 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3780,6 +3780,7 @@ export class CommandCenter { resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); } + // Command which is executed in order to open the multi diff editor, uring the passed in URI, the given title and the resources which are the modified resource and the original resource commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 059a4928862c6..10a75a4490422 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -100,6 +100,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options); } +// There is also a function which creates a multi file diff editor, but maybe we do not need it export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) { const instantiationService = StandaloneServices.initialize(override || {}); return new MultiDiffEditorWidget(domElement, {}, instantiationService); diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index ee2be0b1bcc24..16c8268848bd8 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -416,6 +416,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _sourceControlHandle: number, private _id: string, private _label: string, + // The following appears to be adding multi diff editor support public readonly multiDiffEditorEnableViewChanges: boolean, private readonly _extension: IExtensionDescription, ) { } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 4a18e62f25636..4cfaa3c07eead 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -38,6 +38,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const enum State { Data = 'data', @@ -79,6 +80,7 @@ export class BulkEditPane extends ViewPane { @IDialogService private readonly _dialogService: IDialogService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IStorageService private readonly _storageService: IStorageService, + @ICommandService private readonly commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IKeybindingService keybindingService: IKeybindingService, @@ -223,6 +225,7 @@ export class BulkEditPane extends ViewPane { return Boolean(this._currentInput); } + // Presumably the method where we set the data to show in the tree refactors view private async _setTreeInput(input: BulkFileOperations) { const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); @@ -316,6 +319,7 @@ export class BulkEditPane extends ViewPane { } } + // In this function, we actually open the element as an editor, and this is where we could open a multi file diff editor private async _openElementAsEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -333,9 +337,15 @@ export class BulkEditPane extends ViewPane { return; } + console.log('options : ', JSON.stringify(options)); + const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); if (fileElement.edit.type & BulkFileOperationType.Delete) { + + console.log('fileElement.edit : ', fileElement.edit); + console.log('previewUri : ', JSON.stringify(previewUri)); + // delete -> show single editor this._editorService.openEditor({ label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), @@ -343,7 +353,17 @@ export class BulkEditPane extends ViewPane { options }); + // const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); + // const uri = fileElement.edit.newUri ?? fileElement.edit.uri; + // const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = [{ + // originalUri: fileElement.edit.uri, + // modifiedUri: fileElement.edit.newUri + // }]; + // await commands.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + } else { + console.log('fileElement.edit ; ', fileElement.edit); + // rename, create, edits -> show diff editr let leftResource: URI | undefined; try { @@ -367,13 +387,23 @@ export class BulkEditPane extends ViewPane { label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); } - this._editorService.openEditor({ - original: { resource: leftResource }, - modified: { resource: previewUri }, - label, - description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), - options - }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + console.log('leftResource : ', JSON.stringify(leftResource)); + console.log('previewUri : ', JSON.stringify(previewUri)); + + // this._editorService.openEditor({ + // original: { resource: leftResource }, + // modified: { resource: previewUri }, + // label, + // description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), + // options + // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + + const uri = previewUri; + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ + originalUri: leftResource, + modifiedUri: previewUri + }]; + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index d9e8a1112a0d2..c649d96c78cff 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -307,6 +307,7 @@ export class BulkFileOperations { return result; } + // Getting the edits for a specific file getFileEdits(uri: URI): ISingleEditOperation[] { for (const file of this.fileOperations) { From 366ff2c156ffc3627a7422cc72530589ba213929 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:10:19 +0100 Subject: [PATCH 0055/1863] SCM - extract title context keys into its own workbench contribution (#204736) --- .../workbench/contrib/scm/browser/activity.ts | 118 ++++++++++++------ .../contrib/scm/browser/scm.contribution.ts | 5 +- 2 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 402df518c9940..efd752e0f533a 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -28,18 +28,10 @@ function getCount(repository: ISCMRepository): number { } } -const ContextKeys = { - ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), - ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), -}; - export class SCMStatusController implements IWorkbenchContribution { - private activeRepositoryNameContextKey: IContextKey; - private activeRepositoryBranchNameContextKey: IContextKey; - private statusBarDisposable: IDisposable = Disposable.None; - private focusDisposables = new DisposableStore(); + private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private readonly badgeDisposable = new MutableDisposable(); private readonly disposables = new DisposableStore(); @@ -52,9 +44,7 @@ export class SCMStatusController implements IWorkbenchContribution { @IActivityService private readonly activityService: IActivityService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IContextKeyService contextKeyService: IContextKeyService, - @ITitleService titleService: ITitleService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); @@ -66,14 +56,6 @@ export class SCMStatusController implements IWorkbenchContribution { this.onDidAddRepository(repository); } - this.activeRepositoryNameContextKey = ContextKeys.ActiveRepositoryName.bindTo(contextKeyService); - this.activeRepositoryBranchNameContextKey = ContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); - - titleService.registerVariables([ - { name: 'activeRepositoryName', contextKey: ContextKeys.ActiveRepositoryName.key }, - { name: 'activeRepositoryBranchName', contextKey: ContextKeys.ActiveRepositoryBranchName.key, } - ]); - this.scmViewService.onDidFocusRepository(this.focusRepository, this, this.disposables); this.focusRepository(this.scmViewService.focusedRepository); @@ -144,33 +126,17 @@ export class SCMStatusController implements IWorkbenchContribution { return; } - this.focusDisposables.clear(); + this.focusDisposable.dispose(); this.focusedRepository = repository; - if (repository) { - if (repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposables.add(repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository))); - } - - this.focusDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { - if (repository.provider.historyProvider) { - this.focusDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); - } - - this.updateContextKeys(repository); - })); + if (repository && repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); } - this.updateContextKeys(repository); this.renderStatusBar(repository); this.renderActivityCount(); } - private updateContextKeys(repository: ISCMRepository | undefined): void { - this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); - this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); - } - private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); @@ -239,7 +205,7 @@ export class SCMStatusController implements IWorkbenchContribution { } dispose(): void { - this.focusDisposables.dispose(); + this.focusDisposable.dispose(); this.statusBarDisposable.dispose(); this.badgeDisposable.dispose(); this.disposables.dispose(); @@ -248,6 +214,78 @@ export class SCMStatusController implements IWorkbenchContribution { } } +const ActiveRepositoryContextKeys = { + ActiveRepositoryName: new RawContextKey('scmActiveRepositoryName', ''), + ActiveRepositoryBranchName: new RawContextKey('scmActiveRepositoryBranchName', ''), +}; + +export class SCMActiveRepositoryContextKeyController implements IWorkbenchContribution { + + private activeRepositoryNameContextKey: IContextKey; + private activeRepositoryBranchNameContextKey: IContextKey; + + private readonly disposables = new DisposableStore(); + private readonly focusedRepositoryDisposables = new DisposableStore(); + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IEditorService private readonly editorService: IEditorService, + @ISCMViewService private readonly scmViewService: ISCMViewService, + @ITitleService titleService: ITitleService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + ) { + this.activeRepositoryNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryName.bindTo(contextKeyService); + this.activeRepositoryBranchNameContextKey = ActiveRepositoryContextKeys.ActiveRepositoryBranchName.bindTo(contextKeyService); + + titleService.registerVariables([ + { name: 'activeRepositoryName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryName.key }, + { name: 'activeRepositoryBranchName', contextKey: ActiveRepositoryContextKeys.ActiveRepositoryBranchName.key, } + ]); + + editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + scmViewService.onDidFocusRepository(this.onDidFocusRepository, this, this.disposables); + this.onDidFocusRepository(scmViewService.focusedRepository); + } + + private onDidActiveEditorChange(): void { + const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor); + + if (activeResource?.scheme !== Schemas.file && activeResource?.scheme !== Schemas.vscodeRemote) { + return; + } + + const activeResourceRepository = Iterable.find( + this.scmViewService.visibleRepositories, + r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) + ); + + this.onDidFocusRepository(activeResourceRepository); + } + + private onDidFocusRepository(repository: ISCMRepository | undefined): void { + this.focusedRepositoryDisposables.clear(); + + if (!repository) { + return; + } + + this.focusedRepositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { + if (repository.provider.historyProvider) { + this.focusedRepositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); + } + + this.updateContextKeys(repository); + })); + + this.updateContextKeys(repository); + } + + private updateContextKeys(repository: ISCMRepository | undefined): void { + this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); + this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); + } +} + export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { private activeResourceHasChangesContextKey: IContextKey; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 8fae0d843b8a1..9cd686878977e 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; +import { SCMActiveRepositoryContextKeyController, SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -113,6 +113,9 @@ viewsRegistry.registerViews([{ Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(SCMActiveRepositoryContextKeyController, LifecyclePhase.Restored); + Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); From 3fa90909560116c71afd8b8eeda3947c85d67e5e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:23:47 +0100 Subject: [PATCH 0056/1863] adding support so that the multi file diff editor shows for all of the bulk operations at the same time --- src/vs/platform/list/browser/listService.ts | 1 + .../bulkEdit/browser/preview/bulkEditPane.ts | 134 ++++++++++++------ 2 files changed, 90 insertions(+), 45 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 6857531cb6681..317af0fdf3a43 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -770,6 +770,7 @@ abstract class ResourceNavigator extends Disposable { this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } + // We want to actually retrieve all of the elements, not just the element at hand private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { if (!element) { return; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 4cfaa3c07eead..437721a69fc18 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -145,6 +145,7 @@ export class BulkEditPane extends ViewPane { ); this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); + // Thing is when the tree is clicked, we want actually to show all of the files inside of the multi diff editor this._disposables.add(this._tree.onDidOpen(e => this._openElementAsEditor(e))); // buttons @@ -339,56 +340,53 @@ export class BulkEditPane extends ViewPane { console.log('options : ', JSON.stringify(options)); - const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); - if (fileElement.edit.type & BulkFileOperationType.Delete) { + const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); console.log('fileElement.edit : ', fileElement.edit); console.log('previewUri : ', JSON.stringify(previewUri)); // delete -> show single editor - this._editorService.openEditor({ - label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), - resource: previewUri, - options - }); - - // const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); - // const uri = fileElement.edit.newUri ?? fileElement.edit.uri; - // const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = [{ - // originalUri: fileElement.edit.uri, - // modifiedUri: fileElement.edit.newUri - // }]; - // await commands.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + // this._editorService.openEditor({ + // label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), + // resource: previewUri, + // options + // }); + + const uri = previewUri; + + const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ + originalUri: undefined, + modifiedUri: previewUri + }]; + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); } else { console.log('fileElement.edit ; ', fileElement.edit); // rename, create, edits -> show diff editr - let leftResource: URI | undefined; - try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; - } - - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } - - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - } - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); + // let leftResource: URI | undefined; + // try { + // (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + // leftResource = fileElement.edit.uri; + // } catch { + // leftResource = BulkEditPreviewProvider.emptyPreview; + // } + + // let typeLabel: string | undefined; + // if (fileElement.edit.type & BulkFileOperationType.Rename) { + // typeLabel = localize('rename', "rename"); + // } else if (fileElement.edit.type & BulkFileOperationType.Create) { + // typeLabel = localize('create', "create"); + // } + + // let label: string; + // if (typeLabel) { + // label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); + // } else { + // label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); + // } // this._editorService.openEditor({ // original: { resource: leftResource }, @@ -398,12 +396,58 @@ export class BulkEditPane extends ViewPane { // options // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - const uri = previewUri; - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ - originalUri: leftResource, - modifiedUri: previewUri - }]; - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + // Perhaps a better way to access the parent instead of accessing through the parent of the file element + + let bulkFileOperations: BulkFileOperations | undefined = undefined; + let currentParent = fileElement.parent; + while (true) { + if (currentParent instanceof BulkFileOperations) { + bulkFileOperations = currentParent; + break; + } + currentParent = currentParent.parent; + } + const allBulkFileOperations = bulkFileOperations.fileOperations; + console.log('allBulkFileOperations : ', allBulkFileOperations); + + const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = []; + + for (const operation of allBulkFileOperations) { + const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); + + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + leftResource = fileElement.edit.uri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + + let typeLabel: string | undefined; + if (fileElement.edit.type & BulkFileOperationType.Rename) { + typeLabel = localize('rename', "rename"); + } else if (fileElement.edit.type & BulkFileOperationType.Create) { + typeLabel = localize('create', "create"); + } + + let label: string; + if (typeLabel) { + label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); + } else { + label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); + } + + console.log('leftResource : ', JSON.stringify(leftResource)); + console.log('previewUri : ', JSON.stringify(previewUri)); + + resources.push({ + originalUri: leftResource, + modifiedUri: previewUri + }); + } + + const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); + this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); } } From d40fd3baa2b280c3efc9432ebf07c57fb4aa1a84 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 17:32:06 +0100 Subject: [PATCH 0057/1863] adding notes --- .../bulkEdit/browser/preview/bulkEditPane.ts | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 437721a69fc18..229662787263a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -346,13 +346,6 @@ export class BulkEditPane extends ViewPane { console.log('fileElement.edit : ', fileElement.edit); console.log('previewUri : ', JSON.stringify(previewUri)); - // delete -> show single editor - // this._editorService.openEditor({ - // label: localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)), - // resource: previewUri, - // options - // }); - const uri = previewUri; const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); @@ -365,39 +358,6 @@ export class BulkEditPane extends ViewPane { } else { console.log('fileElement.edit ; ', fileElement.edit); - // rename, create, edits -> show diff editr - // let leftResource: URI | undefined; - // try { - // (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - // leftResource = fileElement.edit.uri; - // } catch { - // leftResource = BulkEditPreviewProvider.emptyPreview; - // } - - // let typeLabel: string | undefined; - // if (fileElement.edit.type & BulkFileOperationType.Rename) { - // typeLabel = localize('rename', "rename"); - // } else if (fileElement.edit.type & BulkFileOperationType.Create) { - // typeLabel = localize('create', "create"); - // } - - // let label: string; - // if (typeLabel) { - // label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - // } else { - // label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - // } - - // this._editorService.openEditor({ - // original: { resource: leftResource }, - // modified: { resource: previewUri }, - // label, - // description: this._labelService.getUriLabel(dirname(leftResource), { relative: true }), - // options - // }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - - // Perhaps a better way to access the parent instead of accessing through the parent of the file element - let bulkFileOperations: BulkFileOperations | undefined = undefined; let currentParent = fileElement.parent; while (true) { @@ -446,6 +406,10 @@ export class BulkEditPane extends ViewPane { }); } + // Issues with current implementation + // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one + // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere + // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); } From fa3500ffc7ff75207a9bdd2aa5ab2429f9065664 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 8 Feb 2024 08:43:41 -0800 Subject: [PATCH 0058/1863] Remove used notebook profile exp (#204739) --- .../contrib/profile/notebookProfile.ts | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index ae330d22aacc3..ec03486e587ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; -import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; export enum NotebookProfileType { default = 'default', @@ -89,40 +86,40 @@ function isSetProfileArgs(args: unknown): args is ISetProfileArgs { setProfileArgs.profile === NotebookProfileType.jupyter; } -export class NotebookProfileContribution extends Disposable { +// export class NotebookProfileContribution extends Disposable { - static readonly ID = 'workbench.contrib.notebookProfile'; +// static readonly ID = 'workbench.contrib.notebookProfile'; - constructor(@IConfigurationService configService: IConfigurationService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService) { - super(); +// constructor(@IConfigurationService configService: IConfigurationService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService) { +// super(); - if (this.experimentService) { - this.experimentService.getTreatment('notebookprofile').then(treatment => { - if (treatment === undefined) { - return; - } else { - // check if settings are already modified - const focusIndicator = configService.getValue(NotebookSetting.focusIndicator); - const insertToolbarPosition = configService.getValue(NotebookSetting.insertToolbarLocation); - const globalToolbar = configService.getValue(NotebookSetting.globalToolbar); - // const cellToolbarLocation = configService.getValue(NotebookSetting.cellToolbarLocation); - const compactView = configService.getValue(NotebookSetting.compactView); - const showCellStatusBar = configService.getValue(NotebookSetting.showCellStatusBar); - const consolidatedRunButton = configService.getValue(NotebookSetting.consolidatedRunButton); - if (focusIndicator === 'border' - && insertToolbarPosition === 'both' - && globalToolbar === false - // && cellToolbarLocation === undefined - && compactView === true - && showCellStatusBar === 'visible' - && consolidatedRunButton === true - ) { - applyProfile(configService, profiles[treatment] ?? profiles[NotebookProfileType.default]); - } - } - }); - } - } -} +// if (this.experimentService) { +// this.experimentService.getTreatment('notebookprofile').then(treatment => { +// if (treatment === undefined) { +// return; +// } else { +// // check if settings are already modified +// const focusIndicator = configService.getValue(NotebookSetting.focusIndicator); +// const insertToolbarPosition = configService.getValue(NotebookSetting.insertToolbarLocation); +// const globalToolbar = configService.getValue(NotebookSetting.globalToolbar); +// // const cellToolbarLocation = configService.getValue(NotebookSetting.cellToolbarLocation); +// const compactView = configService.getValue(NotebookSetting.compactView); +// const showCellStatusBar = configService.getValue(NotebookSetting.showCellStatusBar); +// const consolidatedRunButton = configService.getValue(NotebookSetting.consolidatedRunButton); +// if (focusIndicator === 'border' +// && insertToolbarPosition === 'both' +// && globalToolbar === false +// // && cellToolbarLocation === undefined +// && compactView === true +// && showCellStatusBar === 'visible' +// && consolidatedRunButton === true +// ) { +// applyProfile(configService, profiles[treatment] ?? profiles[NotebookProfileType.default]); +// } +// } +// }); +// } +// } +// } -registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchPhase.BlockRestore); +// registerWorkbenchContribution2(NotebookProfileContribution.ID, NotebookProfileContribution, WorkbenchPhase.BlockRestore); From 44653961b25376dc581990ee1a65a6d72b8c17c7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 8 Feb 2024 18:12:06 +0100 Subject: [PATCH 0059/1863] Adding preview of multi-cursor inline completions (#204146) * Adding preview of multi-cursor inline completions --------- Co-authored-by: Henning Dieterichs --- .../inlineCompletions/browser/ghostText.ts | 5 + .../browser/hoverParticipant.ts | 2 +- .../browser/inlineCompletionContextKeys.ts | 6 +- .../browser/inlineCompletionsController.ts | 67 +++++++--- .../browser/inlineCompletionsHintsWidget.ts | 2 +- .../browser/inlineCompletionsModel.ts | 119 ++++++++++-------- .../browser/singleTextEdit.ts | 5 + .../inlineCompletions/test/browser/utils.ts | 2 +- .../browser/accessibilityContributions.ts | 4 +- 9 files changed, 135 insertions(+), 77 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts index 68f913238a70d..42d0404984fb1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostText.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals } from 'vs/base/common/arrays'; import { splitLines } from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { ColumnRange, applyEdits } from 'vs/editor/contrib/inlineCompletions/browser/utils'; @@ -135,6 +136,10 @@ export class GhostTextReplacement { export type GhostTextOrReplacement = GhostText | GhostTextReplacement; +export function ghostTextsOrReplacementsEqual(a: readonly GhostTextOrReplacement[] | undefined, b: readonly GhostTextOrReplacement[] | undefined): boolean { + return equals(a, b, ghostTextOrReplacementEquals); +} + export function ghostTextOrReplacementEquals(a: GhostTextOrReplacement | undefined, b: GhostTextOrReplacement | undefined): boolean { if (a === b) { return true; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts index 373eece44c2e0..96b27ee7bf54f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hoverParticipant.ts @@ -142,7 +142,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan disposableStore.add(autorun(reader => { /** @description update hover */ - const ghostText = part.controller.model.read(reader)?.ghostText.read(reader); + const ghostText = part.controller.model.read(reader)?.primaryGhostText.read(reader); if (ghostText) { const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber); render(ghostText.renderForScreenReader(lineText)); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts index 1e9a7fbbbd00f..fcbcd62120fd3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts @@ -33,10 +33,10 @@ export class InlineCompletionContextKeys extends Disposable { const model = this.model.read(reader); const state = model?.state.read(reader); - const isInlineCompletionVisible = !!state?.inlineCompletion && state?.ghostText !== undefined && !state?.ghostText.isEmpty(); + const isInlineCompletionVisible = !!state?.inlineCompletion && state?.primaryGhostText !== undefined && !state?.primaryGhostText.isEmpty(); this.inlineCompletionVisible.set(isInlineCompletionVisible); - if (state?.ghostText && state?.inlineCompletion) { + if (state?.primaryGhostText && state?.inlineCompletion) { this.suppressSuggestions.set(state.inlineCompletion.inlineCompletion.source.inlineCompletions.suppressSuggestions); } })); @@ -48,7 +48,7 @@ export class InlineCompletionContextKeys extends Disposable { let startsWithIndentation = false; let startsWithIndentationLessThanTabSize = true; - const ghostText = model?.ghostText.read(reader); + const ghostText = model?.primaryGhostText.read(reader); if (!!model?.selectedSuggestItem && ghostText && ghostText.parts.length > 0) { const { column, lines } = ghostText.parts[0]; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 4f38487a6ffb8..17f6edfee9a02 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -5,8 +5,8 @@ import { createStyleSheet2 } from 'vs/base/browser/dom'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ITransaction, autorun, autorunHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, ITransaction, autorun, autorunHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,6 +29,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; +import { ISettableObservable } from 'vs/base/common/observableInternal/base'; export class InlineCompletionsController extends Disposable { static ID = 'editor.contrib.inlineCompletionsController'; @@ -37,9 +39,9 @@ export class InlineCompletionsController extends Disposable { return editor.getContribution(InlineCompletionsController.ID); } - public readonly model = disposableObservableValue('inlineCompletionModel', undefined); + public readonly model = this._register(disposableObservableValue('inlineCompletionModel', undefined)); private readonly _textModelVersionId = observableValue(this, -1); - private readonly _cursorPosition = observableValue(this, new Position(1, 1)); + private readonly _positions = observableValue(this, [new Position(1, 1)]); private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor( this.editor, () => this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined), @@ -55,11 +57,20 @@ export class InlineCompletionsController extends Disposable { private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled); private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily); - private _ghostTextWidget = this._register(this._instantiationService.createInstance(GhostTextWidget, this.editor, { - ghostText: this.model.map((v, reader) => /** ghostText */ v?.ghostText.read(reader)), - minReservedLineCount: constObservable(0), - targetTextModel: this.model.map(v => v?.textModel), - })); + private readonly _ghostTexts = derived(this, (reader) => { + const model = this.model.read(reader); + return model?.ghostTexts.read(reader) ?? []; + }); + + private readonly _stablizedGhostTexts = convertItemsToStableObservables(this._ghostTexts, this._store); + + private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => { + return store.add(this._instantiationService.createInstance(GhostTextWidget, this.editor, { + ghostText: ghostText, + minReservedLineCount: constObservable(0), + targetTextModel: this.model.map(v => v?.textModel), + })); + }).recomputeInitiallyAndOnChange(this._store); private readonly _debounceValue = this._debounceService.for( this._languageFeaturesService.inlineCompletionsProvider, @@ -101,8 +112,8 @@ export class InlineCompletionsController extends Disposable { InlineCompletionsModel, textModel, this._suggestWidgetAdaptor.selectedItem, - this._cursorPosition, this._textModelVersionId, + this._positions, this._debounceValue, observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).preview), observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).previewMode), @@ -188,7 +199,7 @@ export class InlineCompletionsController extends Disposable { /** @description InlineCompletionsController.forceRenderingAbove */ const state = this.model.read(reader)?.state.read(reader); if (state?.suggestItem) { - if (state.ghostText.lineCount >= 2) { + if (state.primaryGhostText.lineCount >= 2) { this._suggestWidgetAdaptor.forceRenderingAbove(); } } else { @@ -220,10 +231,10 @@ export class InlineCompletionsController extends Disposable { if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.inlineCompletion.semanticId; - const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); + const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); this._audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { - this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText)); + this.provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText)); } }); } @@ -260,11 +271,11 @@ export class InlineCompletionsController extends Disposable { private updateObservables(tx: ITransaction, changeReason: VersionIdChangeReason): void { const newModel = this.editor.getModel(); this._textModelVersionId.set(newModel?.getVersionId() ?? -1, tx, changeReason); - this._cursorPosition.set(this.editor.getPosition() ?? new Position(1, 1), tx); + this._positions.set(this.editor.getSelections()?.map(selection => selection.getPosition()) ?? [new Position(1, 1)], tx); } public shouldShowHoverAt(range: Range) { - const ghostText = this.model.get()?.ghostText.get(); + const ghostText = this.model.get()?.primaryGhostText.get(); if (ghostText) { return ghostText.parts.some(p => range.containsPosition(new Position(ghostText.lineNumber, p.column))); } @@ -272,7 +283,7 @@ export class InlineCompletionsController extends Disposable { } public shouldShowHoverAtViewZone(viewZoneId: string): boolean { - return this._ghostTextWidget.ownsViewZone(viewZoneId); + return this._ghostTextWidgets.get()[0]?.ownsViewZone(viewZoneId) ?? false; } public hide() { @@ -281,3 +292,27 @@ export class InlineCompletionsController extends Disposable { }); } } + +function convertItemsToStableObservables(items: IObservable, store: DisposableStore): IObservable[]> { + const result = observableValue[]>('result', []); + const innerObservables: ISettableObservable[] = []; + + store.add(autorun(reader => { + const itemsValue = items.read(reader); + + transaction(tx => { + if (itemsValue.length !== innerObservables.length) { + innerObservables.length = itemsValue.length; + for (let i = 0; i < innerObservables.length; i++) { + if (!innerObservables[i]) { + innerObservables[i] = observableValue('item', itemsValue[i]); + } + } + result.set([...innerObservables], tx); + } + innerObservables.forEach((o, i) => o.set(itemsValue[i], tx)); + }); + })); + + return result; +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts index 5229bc11df217..30f622e01fc53 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget.ts @@ -40,7 +40,7 @@ export class InlineCompletionsHintsWidget extends Disposable { private sessionPosition: Position | undefined = undefined; private readonly position = derived(this, reader => { - const ghostText = this.model.read(reader)?.ghostText.read(reader); + const ghostText = this.model.read(reader)?.primaryGhostText.read(reader); if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) { this.sessionPosition = undefined; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index c031bdf51fc05..dcd0dec6cac08 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -18,7 +18,7 @@ import { InlineCompletionContext, InlineCompletionTriggerKind } from 'vs/editor/ import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IFeatureDebounceInformation } from 'vs/editor/common/services/languageFeatureDebounce'; -import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; @@ -41,6 +41,7 @@ export class InlineCompletionsModel extends Disposable { // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. private readonly _selectedInlineCompletionId = observableValue(this, undefined); + private readonly _primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1)); private _isAcceptingPartially = false; public get isAcceptingPartially() { return this._isAcceptingPartially; } @@ -48,8 +49,8 @@ export class InlineCompletionsModel extends Disposable { constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, - public readonly cursorPosition: IObservable, public readonly textModelVersionId: IObservable, + private readonly _positions: IObservable, private readonly _debounceValue: IFeatureDebounceInformation, private readonly _suggestPreviewEnabled: IObservable, private readonly _suggestPreviewMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, @@ -126,7 +127,7 @@ export class InlineCompletionsModel extends Disposable { }); } - const cursorPosition = this.cursorPosition.read(reader); + const cursorPosition = this._primaryPosition.read(reader); const context: InlineCompletionContext = { triggerKind: changeSummary.inlineCompletionTriggerKind, selectedSuggestionInfo: suggestItem?.toSelectedSuggestionInfo(), @@ -157,7 +158,7 @@ export class InlineCompletionsModel extends Disposable { private readonly _filteredInlineCompletionItems = derived(this, reader => { const c = this._source.inlineCompletions.read(reader); if (!c) { return []; } - const cursorPosition = this.cursorPosition.read(reader); + const cursorPosition = this._primaryPosition.read(reader); const filteredCompletions = c.inlineCompletions.filter(c => c.isVisible(this.textModel, cursorPosition, reader)); return filteredCompletions; }); @@ -195,12 +196,14 @@ export class InlineCompletionsModel extends Disposable { public readonly state = derivedOpts<{ suggestItem: SuggestItemInfo | undefined; inlineCompletion: InlineCompletionWithUpdatedRange | undefined; - ghostText: GhostTextOrReplacement; + primaryGhostText: GhostTextOrReplacement; + ghostTexts: readonly GhostTextOrReplacement[]; + edits: SingleTextEdit[]; } | undefined>({ owner: this, equalityComparer: (a, b) => { if (!a || !b) { return a === b; } - return ghostTextOrReplacementEquals(a.ghostText, b.ghostText) + return ghostTextsOrReplacementsEqual(a.ghostTexts, b.ghostTexts) && a.inlineCompletion === b.inlineCompletion && a.suggestItem === b.suggestItem; } @@ -219,12 +222,13 @@ export class InlineCompletionsModel extends Disposable { const editPreviewLength = augmentedCompletion ? augmentedCompletion.edit.text.length - suggestCompletion.text.length : 0; const mode = this._suggestPreviewMode.read(reader); - const cursor = this.cursorPosition.read(reader); - const newGhostText = edit.computeGhostText(model, mode, cursor, editPreviewLength); - - // Show an invisible ghost text to reserve space - const ghostText = newGhostText ?? new GhostText(edit.range.endLineNumber, []); - return { ghostText, inlineCompletion: augmentedCompletion?.completion, suggestItem }; + const positions = this._positions.read(reader); + const edits = [edit, ...this._getSecondaryEdits(this.textModel, positions, edit)]; + const ghostTexts = edits + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], editPreviewLength)) + .filter(isDefined); + const primaryGhostText = ghostTexts[0] ?? new GhostText(edit.range.endLineNumber, []); + return { ghostTexts, primaryGhostText, inlineCompletion: augmentedCompletion?.completion, suggestItem, edits }; } else { if (!this._isActive.read(reader)) { return undefined; } const item = this.selectedInlineCompletion.read(reader); @@ -232,9 +236,13 @@ export class InlineCompletionsModel extends Disposable { const replacement = item.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); - const cursor = this.cursorPosition.read(reader); - const ghostText = replacement.computeGhostText(model, mode, cursor); - return ghostText ? { ghostText, inlineCompletion: item, suggestItem: undefined } : undefined; + const positions = this._positions.read(reader); + const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; + const ghostTexts = edits + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) + .filter(isDefined); + if (!ghostTexts[0]) { return undefined; } + return { ghostTexts, primaryGhostText: ghostTexts[0], inlineCompletion: item, suggestItem: undefined, edits }; } }); @@ -254,13 +262,22 @@ export class InlineCompletionsModel extends Disposable { return augmentedCompletion; } - public readonly ghostText = derivedOpts({ + public readonly ghostTexts = derivedOpts({ + owner: this, + equalityComparer: ghostTextsOrReplacementsEqual + }, reader => { + const v = this.state.read(reader); + if (!v) { return undefined; } + return v.ghostTexts; + }); + + public readonly primaryGhostText = derivedOpts({ owner: this, equalityComparer: ghostTextOrReplacementEquals }, reader => { const v = this.state.read(reader); if (!v) { return undefined; } - return v.ghostText; + return v?.primaryGhostText; }); private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { @@ -289,7 +306,7 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } const completion = state.inlineCompletion.toInlineCompletion(undefined); @@ -306,12 +323,13 @@ export class InlineCompletionsModel extends Disposable { editor.setPosition(completion.snippetInfo.range.getStartPosition(), 'inlineCompletionAccept'); SnippetController2.get(editor)?.insert(completion.snippetInfo.snippet, { undoStopBefore: false }); } else { - const edits = this._getEdits(editor, completion.toSingleTextEdit()); + const edits = state.edits; + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', [ - ...edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), + ...edits.map(edit => EditOperation.replaceMove(edit.range, edit.text)), ...completion.additionalTextEdits ]); - editor.setSelections(edits.editorSelections, 'inlineCompletionAccept'); + editor.setSelections(selections, 'inlineCompletionAccept'); } if (completion.command) { @@ -380,10 +398,10 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { + if (!state || state.primaryGhostText.isEmpty() || !state.inlineCompletion) { return; } - const ghostText = state.ghostText; + const ghostText = state.primaryGhostText; const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -415,9 +433,11 @@ export class InlineCompletionsModel extends Disposable { 0, firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); const singleTextEdit = new SingleTextEdit(replaceRange, newText); - const edits = this._getEdits(editor, singleTextEdit); - editor.executeEdits('inlineSuggestion.accept', edits.edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); - editor.setSelections(edits.editorSelections, 'inlineCompletionPartialAccept'); + const positions = this._positions.get(); + const edits = [singleTextEdit, ...this._getSecondaryEdits(this.textModel, positions, singleTextEdit)]; + const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); + editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); + editor.setSelections(selections, 'inlineCompletionPartialAccept'); } finally { this._isAcceptingPartially = false; } @@ -437,36 +457,22 @@ export class InlineCompletionsModel extends Disposable { } } - private _getEdits(editor: ICodeEditor, completion: SingleTextEdit): { edits: SingleTextEdit[]; editorSelections: Selection[] } { + private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const selections = editor.getSelections() ?? []; - const secondaryPositions = selections.slice(1).map(selection => selection.getPosition()); - const primaryPosition = selections[0].getPosition(); - const textModel = editor.getModel()!; + const primaryPosition = positions[0]; + const secondaryPositions = positions.slice(1); const replacedTextAfterPrimaryCursor = textModel .getLineContent(primaryPosition.lineNumber) - .substring(primaryPosition.column - 1, completion.range.endColumn - 1); - const secondaryEditText = completion.text.substring(primaryPosition.column - completion.range.startColumn); - const edits = [ - new SingleTextEdit(completion.range, completion.text), - ...secondaryPositions.map(pos => { - const textAfterSecondaryCursor = this.textModel - .getLineContent(pos.lineNumber) - .substring(pos.column - 1); - const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - const range = Range.fromPositions(pos, pos.delta(0, l)); - return new SingleTextEdit(range, secondaryEditText); - }) - ]; - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); - const newRanges = sortPerm.inverse().apply(sortedNewRanges); - const editorSelections = newRanges.map(range => Selection.fromPositions(range.getEndPosition())); - - return { - edits, - editorSelections - }; + .substring(primaryPosition.column - 1, primaryEdit.range.endColumn - 1); + const secondaryEditText = primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); + return secondaryPositions.map(pos => { + const textAfterSecondaryCursor = this.textModel + .getLineContent(pos.lineNumber) + .substring(pos.column - 1); + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); + const range = Range.fromPositions(pos, pos.delta(0, l)); + return new SingleTextEdit(range, secondaryEditText); + }); } public handleSuggestAccepted(item: SuggestItemInfo) { @@ -482,3 +488,10 @@ export class InlineCompletionsModel extends Disposable { ); } } + +function getEndPositionsAfterApplying(edits: SingleTextEdit[]): Position[] { + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const newRanges = sortPerm.inverse().apply(sortedNewRanges); + return newRanges.map(range => range.getEndPosition()); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts index c958fcdb6053d..8517f24ec854b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/singleTextEdit.ts @@ -18,6 +18,11 @@ export class SingleTextEdit { ) { } + static equals(first: SingleTextEdit, second: SingleTextEdit) { + return first.range.equalsRange(second.range) && first.text === second.text; + + } + removeCommonPrefix(model: ITextModel, validModelRange?: Range): SingleTextEdit { const modelRange = validModelRange ? this.range.intersectRanges(validModelRange) : this.range; if (!modelRange) { diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts index 2770ea2aa33bf..49e09d4e4a788 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts @@ -83,7 +83,7 @@ export class GhostTextContext extends Disposable { this._register(autorun(reader => { /** @description update */ - const ghostText = model.ghostText.read(reader); + const ghostText = model.primaryGhostText.read(reader); let view: string | undefined; if (ghostText) { view = ghostText.render(this.editor.getValue(), true); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 815ac45fe1d0a..065bf265a0687 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -242,8 +242,8 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { if (!model || !state) { return false; } - const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - const ghostText = state.ghostText.renderForScreenReader(lineText); + const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber); + const ghostText = state.primaryGhostText.renderForScreenReader(lineText); if (!ghostText) { return false; } From 31a7befa311836b14adc6348293ad22f2257998a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:12:46 +0100 Subject: [PATCH 0060/1863] api - some notes on chat agent request, history, and potential for notify (#204740) * api - language model access - rename LanguageModelRequest -> LanguageModelResponse - remove cancel-method - remove event of streams which isn't needed at first * api - some notes on chat agent request, history, and potential for notify --- .../api/common/extHostChatProvider.ts | 20 +++----- .../vscode.proposed.chatAgents2.d.ts | 28 +++++++++++ .../vscode.proposed.chatRequestAccess.d.ts | 49 ++----------------- 3 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index c76f419515b31..d9159a7350a81 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -22,24 +22,20 @@ type LanguageModelData = { class LanguageModelResponseStream { - readonly apiObj: vscode.LanguageModelResponseStream; readonly stream = new AsyncIterableSource(); - constructor(option: number, stream?: AsyncIterableSource) { + constructor( + readonly option: number, + stream?: AsyncIterableSource + ) { this.stream = stream ?? new AsyncIterableSource(); - const that = this; - this.apiObj = { - option: option, - response: that.stream.asyncIterable - }; } } class LanguageModelRequest { - readonly apiObject: vscode.LanguageModelRequest; + readonly apiObject: vscode.LanguageModelResponse; - private readonly _onDidStart = new Emitter(); private readonly _responseStreams = new Map(); private readonly _defaultStream = new AsyncIterableSource(); private _isDone: boolean = false; @@ -51,9 +47,8 @@ class LanguageModelRequest { const that = this; this.apiObject = { result: promise, - response: that._defaultStream.asyncIterable, - onDidStartResponseStream: that._onDidStart.event, - cancel() { cts.cancel(); }, + stream: that._defaultStream.asyncIterable, + // responses: AsyncIterable[] // FUTURE responses per N }; promise.finally(() => { @@ -81,7 +76,6 @@ class LanguageModelRequest { res = new LanguageModelResponseStream(fragment.index); } this._responseStreams.set(fragment.index, res); - this._onDidStart.fire(res.apiObj); } res.stream.emitOne(fragment.part); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 9864b23c7e2db..253347776e4da 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -12,6 +12,7 @@ declare module 'vscode' { /** * The request that was sent to the chat agent. */ + // TODO@API make this optional? Allow for response without request? request: ChatAgentRequest; /** @@ -25,11 +26,27 @@ declare module 'vscode' { result: ChatAgentResult2; } + // TODO@API class + // export interface ChatAgentResponse { + // /** + // * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + // */ + // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + + // /** + // * The result that was received from the chat agent. + // */ + // result: ChatAgentResult2; + // } + export interface ChatAgentContext { /** * All of the chat messages so far in the current chat session. */ history: ChatAgentHistoryEntry[]; + + // TODO@API have "turns" + // history2: (ChatAgentRequest | ChatAgentResponse)[]; } /** @@ -235,6 +252,14 @@ declare module 'vscode' { */ followupProvider?: ChatAgentFollowupProvider; + + // TODO@ + // notify(request: ChatResponsePart, reference: string): boolean; + + // TODO@API + // clear NEVER happens + // onDidClearResult(value: TResult): void; + /** * When the user clicks this agent in `/help`, this text will be submitted to this subCommand */ @@ -276,6 +301,9 @@ declare module 'vscode' { subCommand?: string; variables: Record; + + // TODO@API argumented prompt, reverse order! + // variables2: { start:number, length:number, values: ChatVariableValue[]}[] } export interface ChatAgentResponseStream { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 733e985dd9817..1134553728c66 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,57 +5,16 @@ declare module 'vscode' { - export interface LanguageModelResponseStream { - - /** - * The response stream. - */ - readonly response: AsyncIterable; - - /** - * The variant of multiple responses. This is used to disambiguate between multiple - * response streams when having asked for multiple response options - */ - readonly option: number; - } - - // TODO@API NAME: LanguageModelResponse (depends on having cancel or not) - export interface LanguageModelRequest { + export interface LanguageModelResponse { /** * The overall result of the request which represents failure or success * but _not_ the actual response or responses */ // TODO@API define this type! - result: Thenable; + result: Thenable; - /** - * The _default response_ stream. This is the stream of the first response option - * receiving data. - * - * Usually there is only one response option and this stream is more convienient to use - * than the {@link onDidStartResponseStream `onDidStartResponseStream`} event. - */ - // TODO@API NAME: responseStream - response: AsyncIterable; - - /** - * An event that fires whenever a new response option is available. The response - * itself is a stream of the actual response. - * - * *Note* that the first time this event fires, the {@link LanguageModelResponseStream.response response stream} - * is the same as the {@link response `default response stream`}. - * - * *Note* that unless requested there is only one response option, so this event will only fire - * once. - */ - onDidStartResponseStream: Event; - - /** - * Cancel this request. - */ - // TODO@API remove this? We pass a token to makeRequest call already - cancel(): void; + stream: AsyncIterable; } /** @@ -90,7 +49,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelRequest; + makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { From f618bf3befda9b9c5bade12bc77cb33dff8d1284 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:16:28 +0100 Subject: [PATCH 0061/1863] Custom hover for editor tabs (#204742) --- .../browser/parts/editor/editorTabsControl.ts | 46 ++++++++++++++++++- .../parts/editor/multiEditorTabsControl.ts | 15 +++--- .../parts/editor/singleEditorTabsControl.ts | 9 +--- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 3d202d8d48b1f..058d0a91a9202 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -25,7 +25,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier, Verbosity } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -44,6 +44,11 @@ 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 { MarkdownString, MarkdownStringTextNewlineStyle } from 'vs/base/common/htmlContent'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -135,7 +140,9 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IDecorationsService private readonly decorationsService: IDecorationsService, + @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -444,6 +451,41 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } + protected getHoverTitle(editor: EditorInput): ITooltipMarkdownString { + const title = editor.getTitle(Verbosity.LONG); + const markdown = new MarkdownString(title); + + if (editor.resource) { + const decoration = this.decorationsService.getDecoration(editor.resource, false); + if (decoration) { + const decorations = decoration.tooltip.split('• '); + const decorationString = `• ${decorations.join('\n• ')}`; + + markdown.appendText('\n', MarkdownStringTextNewlineStyle.Paragraph); + markdown.appendText(decorationString, MarkdownStringTextNewlineStyle.Break); + } + } + + return { + markdown, + markdownNotSupportedFallback: title + }; + } + + protected getHoverDelegate(): IHoverDelegate { + return { + delay: 500, + showHover: options => { + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + } + protected updateTabHeight(): void { this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`); } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 4372fd674a490..d79a5be26c35f 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -56,6 +56,8 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -149,9 +151,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IPathService private readonly pathService: IPathService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IDecorationsService decorationsService: IDecorationsService, + @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, decorationsService, hoverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -793,7 +797,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.appendChild(tabBorderTopContainer); // Tab Editor Label - const editorLabel = this.tabResourceLabels.create(tabContainer); + const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverDelegate: this.getHoverDelegate() }); // Tab Actions const tabActionsContainer = document.createElement('div'); @@ -1469,14 +1473,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.setAttribute('aria-description', ''); } - const title = tabLabel.title || ''; - tabContainer.title = title; - // Label tabLabelWidget.setResource( { name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, { - title, + title: this.getHoverTitle(editor), extraClasses: coalesce(['tab-label', fileDecorationBadges ? 'tab-label-has-badge' : undefined].concat(editor.getLabelExtraClasses())), italic: !this.tabsModel.isPinned(editor), forceLabel, diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 810dbc7308343..c33305205c257 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -51,7 +51,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { titleContainer.appendChild(labelContainer); // Editor Label - this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, undefined)).element; + this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, { hoverDelegate: this.getHoverDelegate() })).element; this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs @@ -304,11 +304,6 @@ export class SingleEditorTabsControl extends EditorTabsControl { description = editor.getDescription(this.getVerbosity(labelFormat)) || ''; } - let title = editor.getTitle(Verbosity.LONG); - if (description === title) { - title = ''; // dont repeat what is already shown - } - editorLabel.setResource( { resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }), @@ -316,7 +311,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { description }, { - title, + title: this.getHoverTitle(editor), italic: !isEditorPinned, extraClasses: ['single-tab', 'title-label'].concat(editor.getLabelExtraClasses()), fileDecorations: { From 884acabd703fee0ef844794210798567f85425d1 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 10:28:40 -0700 Subject: [PATCH 0062/1863] When receiving an unexpected status code also add information surrounding the headers and body (#204741) * When receiving an unexpected status code also add information surrounding the headers and body * Add compiled file --- build/azure-pipelines/common/publish.js | 2 +- build/azure-pipelines/common/publish.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index d035de8bb84a5..bc7d500d450da 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -79,7 +79,7 @@ class ProvisionService { }; const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); if (!res.ok || res.status < 200 || res.status >= 500) { - throw new Error(`Unexpected status code: ${res.status}`); + throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); } diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 6b20492c87c08..6048c19f7a0e3 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -113,7 +113,7 @@ class ProvisionService { const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); if (!res.ok || res.status < 200 || res.status >= 500) { - throw new Error(`Unexpected status code: ${res.status}`); + throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); From 49001e5237910cfdae20dde5219b3c6572bd99f5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:43:24 +0100 Subject: [PATCH 0063/1863] know for whom a LM request is made (#204744) --- .../workbench/api/browser/mainThreadChatProvider.ts | 6 +++--- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostChatProvider.ts | 11 ++++++----- src/vs/workbench/contrib/chat/common/chatProvider.ts | 8 ++++---- src/vscode-dts/vscode.proposed.chatProvider.d.ts | 2 ++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 24ce8d39797ee..7c6534079e21e 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -48,11 +48,11 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { const registration = this._chatProviderService.registerChatResponseProvider(identifier, { metadata, - provideChatResponse: async (messages, options, progress, token) => { + provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; this._pendingProgress.set(requestId, progress); try { - await this._proxy.$provideLanguageModelResponse(handle, requestId, messages, options, token); + await this._proxy.$provideLanguageModelResponse(handle, requestId, from, messages, options, token); } finally { this._pendingProgress.delete(requestId); } @@ -80,7 +80,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); - const task = this._chatProviderService.fetchChatResponse(providerId, messages, options, new Progress(value => { + const task = this._chatProviderService.fetchChatResponse(providerId, extension, messages, options, new Progress(value => { this._proxy.$handleResponseFragment(requestId, value); }), token); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e8b75cbcebab8..a34f7a83238fa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1183,7 +1183,7 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; - $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; + $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index d9159a7350a81..d47cf51900612 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -121,7 +121,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { }); } - async $provideLanguageModelResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { + async $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise { const data = this._languageModels.get(handle); if (!data) { return; @@ -134,14 +134,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + if (data.provider.provideLanguageModelResponse) { + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } else { + return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); + } } //#region --- making request - - - $updateLanguageModels(data: { added?: string[] | undefined; removed?: string[] | undefined }): void { const added: string[] = []; const removed: string[] = []; diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 88349952e4e48..38be72eb51e49 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -35,7 +35,7 @@ export interface IChatResponseProviderMetadata { export interface IChatResponseProvider { metadata: IChatResponseProviderMetadata; - provideChatResponse(messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + provideChatResponse(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export const IChatProviderService = createDecorator('chatProviderService'); @@ -52,7 +52,7 @@ export interface IChatProviderService { registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; - fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } export class ChatProviderService implements IChatProviderService { @@ -89,11 +89,11 @@ export class ChatProviderService implements IChatProviderService { }); } - fetchChatResponse(identifier: string, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { const provider = this._providers.get(identifier); if (!provider) { throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); } - return provider.provideChatResponse(messages, options, progress, token); + return provider.provideChatResponse(messages, from, options, progress, token); } } diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 3ff89cca683ee..1d218bc8e004d 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -17,6 +17,8 @@ declare module 'vscode' { */ export interface ChatResponseProvider { provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; + + provideLanguageModelResponse?(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { From 249a9514f273478950cdfd514a94f06f9fe0c3d9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 8 Feb 2024 18:59:55 +0100 Subject: [PATCH 0064/1863] add ownership file for vscode-dts (#204746) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..8da51487c846f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# ensure the API police is aware of changes to the vscode-dts file +# this is only about the final API, not about proposed API changes +src/vscode-dts/vscode.d.ts @jrieken @mjbvz From cfb737085554c4d588f51e5f5f4010784b4595ac Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 9 Feb 2024 03:02:19 +0900 Subject: [PATCH 0065/1863] fix: inheriting NODE_OPTIONS on macOS with integrated terminal (#204682) --- resources/darwin/bin/code.sh | 4 ++++ .../terminal/common/terminalEnvironment.ts | 22 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index 1d66515c2c73b..de5c3bfcab0f4 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -31,5 +31,9 @@ fi CONTENTS="$APP_PATH/Contents" ELECTRON="$CONTENTS/MacOS/Electron" CLI="$CONTENTS/Resources/app/out/cli.js" +export VSCODE_NODE_OPTIONS=$NODE_OPTIONS +export VSCODE_NODE_REPL_EXTERNAL_MODULE=$NODE_REPL_EXTERNAL_MODULE +unset NODE_OPTIONS +unset NODE_REPL_EXTERNAL_MODULE ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 22aedd0758cbe..27ccb9cb61fe0 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -13,7 +13,7 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; import { IShellLaunchConfig, ITerminalBackend, ITerminalEnvironment, TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { IProcessEnvironment, isWindows, language, OperatingSystem } from 'vs/base/common/platform'; +import { IProcessEnvironment, isWindows, isMacintosh, language, OperatingSystem } from 'vs/base/common/platform'; import { escapeNonWindowsPath, sanitizeCwd } from 'vs/platform/terminal/common/terminalEnvironment'; import { isString } from 'vs/base/common/types'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -269,6 +269,26 @@ export async function createTerminalEnvironment( } } + // Workaround for https://github.com/microsoft/vscode/issues/204005 + // We should restore the following environment variables when a user + // launches the application using the CLI so that integrated terminal + // can still inherit these variables. + // We are not bypassing the restrictions implied in https://github.com/electron/electron/pull/40770 + // since this only affects integrated terminal and not the application itself. + if (isMacintosh) { + // Restore NODE_OPTIONS if it was set + if (env['VSCODE_NODE_OPTIONS']) { + env['NODE_OPTIONS'] = env['VSCODE_NODE_OPTIONS']; + delete env['VSCODE_NODE_OPTIONS']; + } + + // Restore NODE_REPL_EXTERNAL_MODULE if it was set + if (env['VSCODE_NODE_REPL_EXTERNAL_MODULE']) { + env['NODE_REPL_EXTERNAL_MODULE'] = env['VSCODE_NODE_REPL_EXTERNAL_MODULE']; + delete env['VSCODE_NODE_REPL_EXTERNAL_MODULE']; + } + } + // Sanitize the environment, removing any undesirable VS Code and Electron environment // variables sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI'); From 4bc4f1adaf04dfeb757d3c897e2a14e4f1874b0b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:48:55 -0800 Subject: [PATCH 0066/1863] Add zsh and fish as valid shell code block languages This will make the run in terminal button move out of the overflow menu --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 2d616c7be6893..0076b4eb1f8ae 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -382,8 +382,10 @@ export function registerChatCodeBlockActions() { }); const shellLangIds = [ + 'fish', 'powershell', - 'shellscript' + 'shellscript', + 'zsh' ]; registerAction2(class RunInTerminalAction extends ChatCodeBlockAction { constructor() { From f1f5d07d14472f954553049209e8fdb0afe0683e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 8 Feb 2024 11:00:23 -0800 Subject: [PATCH 0067/1863] debug: restore default notification priority for progress (#204750) Fixes #204334 --- src/vs/workbench/contrib/debug/browser/debugProgress.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugProgress.ts b/src/vs/workbench/contrib/debug/browser/debugProgress.ts index acf23c9708090..c87b19974f5b7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugProgress.ts +++ b/src/vs/workbench/contrib/debug/browser/debugProgress.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugService, VIEWLET_ID, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService, IDebugSession, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { NotificationPriority } from 'vs/platform/notification/common/notification'; export class DebugProgressContribution implements IWorkbenchContribution { @@ -45,7 +44,6 @@ export class DebugProgressContribution implements IWorkbenchContribution { location: ProgressLocation.Notification, title: progressStartEvent.body.title, cancellable: progressStartEvent.body.cancellable, - priority: NotificationPriority.SILENT, source, delay: 500 }, progressStep => { From 2033eae5af6a253b580753f9860c9b7242f2954b Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 12:00:44 -0700 Subject: [PATCH 0068/1863] Allow publishing retry (#204758) * Allow publishing retry * Update build/azure-pipelines/common/publish.ts Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> * Update build/azure-pipelines/common/publish.ts Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> * Compile --------- Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> --- build/azure-pipelines/common/publish.js | 11 ++++++++++- build/azure-pipelines/common/publish.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index bc7d500d450da..c7c0efe9ba0e0 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -41,6 +41,9 @@ class Temp { } } } +function isCreateProvisionedFilesErrorResponse(response) { + return response?.ErrorDetails !== undefined; +} class ProvisionService { log; accessToken; @@ -63,6 +66,10 @@ class ProvisionService { }); this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); const res = await (0, retry_1.retry)(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); + if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { + this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); + return; + } if (!res.IsSuccess) { throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); } @@ -78,7 +85,9 @@ class ProvisionService { } }; const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - if (!res.ok || res.status < 200 || res.status >= 500) { + // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless + // Otherwise log the text body and headers. We do text because some responses are not JSON. + if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } return await res.json(); diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 6048c19f7a0e3..90da4fd5235aa 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -69,6 +69,10 @@ interface CreateProvisionedFilesErrorResponse { type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; +function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { + return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails !== undefined; +} + class ProvisionService { constructor( @@ -93,6 +97,11 @@ class ProvisionService { this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); const res = await retry(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); + if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { + this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); + return; + } + if (!res.IsSuccess) { throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); } @@ -112,7 +121,10 @@ class ProvisionService { const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - if (!res.ok || res.status < 200 || res.status >= 500) { + + // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless + // Otherwise log the text body and headers. We do text because some responses are not JSON. + if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); } From 53031979503c9e97552f2bb5bf185d41bb977bc3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 12:37:39 -0700 Subject: [PATCH 0069/1863] Update type guard (#204763) --- build/azure-pipelines/common/publish.js | 2 +- build/azure-pipelines/common/publish.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index c7c0efe9ba0e0..e6b24921ac10e 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -42,7 +42,7 @@ class Temp { } } function isCreateProvisionedFilesErrorResponse(response) { - return response?.ErrorDetails !== undefined; + return response?.ErrorDetails?.Code !== undefined; } class ProvisionService { log; diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 90da4fd5235aa..f144a7be7939d 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -70,7 +70,7 @@ interface CreateProvisionedFilesErrorResponse { type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { - return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails !== undefined; + return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails?.Code !== undefined; } class ProvisionService { From fcaef74d2ef94479f4924d1f5aa7647d4281815f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 20:43:28 +0100 Subject: [PATCH 0070/1863] Fix smoke tests (#204764) fix smoke tests --- test/automation/src/extensions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index aa59f7cd7cf31..1d0f97dadf346 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -41,7 +41,7 @@ export class Extensions extends Viewlet { } async closeExtension(title: string): Promise { - await this.code.waitAndClick(`.tabs-container div.tab[title="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); } async installExtension(id: string, waitUntilEnabled: boolean): Promise { From 56f9e01216f191f7d6b8e32d32b62c816e5a6f77 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 12:05:19 -0800 Subject: [PATCH 0071/1863] Improvements to extensions features * - show all runtime information in runtime status feature - show runtime feature information in runtime extensions editor - filter extensions by feature in view --- .../api/browser/mainThreadChatProvider.ts | 27 ++-- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostChatAgents2.ts | 5 + .../contrib/chat/browser/chat.contribution.ts | 64 +------- .../contrib/chat/common/chatService.ts | 1 - .../abstractRuntimeExtensionsEditor.ts | 25 ++- .../browser/extensionFeaturesTab.ts | 153 +++++++++++++++--- .../extensions/browser/extensionsViews.ts | 38 ++++- .../browser/media/extensionEditor.css | 2 +- .../runtimeExtensionsEditor.ts | 4 +- .../common/extensionFeatures.ts | 9 +- .../common/abstractExtensionService.ts | 77 ++------- 12 files changed, 236 insertions(+), 171 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 7c6534079e21e..76001d0496008 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -5,13 +5,14 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgress, Progress } from 'vs/platform/progress/common/progress'; +import { Registry } from 'vs/platform/registry/common/platform'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { CHAT_FEATURE_ID } from 'vs/workbench/contrib/chat/common/chatService'; -import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) @@ -31,13 +32,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); - this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); - this._store.add(_extensionFeaturesManagementService.onDidChangeEnablement(e => { - if (e.featureId === CHAT_FEATURE_ID) { - this._proxy.$updateAccesslist(_extensionFeaturesManagementService.getEnablementData(CHAT_FEATURE_ID)); - } - })); } dispose(): void { @@ -46,7 +41,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { - const registration = this._chatProviderService.registerChatResponseProvider(identifier, { + const dipsosables = new DisposableStore(); + dipsosables.add(this._chatProviderService.registerChatResponseProvider(identifier, { metadata, provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; @@ -57,8 +53,15 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._pendingProgress.delete(requestId); } } - }); - this._providerRegistrations.set(handle, registration); + })); + dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ + id: `lm-${identifier}`, + label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), + access: { + canToggle: false, + }, + })); + this._providerRegistrations.set(handle, dipsosables); } async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise { @@ -70,7 +73,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - const access = await this._extensionFeaturesManagementService.getAccess(extension, CHAT_FEATURE_ID, justification); + const access = await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`, justification); if (!access) { return undefined; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index db680538d6b24..5b672c3356a56 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); - const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index dbcce3f64d7a1..d83e50c844245 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -15,6 +15,7 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -153,6 +154,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { constructor( mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); @@ -177,6 +179,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } + this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); try { const convertedHistory = await this.prepareHistory(agent, request, context); @@ -211,6 +215,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); + this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: false }]); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index cc1983fb53b29..5a9d5ef5cf8e8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isMacintosh } from 'vs/base/common/platform'; -import { Emitter } from 'vs/base/common/event'; import * as nls from 'vs/nls'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -31,7 +30,7 @@ import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget' import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { CHAT_FEATURE_ID, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -59,9 +58,6 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionFeatureMarkdownRenderer, Extensions as ExtensionFeaturesExtensions, IRenderedData, IExtensionFeaturesRegistry, IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; -import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -287,62 +283,6 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } } -class ChatFeatureMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { - - readonly type = 'markdown'; - - constructor( - @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, - ) { - super(); - } - - shouldRender(manifest: IExtensionManifest): boolean { - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); - return !!accessData; - } - - render(manifest: IExtensionManifest): IRenderedData { - const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(e => { - if (ExtensionIdentifier.equals(e.extension, extensionId) && e.featureId === CHAT_FEATURE_ID) { - emitter.fire(this.getMarkdownData(extensionId)); - } - })); - return { - data: this.getMarkdownData(extensionId), - onDidChange: emitter.event, - dispose: () => { disposables.dispose(); } - }; - } - - private getMarkdownData(extensionId: ExtensionIdentifier): IMarkdownString { - const markdown = new MarkdownString(); - const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, CHAT_FEATURE_ID); - if (accessData && accessData.totalCount) { - if (accessData.current) { - markdown.appendMarkdown(nls.localize('requests count session', "Requests (Session) : `{0}`", accessData.current.count)); - markdown.appendText('\n'); - } - markdown.appendMarkdown(nls.localize('requests count total', "Requests (Overall): `{0}`", accessData.totalCount)); - } - return markdown; - } -} - -Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: CHAT_FEATURE_ID, - label: nls.localize('chat', "Chat"), - description: nls.localize('chatFeatureDescription', "Allows the extension to make requests to the Large Language Model (LLM)."), - access: { - canToggle: false, - }, - renderer: new SyncDescriptor(ChatFeatureMarkdowneRenderer), -}); - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribution, WorkbenchPhase.BlockStartup); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 5b4e36308a7a8..9a9c1196e72d0 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -311,4 +311,3 @@ export interface IChatService { } export const KEYWORD_ACTIVIATION_SETTING_ID = 'accessibility.voice.keywordActivation'; -export const CHAT_FEATURE_ID = 'chat'; diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index fc0f45125f8c9..413da693a6a74 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -11,6 +11,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { fromNow } from 'vs/base/common/date'; import { memoize } from 'vs/base/common/decorators'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -25,16 +26,19 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, IExtensionDescription } fr import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { errorIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/common/runtimeExtensionsInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { DefaultIconPath, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { IExtensionHostProfile, IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; @@ -83,6 +87,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { @ILabelService private readonly _labelService: ILabelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IClipboardService private readonly _clipboardService: IClipboardService, + @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { super(AbstractRuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -91,6 +96,7 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { this._updateSoon = this._register(new RunOnceScheduler(() => this._updateExtensions(), 200)); this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); + this._register(this._extensionFeaturesManagementService.onDidChangeAccessData(() => this._updateSoon.schedule())); this._updateExtensions(); } @@ -400,6 +406,23 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { data.msgContainer.appendChild(el); } + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + for (const feature of features) { + const accessData = this._extensionFeaturesManagementService.getAccessData(element.description.identifier, feature.id); + if (accessData) { + const status = accessData?.current?.status; + if (status) { + data.msgContainer.appendChild($('span', undefined, `${feature.label}: `)); + data.msgContainer.appendChild($('span', undefined, ...renderLabelWithIcons(`$(${status.severity === Severity.Error ? errorIcon.id : warningIcon.id}) ${status.message}`))); + } + if (accessData?.current) { + const element = $('span', undefined, nls.localize('requests count', "{0} Requests: {1} (Session)", feature.label, accessData.current.count)); + element.title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + data.msgContainer.appendChild(element); + } + } + } + if (element.profileInfo) { data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; } else { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 71a1018af4da5..a778ba34bdbd3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -5,10 +5,10 @@ import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { $, append, clearNode } from 'vs/base/browser/dom'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; -import { IExtensionFeatureDescriptor, Extensions, IExtensionFeaturesRegistry, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeatureTableRenderer, IExtensionFeatureMarkdownRenderer, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; +import { IExtensionFeatureDescriptor, Extensions, IExtensionFeaturesRegistry, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeatureTableRenderer, IExtensionFeatureMarkdownRenderer, ITableData, IRenderedData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; @@ -19,7 +19,7 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { Button } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles, defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; @@ -32,13 +32,119 @@ import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { OS } from 'vs/base/common/platform'; -import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Color } from 'vs/base/common/color'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Codicon } from 'vs/base/common/codicons'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { fromNow } from 'vs/base/common/date'; + +class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { + + static readonly ID = 'runtimeStatus'; + readonly type = 'markdown'; + + constructor( + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, + ) { + super(); + } + + shouldRender(manifest: IExtensionManifest): boolean { + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + if (this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { + return !!manifest.main || !!manifest.browser; + } + return !!manifest.activationEvents; + } + + render(manifest: IExtensionManifest): IRenderedData { + const disposables = new DisposableStore(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const emitter = disposables.add(new Emitter()); + disposables.add(this.extensionService.onDidChangeExtensionsStatus(e => { + if (e.some(extension => ExtensionIdentifier.equals(extension, extensionId))) { + emitter.fire(this.getActivationData(manifest)); + } + })); + disposables.add(this.extensionFeaturesManagementService.onDidChangeAccessData(e => emitter.fire(this.getActivationData(manifest)))); + return { + onDidChange: emitter.event, + data: this.getActivationData(manifest), + dispose: () => disposables.dispose() + }; + } + + private getActivationData(manifest: IExtensionManifest): IMarkdownString { + const data = new MarkdownString(); + const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); + const status = this.extensionService.getExtensionsStatus()[extensionId.value]; + if (this.extensionService.extensions.some(extension => ExtensionIdentifier.equals(extension.identifier, extensionId))) { + data.appendMarkdown(`### ${localize('activation', "Activation")}\n\n`); + if (status.activationTimes) { + if (status.activationTimes.activationReason.startup) { + data.appendMarkdown(`Activated on Startup: \`${status.activationTimes.activateCallTime}ms\``); + } else { + data.appendMarkdown(`Activated by \`${status.activationTimes.activationReason.activationEvent}\` event: \`${status.activationTimes.activateCallTime}ms\``); + } + } else { + data.appendMarkdown('Not yet activated'); + } + if (status.runtimeErrors.length) { + data.appendMarkdown(`\n ### ${localize('uncaught errors', "Uncaught Errors ({0})", status.runtimeErrors.length)}\n`); + for (const error of status.runtimeErrors) { + data.appendMarkdown(`$(${Codicon.error.id}) ${getErrorMessage(error)}\n\n`); + } + } + if (status.messages.length) { + data.appendMarkdown(`\n ### ${localize('messaages', "Messages ({0})", status.messages.length)}\n`); + for (const message of status.messages) { + data.appendMarkdown(`$(${(message.type === Severity.Error ? Codicon.error : message.type === Severity.Warning ? Codicon.warning : Codicon.info).id}) ${message.message}\n\n`); + } + } + } + const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); + for (const feature of features) { + const accessData = this.extensionFeaturesManagementService.getAccessData(extensionId, feature.id); + if (accessData) { + data.appendMarkdown(`\n ### ${feature.label}\n\n`); + const status = accessData?.current?.status; + if (status) { + if (status?.severity === Severity.Error) { + data.appendMarkdown(`$(${errorIcon.id}) ${status.message}\n\n`); + } + if (status?.severity === Severity.Warning) { + data.appendMarkdown(`$(${warningIcon.id}) ${status.message}\n\n`); + } + } + if (accessData?.totalCount) { + if (accessData.current) { + data.appendMarkdown(`${localize('last request', "Last Request: `{0}`", fromNow(accessData.current.lastAccessed, true, true))}\n\n`); + data.appendMarkdown(`${localize('requests count session', "Requests (Session) : `{0}`", accessData.current.count)}\n\n`); + } + data.appendMarkdown(`${localize('requests count total', "Requests (Overall): `{0}`", accessData.totalCount)}\n\n`); + } + } + } + return data; + } +} + interface ILayoutParticipant { layout(height?: number, width?: number): void; } +const runtimeStatusFeature = { + id: RuntimeStatusMarkdownRenderer.ID, + label: localize('runtime', "Runtime Status"), + access: { + canToggle: false + }, + renderer: new SyncDescriptor(RuntimeStatusMarkdownRenderer), +}; + export class ExtensionFeaturesTab extends Themable { readonly domNode: HTMLElement; @@ -162,17 +268,24 @@ export class ExtensionFeaturesTab extends Themable { } private getFeatures(): IExtensionFeatureDescriptor[] { - const features = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeatures(); - return features.filter(feature => { - const renderer = this.getRenderer(feature); - const shouldRender = renderer.shouldRender(this.manifest); - renderer.dispose(); - return shouldRender; - }).sort((a, b) => a.label.localeCompare(b.label)); + const features = Registry.as(Extensions.ExtensionFeaturesRegistry) + .getExtensionFeatures().filter(feature => { + const renderer = this.getRenderer(feature); + const shouldRender = renderer?.shouldRender(this.manifest); + renderer?.dispose(); + return shouldRender; + }).sort((a, b) => a.label.localeCompare(b.label)); + + const renderer = this.getRenderer(runtimeStatusFeature); + if (renderer?.shouldRender(this.manifest)) { + features.splice(0, 0, runtimeStatusFeature); + } + renderer?.dispose(); + return features; } - private getRenderer(feature: IExtensionFeatureDescriptor): IExtensionFeatureRenderer { - return this.instantiationService.createInstance(feature.renderer); + private getRenderer(feature: IExtensionFeatureDescriptor): IExtensionFeatureRenderer | undefined { + return feature.renderer ? this.instantiationService.createInstance(feature.renderer) : undefined; } } @@ -210,7 +323,7 @@ class ExtensionFeatureItemRenderer implements IListRenderer { if (ExtensionIdentifier.equals(extension, this.extensionId) && featureId === element.id) { @@ -319,11 +432,13 @@ class ExtensionFeatureView extends Disposable { } const featureContentElement = append(bodyContent, $('.feature-content')); - const renderer = this.instantiationService.createInstance(this.feature.renderer); - if (renderer.type === 'table') { - this.renderTableData(featureContentElement, renderer); - } else if (renderer.type === 'markdown') { - this.renderMarkdownData(featureContentElement, renderer); + if (this.feature.renderer) { + const renderer = this.instantiationService.createInstance(this.feature.renderer); + if (renderer.type === 'table') { + this.renderTableData(featureContentElement, renderer); + } else if (renderer.type === 'markdown') { + this.renderMarkdownData(featureContentElement, renderer); + } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index cd8a1b647cdf5..adcd04b081ff0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -36,7 +36,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction, Action, Separator, ActionRunner } from 'vs/base/common/actions'; -import { ExtensionIdentifierMap, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; @@ -55,6 +55,8 @@ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { ILogService } from 'vs/platform/log/common/log'; import { isOfflineError } from 'vs/base/parts/request/common/request'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IExtensionFeatureRenderer, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; export const NONE_CATEGORY = 'none'; @@ -145,6 +147,7 @@ export class ExtensionsListView extends ViewPane { @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly logService: ILogService ) { super({ @@ -442,6 +445,10 @@ export class ExtensionsListView extends ViewPane { extensions = this.filterRecentlyUpdatedExtensions(local, query, options); } + else if (/@feature:/i.test(query.value)) { + extensions = this.filterExtensionsByFeature(local, query, options); + } + return { extensions, canIncludeInstalledExtensions }; } @@ -677,6 +684,23 @@ export class ExtensionsListView extends ViewPane { return this.sortExtensions(result, options); } + private filterExtensionsByFeature(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { + const value = query.value.replace(/@feature:/g, '').trim().toLowerCase(); + const featureId = value.split(' ')[0]; + const feature = Registry.as(Extensions.ExtensionFeaturesRegistry).getExtensionFeature(featureId); + if (!feature) { + return []; + } + const renderer = feature.renderer ? this.instantiationService.createInstance(feature.renderer) : undefined; + const result = local.filter(e => { + if (!e.local) { + return false; + } + return renderer?.shouldRender(e.local.manifest) || this.extensionFeaturesManagementService.getAccessData(new ExtensionIdentifier(e.identifier.id), featureId); + }); + return this.sortExtensions(result, options); + } + private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined { const oldExtensions = [...extensions]; const findPreviousExtensionIndex = (from: number): number => { @@ -1074,7 +1098,8 @@ export class ExtensionsListView extends ViewPane { || this.isSearchWorkspaceUnsupportedExtensionsQuery(query) || this.isSearchRecentlyUpdatedQuery(query) || this.isSearchExtensionUpdatesQuery(query) - || this.isSortInstalledExtensionsQuery(query, sortBy); + || this.isSortInstalledExtensionsQuery(query, sortBy) + || this.isFeatureExtensionsQuery(query); } static isSearchBuiltInExtensionsQuery(query: string): boolean { @@ -1098,7 +1123,7 @@ export class ExtensionsListView extends ViewPane { } static isSearchInstalledExtensionsQuery(query: string): boolean { - return /@installed\s./i.test(query); + return /@installed\s./i.test(query) || this.isFeatureExtensionsQuery(query); } static isOutdatedExtensionsQuery(query: string): boolean { @@ -1169,6 +1194,10 @@ export class ExtensionsListView extends ViewPane { return /@sort:updateDate/i.test(query); } + static isFeatureExtensionsQuery(query: string): boolean { + return /@feature:/i.test(query); + } + override focus(): void { super.focus(); if (!this.list) { @@ -1283,12 +1312,13 @@ export class StaticQueryExtensionsView extends ExtensionsListView { @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService logService: ILogService ) { super(options, viewletViewOptions, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, extensionRecommendationsService, telemetryService, configurationService, contextService, extensionManagementServerService, extensionManifestPropertiesService, extensionManagementService, workspaceService, productService, contextKeyService, viewDescriptorService, openerService, - preferencesService, storageService, workspaceTrustManagementService, extensionEnablementService, layoutService, logService); + preferencesService, storageService, workspaceTrustManagementService, extensionEnablementService, layoutService, extensionFeaturesManagementService, logService); } override show(): Promise> { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index a54bd50bf16c1..93b10e5bb6e41 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -880,5 +880,5 @@ } .extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-content.markdown .codicon { - vertical-align: bottom; + vertical-align: sub; } diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts index ad8794fb8c6a0..11dc035fb5b92 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/runtimeExtensionsEditor.ts @@ -29,6 +29,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; +import { IExtensionFeaturesManagementService } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -77,8 +78,9 @@ export class RuntimeExtensionsEditor extends AbstractRuntimeExtensionsEditor { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IClipboardService clipboardService: IClipboardService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, + @IExtensionFeaturesManagementService extensionFeaturesManagementService: IExtensionFeaturesManagementService, ) { - super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService); + super(telemetryService, themeService, contextKeyService, extensionsWorkbenchService, extensionService, notificationService, contextMenuService, instantiationService, storageService, labelService, environmentService, clipboardService, extensionFeaturesManagementService); this._profileInfo = this._extensionHostProfileService.lastProfile; this._extensionsHostRecorded = CONTEXT_EXTENSION_HOST_PROFILE_RECORDED.bindTo(contextKeyService); this._profileSessionState = CONTEXT_PROFILE_SESSION_STATE.bindTo(contextKeyService); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index becb8e21f3708..e72d5eee7353d 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -54,12 +54,12 @@ export interface IExtensionFeatureDescriptor { readonly requireUserConsent?: boolean; readonly extensionsList?: IStringDictionary; }; - readonly renderer: SyncDescriptor; + readonly renderer?: SyncDescriptor; } export interface IExtensionFeaturesRegistry { - registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void; + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): IDisposable; getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined; getExtensionFeatures(): ReadonlyArray; } @@ -93,11 +93,14 @@ class ExtensionFeaturesRegistry implements IExtensionFeaturesRegistry { private readonly extensionFeatures = new Map(); - registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): void { + registerExtensionFeature(descriptor: IExtensionFeatureDescriptor): IDisposable { if (this.extensionFeatures.has(descriptor.id)) { throw new Error(`Extension feature with id '${descriptor.id}' already exists`); } this.extensionFeatures.set(descriptor.id, descriptor); + return { + dispose: () => this.extensionFeatures.delete(descriptor.id) + }; } getExtensionFeature(id: string): IExtensionFeatureDescriptor | undefined { diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index c44dc4d24d9d6..2d93e41d0f083 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Barrier } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as perf from 'vs/base/common/performance'; import { isCI } from 'vs/base/common/platform'; @@ -20,7 +18,6 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; import { ExtensionIdentifier, ExtensionIdentifierMap, IExtension, IExtensionContributions, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; @@ -1354,80 +1351,28 @@ class ActivationFeatureMarkdowneRenderer extends Disposable implements IExtensio readonly type = 'markdown'; - constructor( - @IExtensionService private readonly _extensionService: IExtensionService - ) { - super(); - } - shouldRender(manifest: IExtensionManifest): boolean { - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - if (this._extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { - return !!manifest.main || !!manifest.browser; - } return !!manifest.activationEvents; } render(manifest: IExtensionManifest): IRenderedData { - const disposables = new DisposableStore(); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const emitter = disposables.add(new Emitter()); - this._extensionService.onDidChangeExtensionsStatus(e => { - if (e.some(extension => ExtensionIdentifier.equals(extension, extensionId))) { - emitter.fire(this.getActivationData(manifest)); - } - }); - return { - onDidChange: emitter.event, - data: this.getActivationData(manifest), - dispose: () => disposables.dispose() - }; - } - - private getActivationData(manifest: IExtensionManifest): IMarkdownString { + const activationEvents = manifest.activationEvents || []; const data = new MarkdownString(); - const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - const status = this._extensionService.getExtensionsStatus()[extensionId.value]; - if (this._extensionService.extensions.some(extension => ExtensionIdentifier.equals(extension.identifier, extensionId))) { - if (status.activationTimes) { - if (status.activationTimes.activationReason.startup) { - data.appendText('Activated on startup in `') - .appendText(`${status.activationTimes.activateCallTime}ms`) - .appendText('`'); - } else { - data.appendMarkdown('Activated in `' + status.activationTimes.activateCallTime + 'ms` by `' + status.activationTimes.activationReason.activationEvent + '` event.'); - } - } else { - data.appendMarkdown('Not yet activated'); - } - if (status.runtimeErrors.length) { - data.appendMarkdown(`\n ### ${nls.localize('uncaught errors', "Uncaught Errors ({0})", status.runtimeErrors.length)}\n`); - for (const error of status.runtimeErrors) { - data.appendMarkdown(`$(${Codicon.error.id}) ${getErrorMessage(error)}\n\n`); - } - } - if (status.messages.length) { - data.appendMarkdown(`\n ### ${nls.localize('messaages', "Messages ({0})", status.messages.length)}\n`); - for (const message of status.messages) { - data.appendMarkdown(`$(${(message.type === Severity.Error ? Codicon.error : message.type === Severity.Warning ? Codicon.warning : Codicon.info).id}) ${message.message}\n\n`); - } - } - } else { - const activationEvents = manifest.activationEvents || []; - if (activationEvents.length) { - data.appendMarkdown(`### ${nls.localize('activation events', "Activation Events")}\n\n`); - for (const activationEvent of activationEvents) { - data.appendMarkdown(`- \`${activationEvent}\`\n`); - } + if (activationEvents.length) { + for (const activationEvent of activationEvents) { + data.appendMarkdown(`- \`${activationEvent}\`\n`); } } - return data; + return { + data, + dispose: () => { } + }; } } Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: 'activation', - label: nls.localize('activation', "Activation"), + id: 'activationEvents', + label: nls.localize('activation', "Activation Events"), access: { canToggle: false }, From 7dd84ca58f2bb0a81b70851495d430a0c50aca18 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 22:00:08 +0100 Subject: [PATCH 0072/1863] Breadcrumb hover (#204765) breadcrumbs hover --- .../parts/editor/breadcrumbsControl.ts | 24 ++++++++++++++++--- .../browser/outline/documentSymbolsTree.ts | 21 ++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 0eb27fcfcb660..b8b74e5aec1d4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -40,6 +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 { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; class OutlineItem extends BreadcrumbsItem { @@ -108,7 +110,8 @@ class FileItem extends BreadcrumbsItem { readonly model: BreadcrumbsModel, readonly element: FileElement, readonly options: IBreadcrumbsControlOptions, - private readonly _labels: ResourceLabels + private readonly _labels: ResourceLabels, + private readonly _hoverDelegate: IHoverDelegate ) { super(); } @@ -129,7 +132,7 @@ class FileItem extends BreadcrumbsItem { render(container: HTMLElement): void { // file/folder - const label = this._labels.create(container); + const label = this._labels.create(container, { hoverDelegate: this._hoverDelegate }); label.setFile(this.element.uri, { hidePath: true, hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, @@ -186,6 +189,8 @@ export class BreadcrumbsControl { private _breadcrumbsPickerShowing = false; private _breadcrumbsPickerIgnoreOnceItem: BreadcrumbsItem | undefined; + private readonly _hoverDelegate: IHoverDelegate; + private readonly _onDidVisibilityChange = this._disposables.add(new Emitter()); get onDidVisibilityChange() { return this._onDidVisibilityChange.event; } @@ -202,6 +207,7 @@ export class BreadcrumbsControl { @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, + @IHoverService private readonly hoverService: IHoverService ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); @@ -224,6 +230,18 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); + this._hoverDelegate = { + delay: 500, + showHover: (options: IHoverOptions) => { + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); } @@ -321,7 +339,7 @@ export class BreadcrumbsControl { showFileIcons: this._options.showFileIcons && showIcons, showSymbolIcons: this._options.showSymbolIcons && showIcons }; - const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels) : new OutlineItem(model, element, options)); + const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels, this._hoverDelegate) : new OutlineItem(model, element, options)); if (items.length === 0) { this._widget.setEnabled(false); this._widget.setItems([new class extends BreadcrumbsItem { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 437a260752ee5..8434fa5acf3c4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -24,6 +24,8 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys } 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 { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -115,15 +117,30 @@ export class DocumentSymbolRenderer implements ITreeRenderer { + return hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + } renderTemplate(container: HTMLElement): DocumentSymbolTemplate { container.classList.add('outline-element'); - const iconLabel = new IconLabel(container, { supportHighlights: true }); + const iconLabel = new IconLabel(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate }); const iconClass = dom.$('.outline-element-icon'); const decoration = dom.$('.outline-element-decoration'); container.prepend(iconClass); From 94799a61c93dbe183eee1af84d9e15b056199ddb Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 22:00:48 +0100 Subject: [PATCH 0073/1863] Editor tabs Align hover functionality (#204767) align hover --- .../browser/parts/editor/editorTabsControl.ts | 25 ++----------------- .../parts/editor/multiEditorTabsControl.ts | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 058d0a91a9202..651a9b3ff16e4 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -44,9 +44,6 @@ 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 { MarkdownString, MarkdownStringTextNewlineStyle } from 'vs/base/common/htmlContent'; -import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/platform/hover/browser/hover'; @@ -141,7 +138,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IHostService private readonly hostService: IHostService, - @IDecorationsService private readonly decorationsService: IDecorationsService, @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -451,25 +447,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } - protected getHoverTitle(editor: EditorInput): ITooltipMarkdownString { - const title = editor.getTitle(Verbosity.LONG); - const markdown = new MarkdownString(title); - - if (editor.resource) { - const decoration = this.decorationsService.getDecoration(editor.resource, false); - if (decoration) { - const decorations = decoration.tooltip.split('• '); - const decorationString = `• ${decorations.join('\n• ')}`; - - markdown.appendText('\n', MarkdownStringTextNewlineStyle.Paragraph); - markdown.appendText(decorationString, MarkdownStringTextNewlineStyle.Break); - } - } - - return { - markdown, - markdownNotSupportedFallback: title - }; + protected getHoverTitle(editor: EditorInput): string { + return editor.getTitle(Verbosity.LONG); } protected getHoverDelegate(): IHoverDelegate { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index d79a5be26c35f..2f613acd93fad 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -155,7 +155,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IDecorationsService decorationsService: IDecorationsService, @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, decorationsService, hoverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, hoverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the From 73058072bf1b761b5e0ed0e551b2969d33093dbe Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 8 Feb 2024 13:10:06 -0800 Subject: [PATCH 0074/1863] track when LM request is made (#204773) --- src/vs/workbench/api/browser/mainThreadChatProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 76001d0496008..dcf7e7ab3e096 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -73,14 +73,12 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - const access = await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`, justification); - if (!access) { - return undefined; - } return this._chatProviderService.lookupChatResponseProvider(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { + await this._extensionFeaturesManagementService.getAccess(extension, `lm-${providerId}`); + this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); const task = this._chatProviderService.fetchChatResponse(providerId, extension, messages, options, new Progress(value => { From bcf9b4ff0ff453a72d01f4bceaea05e166f347c6 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 8 Feb 2024 14:14:55 -0700 Subject: [PATCH 0075/1863] Skip extension smoketest (#204775) --- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index c78cbe8708973..8a7522c6ea77e 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -12,7 +12,7 @@ export function setup(logger: Logger) { // Shared before/after handling installAllHandlers(logger); - it('install and enable vscode-smoketest-check extension', async function () { + it.skip('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; await app.workbench.extensions.installExtension('ms-vscode.vscode-smoketest-check', true); From 90cebfaeb260263075f88dc2a8b97658942a09d7 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:17:58 -0600 Subject: [PATCH 0076/1863] Fix fuzzy searching for findFiles2 (#204768) * progress on making fuzzy option * finish connection to findfiles API --- .../src/singlefolder-tests/workspace.test.ts | 10 ++++------ src/vs/workbench/api/common/extHostWorkspace.ts | 1 + .../workbench/services/search/common/queryBuilder.ts | 2 ++ src/vs/workbench/services/search/common/search.ts | 12 ++++++++++-- src/vs/workbench/services/search/node/fileSearch.ts | 4 +++- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 6 ++++++ 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index b867766d027d4..11caa87618dc4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -598,9 +598,8 @@ suite('vscode API - workspace', () => { }); test('`findFiles2`', () => { - return vscode.workspace.findFiles2('*image.png').then((res) => { - assert.strictEqual(res.length, 4); - // TODO: see why this is fuzzy matching + return vscode.workspace.findFiles2('**/image.png').then((res) => { + assert.strictEqual(res.length, 2); }); }); @@ -619,9 +618,8 @@ suite('vscode API - workspace', () => { }); test('findFiles2, exclude', () => { - return vscode.workspace.findFiles2('*image.png', { exclude: '**/sub/**' }).then((res) => { - assert.strictEqual(res.length, 3); - // TODO: see why this is fuzzy matching + return vscode.workspace.findFiles2('**/image.png', { exclude: '**/sub/**' }).then((res) => { + assert.strictEqual(res.length, 1); }); }); diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index dcb32598916b2..65cee0c4faab6 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -497,6 +497,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac disregardSearchExcludeSettings: typeof options.useDefaultSearchExcludes === 'boolean' ? !options.useDefaultSearchExcludes : false, maxResults: options.maxResults, excludePattern: excludePattern, + shouldGlobSearch: typeof options.fuzzy === 'boolean' ? !options.fuzzy : true, _reason: 'startFileSearch' }; let folderToUse: URI | undefined; diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 78b4d0c9227b2..9124249957aa6 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -72,6 +72,7 @@ export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { exists?: boolean; sortByScore?: boolean; cacheKey?: string; + shouldGlobSearch?: boolean; } export interface ITextQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -188,6 +189,7 @@ export class QueryBuilder { exists: options.exists, sortByScore: options.sortByScore, cacheKey: options.cacheKey, + shouldGlobMatchFilePattern: options.shouldGlobSearch }; } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 7803e26cb47a7..ca93adc467b7a 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -81,6 +81,8 @@ export interface ICommonQueryProps { _reason?: string; folderQueries: IFolderQuery[]; + // The include pattern for files that gets passed into ripgrep. + // Note that this will override any ignore files if applicable. includePattern?: glob.IExpression; excludePattern?: glob.IExpression; extraFileResources?: U[]; @@ -95,6 +97,10 @@ export interface IFileQueryProps extends ICommonQueryPr type: QueryType.File; filePattern?: string; + // when walking through the tree to find the result, don't use the filePattern to fuzzy match. + // Instead, should use glob matching. + shouldGlobMatchFilePattern?: boolean; + /** * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not. * Currently does not work with queries including a 'siblings clause'. @@ -586,9 +592,11 @@ export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg i return !!(arg).path; } -export function isFilePatternMatch(candidate: IRawFileMatch, normalizedFilePatternLowercase: string): boolean { +export function isFilePatternMatch(candidate: IRawFileMatch, filePatternToUse: string, fuzzy = true): boolean { const pathToMatch = candidate.searchPath ? candidate.searchPath : candidate.relativePath; - return fuzzyContains(pathToMatch, normalizedFilePatternLowercase); + return fuzzy ? + fuzzyContains(pathToMatch, filePatternToUse) : + glob.match(filePatternToUse, pathToMatch); } export interface ISerializedFileMatch { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 3abfea8d4f32a..9b372b8dedbe6 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -75,7 +75,7 @@ export class FileWalker { this.errors = []; if (this.filePattern) { - this.normalizedFilePatternLowercase = prepareQuery(this.filePattern).normalizedLowercase; + this.normalizedFilePatternLowercase = config.shouldGlobMatchFilePattern ? null : prepareQuery(this.filePattern).normalizedLowercase; } this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); @@ -579,6 +579,8 @@ export class FileWalker { if (this.normalizedFilePatternLowercase) { return isFilePatternMatch(candidate, this.normalizedFilePatternLowercase); + } else if (this.filePattern) { + return isFilePatternMatch(candidate, this.filePattern, false); } } diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 45d734153a20a..37743a897da23 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -55,6 +55,12 @@ declare module 'vscode' { * See the vscode setting `"search.followSymlinks"`. */ followSymlinks?: boolean; + + /** + * If set to true, the `filePattern` arg will be fuzzy-searched instead of glob-searched. + * If `filePattern` is a `GlobPattern`, then the fuzzy search will act on the `pattern` of the `RelativePattern` + */ + fuzzy?: boolean; } /** From 4217d852aeb1f6fbb920063257ee351de125d92c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 8 Feb 2024 15:11:30 -0800 Subject: [PATCH 0077/1863] Make chat code blocks non-simple (#204780) This enables some features we don't want but we want to test it in insiders to see the impact --- src/vs/workbench/contrib/chat/browser/codeBlockPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index a68207d86c8c2..17b169bc4f1c4 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -380,7 +380,7 @@ export class SimpleCodeBlockPart extends BaseCodeBlockPart protected override createEditor(instantiationService: IInstantiationService, parent: HTMLElement, options: Readonly): CodeEditorWidget { return this._register(instantiationService.createInstance(CodeEditorWidget, parent, options, { - isSimpleWidget: true, + isSimpleWidget: false, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ MenuPreventer.ID, SelectionClipboardContributionID, From ba3f30b358e0fbeeb3625d1d90ada81252a97000 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 8 Feb 2024 16:29:02 -0800 Subject: [PATCH 0078/1863] Display trusted auth extensions (#204785) * Display trusted auth extensions To do this, the product.json entry takes a new shape... specifying which extensions use which auth providers. * fix bug * fix tests --- src/vs/base/common/product.ts | 2 +- .../extHostAuthentication.integrationTest.ts | 4 +- .../browser/authenticationService.ts | 181 +++++++++++++----- 3 files changed, 132 insertions(+), 55 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index cbd573fdc722f..3a57612651d14 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -113,7 +113,7 @@ export interface IProductConfiguration { readonly webExtensionTips?: readonly string[]; readonly languageExtensionTips?: readonly string[]; readonly trustedExtensionUrlPublicKeys?: IStringDictionary; - readonly trustedExtensionAuthAccess?: readonly string[]; + readonly trustedExtensionAuthAccess?: string[] | IStringDictionary; readonly trustedExtensionProtocolHandlers?: readonly string[]; readonly commandPaletteSuggestedCommandIds?: string[]; diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index 25455816095fe..5e4e8ed151073 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -24,9 +24,10 @@ import { IExtensionService, nullExtensionDescription as extensionDescription } f import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TestEnvironmentService, TestQuickInputService, TestRemoteAgentService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestActivityService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestActivityService, TestExtensionService, TestProductService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import type { AuthenticationProvider, AuthenticationSession } from 'vscode'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; class AuthQuickPick { private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; @@ -111,6 +112,7 @@ suite('ExtHostAuthentication', () => { instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IBrowserWorkbenchEnvironmentService, TestEnvironmentService); + instantiationService.stub(IProductService, TestProductService); const rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IAuthenticationService, instantiationService.createInstance(AuthenticationService)); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 401fb69b0e755..78963a374a120 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -19,7 +19,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Severity } from 'vs/platform/notification/common/notification'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -107,22 +107,13 @@ export async function getCurrentAuthenticationSessionInfo( return undefined; } -export interface AllowedExtension { +interface AllowedExtension { id: string; name: string; allowed?: boolean; -} - -function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { - let trustedExtensions: AllowedExtension[] = []; - try { - const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); - if (trustedExtensionSrc) { - trustedExtensions = JSON.parse(trustedExtensionSrc); - } - } catch (err) { } - - return trustedExtensions; + lastUsed?: number; + // If true, this comes from the product.json + trusted?: boolean; } // OAuth2 spec prohibits space in a scope, so use that to join them. @@ -439,24 +430,26 @@ export class AuthenticationService extends Disposable implements IAuthentication * if they haven't made a choice yet */ isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - const extensionData = allowList.find(extension => extension.id === extensionId); - if (extensionData) { - // This property didn't exist on this data previously, inclusion in the list at all indicates allowance - return extensionData.allowed !== undefined - ? extensionData.allowed - : true; - } - - if (this.productService.trustedExtensionAuthAccess?.includes(extensionId)) { + const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; + if (Array.isArray(trustedExtensionAuthAccess)) { + return trustedExtensionAuthAccess.includes(extensionId) ?? undefined; + } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { return true; } - return undefined; + const allowList = this.readAllowedExtensions(providerId, accountName); + const extensionData = allowList.find(extension => extension.id === extensionId); + if (!extensionData) { + return undefined; + } + // This property didn't exist on this data previously, inclusion in the list at all indicates allowance + return extensionData.allowed !== undefined + ? extensionData.allowed + : true; } updateAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): void { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); + const allowList = this.readAllowedExtensions(providerId, accountName); const index = allowList.findIndex(extension => extension.id === extensionId); if (index === -1) { allowList.push({ id: extensionId, name: extensionName, allowed: isAllowed }); @@ -823,67 +816,149 @@ export class AuthenticationService extends Disposable implements IAuthentication } } + private readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { + let trustedExtensions: AllowedExtension[] = []; + try { + const trustedExtensionSrc = this.storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); + if (trustedExtensionSrc) { + trustedExtensions = JSON.parse(trustedExtensionSrc); + } + } catch (err) { } + + return trustedExtensions; + } + + // TODO: pull this out into an Action in a contribution async manageTrustedExtensionsForAccount(id: string, accountName: string): Promise { const authProvider = this._authenticationProviders.get(id); if (!authProvider) { throw new Error(`No authentication provider '${id}' is currently registered.`); } - const allowedExtensions = readAllowedExtensions(this.storageService, authProvider.id, accountName); + + const allowedExtensions = this.readAllowedExtensions(authProvider.id, accountName); + const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; + const trustedExtensionIds = + // Case 1: trustedExtensionAuthAccess is an array + Array.isArray(trustedExtensionAuthAccess) + ? trustedExtensionAuthAccess + // Case 2: trustedExtensionAuthAccess is an object + : typeof trustedExtensionAuthAccess === 'object' + ? trustedExtensionAuthAccess[authProvider.id] ?? [] + : []; + for (const extensionId of trustedExtensionIds) { + const allowedExtension = allowedExtensions.find(ext => ext.id === extensionId); + if (!allowedExtension) { + // Add the extension to the allowedExtensions list + const extension = await this.extensionService.getExtension(extensionId); + if (extension) { + allowedExtensions.push({ + id: extensionId, + name: extension.displayName || extension.name, + allowed: true, + trusted: true + }); + } + } else { + // Update the extension to be allowed + allowedExtension.allowed = true; + allowedExtension.trusted = true; + } + } if (!allowedExtensions.length) { this.dialogService.info(nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); return; } - type TrustedExtensionsQuickPickItem = { - label: string; - description: string; + interface TrustedExtensionsQuickPickItem extends IQuickPickItem { extension: AllowedExtension; - }; - const quickPick = this.quickInputService.createQuickPick(); + lastUsed?: number; + } + + const disposableStore = new DisposableStore(); + const quickPick = disposableStore.add(this.quickInputService.createQuickPick()); quickPick.canSelectMany = true; quickPick.customButton = true; quickPick.customLabel = nls.localize('manageTrustedExtensions.cancel', 'Cancel'); const usages = readAccountUsages(this.storageService, authProvider.id, accountName); - const items = allowedExtensions.map(extension => { + const trustedExtensions = []; + const otherExtensions = []; + for (const extension of allowedExtensions) { const usage = usages.find(usage => extension.id === usage.extensionId); + extension.lastUsed = usage?.lastUsed; + if (extension.trusted) { + trustedExtensions.push(extension); + } else { + otherExtensions.push(extension); + } + } + + const sortByLastUsed = (a: AllowedExtension, b: AllowedExtension) => (b.lastUsed || 0) - (a.lastUsed || 0); + const toQuickPickItem = function (extension: AllowedExtension) { + const lastUsed = extension.lastUsed; + const description = lastUsed + ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(lastUsed, true)) + : nls.localize('notUsed', "Has not used this account"); + let tooltip: string | undefined; + if (extension.trusted) { + tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and has access to this account"); + } return { label: extension.name, - description: usage - ? nls.localize({ key: 'accountLastUsedDate', comment: ['The placeholder {0} is a string with time information, such as "3 days ago"'] }, "Last used this account {0}", fromNow(usage.lastUsed, true)) - : nls.localize('notUsed', "Has not used this account"), - extension + extension, + description, + tooltip }; - }); + }; + const items: Array = [ + ...otherExtensions.sort(sortByLastUsed).map(toQuickPickItem), + { type: 'separator', label: nls.localize('trustedExtensions', "Trusted by Microsoft") }, + ...trustedExtensions.sort(sortByLastUsed).map(toQuickPickItem) + ]; quickPick.items = items; - quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); + quickPick.selectedItems = items.filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator' && (item.extension.allowed === undefined || item.extension.allowed)); quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = nls.localize('manageExtensions', "Choose which extensions can access this account"); - quickPick.onDidAccept(() => { - const updatedAllowedList = quickPick.items.map(i => (i as TrustedExtensionsQuickPickItem).extension); + disposableStore.add(quickPick.onDidAccept(() => { + const updatedAllowedList = quickPick.items + .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') + .map(i => i.extension); this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); - quickPick.dispose(); - }); + quickPick.hide(); + })); - quickPick.onDidChangeSelection((changed) => { + disposableStore.add(quickPick.onDidChangeSelection((changed) => { + const trustedItems = new Set(); quickPick.items.forEach(item => { - if ((item as TrustedExtensionsQuickPickItem).extension) { - (item as TrustedExtensionsQuickPickItem).extension.allowed = false; + const trustItem = item as TrustedExtensionsQuickPickItem; + if (trustItem.extension) { + if (trustItem.extension.trusted) { + trustedItems.add(trustItem); + } else { + trustItem.extension.allowed = false; + } } }); + changed.forEach((item) => { + item.extension.allowed = true; + trustedItems.delete(item); + }); - changed.forEach((item) => item.extension.allowed = true); - }); + // reselect trusted items if a user tried to unselect one since quick pick doesn't support forcing selection + if (trustedItems.size) { + quickPick.selectedItems = [...changed, ...trustedItems]; + } + })); - quickPick.onDidHide(() => { - quickPick.dispose(); - }); + disposableStore.add(quickPick.onDidHide(() => { + disposableStore.dispose(); + })); - quickPick.onDidCustom(() => { + disposableStore.add(quickPick.onDidCustom(() => { quickPick.hide(); - }); + })); quickPick.show(); } From 820bfcb5251e322c67d31a07ef086bcae940fe51 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 8 Feb 2024 22:41:59 -0300 Subject: [PATCH 0079/1863] Fix converting tree data (#204791) --- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6237bc4482349..faafa59f8f07a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2376,7 +2376,7 @@ export namespace ChatResponseFilesPart { }; } export function from(part: Dto): vscode.ChatResponseFileTreePart { - const { treeData } = revive(part.treeData); + const treeData = revive(part.treeData); function convert(items: extHostProtocol.IChatResponseProgressFileTreeData[]): vscode.ChatResponseFileTree[] { return items.map(item => { return { From 8fa84589eef3538dbc763ff98dc7d5a8a0c56374 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 8 Feb 2024 18:57:55 -0800 Subject: [PATCH 0080/1863] show variable view only when applicable (#204789) * only initialize view if there is a variable provider available * use context key to show/hide the view --- .../notebookVariableContextKeys.ts | 8 +++ .../notebookVariables/notebookVariables.ts | 63 +++++++++++++++---- .../notebookVariablesDataSource.ts | 6 +- 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts new file mode 100644 index 0000000000000..b90769ef43cf5 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const NOTEBOOK_VARIABLE_VIEW_ENABLED = new RawContextKey('notebookVariableViewEnabled', false); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 962f144cc7061..0da62de50b4c8 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -9,40 +9,75 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IViewContainersRegistry, IViewsRegistry } from 'vs/workbench/common/views'; import { VIEWLET_ID as debugContainerId } from 'vs/workbench/contrib/debug/common/debug'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { NotebookVariablesView } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; -import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; + private configListener: IDisposable; + private initialized = false; + + private viewEnabled: IContextKey; constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService configurationService: IConfigurationService, - @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService + @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService ) { super(); - this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent(configurationService))); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent(configurationService))); + this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); + + this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent())); + + this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); + } + + private handleConfigChange(e: IConfigurationChangeEvent) { + if (e.affectsConfiguration(NotebookSetting.notebookVariablesView)) { + if (!this.configurationService.getValue(NotebookSetting.notebookVariablesView)) { + this.viewEnabled.set(false); + } else if (this.initialized) { + this.viewEnabled.set(true); + } else { + this.handleInitEvent(); + } + } } - private handleInitEvent(configurationService: IConfigurationService) { - if (configurationService.getValue(NotebookSetting.notebookVariablesView) + private handleInitEvent() { + if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { - if (this.initializeView()) { + + if (this.hasVariableProvider()) { + this.viewEnabled.set(true); + } + + if (!this.initialized && this.initializeView()) { + this.initialized = true; this.listeners.forEach(listener => listener.dispose()); } } } + private hasVariableProvider() { + const notebookDocument = getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; + return notebookDocument && this.notebookKernelService.getMatchingKernel(notebookDocument).selected?.hasVariableProvider; + } + private initializeView() { const debugViewContainer = Registry.as('workbench.registry.view.containers').get(debugContainerId); @@ -51,7 +86,7 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut const viewDescriptor = { id: 'NOTEBOOK_VARIABLES', name: nls.localize2('notebookVariables', "Notebook Variables"), containerIcon: variablesViewIcon, ctorDescriptor: new SyncDescriptor(NotebookVariablesView), - order: 50, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.notEquals(NOTEBOOK_KERNEL.key, ''), + order: 50, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: NOTEBOOK_VARIABLE_VIEW_ENABLED, }; viewsRegistry.registerViews([viewDescriptor], debugViewContainer); @@ -61,4 +96,10 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut return false; } + override dispose(): void { + super.dispose(); + this.listeners.forEach(listener => listener.dispose()); + this.configListener.dispose(); + } + } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 035e5b0ee7f9e..2ca03e7798e59 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -53,7 +53,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { + private async getVariables(parent: INotebookVariableElement): Promise { const selectedKernel = this.notebookKernelService.getMatchingKernel(parent.notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { @@ -75,7 +75,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource variablePageSize) { @@ -128,7 +128,7 @@ export class NotebookVariableDataSource implements IAsyncDataSource { + private async getRootVariables(notebook: NotebookTextModel): Promise { const selectedKernel = this.notebookKernelService.getMatchingKernel(notebook).selected; if (selectedKernel && selectedKernel.hasVariableProvider) { const variables = selectedKernel.provideVariables(notebook.uri, undefined, 'named', 0, this.cancellationTokenSource.token); From d1dc4e46ff8b42bb2d86980034f7b61f1edbc961 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 07:01:36 +0100 Subject: [PATCH 0081/1863] Voice: support prompts, like `@workSpace` or `/explain` (fix #201220) --- .../contrib/chat/browser/chat.contribution.ts | 2 + .../contrib/chat/common/voiceChat.ts | 108 ++++++++++++++++++ .../actions/voiceChatActions.ts | 5 +- .../electron-sandbox/inlineChatQuickVoice.ts | 7 +- 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/voiceChat.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 5a9d5ef5cf8e8..52871880c7ced 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -58,6 +58,7 @@ import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/c import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -310,3 +311,4 @@ registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.D registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); +registerSingleton(IVoiceChatService, VoiceChatService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts new file mode 100644 index 0000000000000..e68335616fa17 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { startsWithIgnoreCase } from 'vs/base/common/strings'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; + +export const IVoiceChatService = createDecorator('voiceChatService'); + +export interface IVoiceChatService { + + readonly _serviceBrand: undefined; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; +} + +export class VoiceChatService extends Disposable implements IVoiceChatService { + + readonly _serviceBrand: undefined; + + private static readonly AGENT_PREFIX = chatAgentLeader; + private static readonly COMMAND_PREFIX = chatSubcommandLeader; + + private static readonly PHRASES = { + [VoiceChatService.AGENT_PREFIX]: 'at ', + [VoiceChatService.COMMAND_PREFIX]: 'slash ' + }; + + private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); + + constructor( + @ISpeechService private readonly speechService: ISpeechService, + @IChatAgentService private readonly chatAgentService: IChatAgentService + ) { + super(); + } + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + const phrases = new Map(); + + for (const agent of this.chatAgentService.getAgents()) { + const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; + phrases.set(agentPhrase, agentResult); + + if (agent.lastSlashCommands) { + for (const slashCommand of agent.lastSlashCommands) { + const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); + const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; + phrases.set(slashCommandPhrase, slashCommandResult); + } + } + } + + const session = this.speechService.createSpeechToTextSession(token); + + const disposables = new DisposableStore(); + + const emitter = disposables.add(new Emitter()); + disposables.add(session.onDidChange(e => { + switch (e.status) { + case SpeechToTextStatus.Recognizing: + case SpeechToTextStatus.Recognized: + if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.AGENT_PREFIX)) { + let words = e.text.split(' '); + + // Check for slash command + if (words.length >= 4) { + const slashCommandResult = phrases.get(words.slice(0, 4).join(' ').toLowerCase()); + if (slashCommandResult) { + words = [slashCommandResult, ...words.slice(4)]; + } + } + + // Check for agent + if (words.length >= 2) { + const agentResult = phrases.get(words.slice(0, 2).join(' ').toLowerCase()); + if (agentResult) { + words = [agentResult, ...words.slice(2)]; + } + } + + emitter.fire({ + status: e.status, + text: words.join(' ') + }); + + break; + } + default: + emitter.fire(e); + break; + } + })); + + return { + onDidChange: emitter.event, + dispose: () => disposables.dispose() + }; + } +} 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 3b605a096d548..88fcb52e53388 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -50,6 +50,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; 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.") }); @@ -249,7 +250,7 @@ class VoiceChatSessions { constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ISpeechService private readonly speechService: ISpeechService, + @IVoiceChatService private readonly voiceChatService: IVoiceChatService, @IConfigurationService private readonly configurationService: IConfigurationService ) { } @@ -273,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const speechToTextSession = session.disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + const speechToTextSession = session.disposables.add(this.voiceChatService.createSpeechToTextSession(cts.token)); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 948093ae666d6..aedef0cc8bd89 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -14,7 +14,7 @@ import { localize, localize2 } from 'vs/nls'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { HasSpeechProvider, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import * as dom from 'vs/base/browser/dom'; @@ -25,6 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey('inlineChat.quickChatInProgress', false); @@ -215,7 +216,7 @@ export class InlineChatQuickVoice implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, - @ISpeechService private readonly _speechService: ISpeechService, + @IVoiceChatService private readonly _voiceChatService: IVoiceChatService, @IContextKeyService contextKeyService: IContextKeyService, ) { this._widget = this._store.add(new QuickVoiceWidget(this._editor)); @@ -239,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._speechService.createSpeechToTextSession(cts.token); + const session = this._voiceChatService.createSpeechToTextSession(cts.token); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From 77c16ff1f3df3d66cd831e76b5cc30624f571f73 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 09:26:19 +0100 Subject: [PATCH 0082/1863] . --- .../contrib/chat/common/voiceChat.ts | 52 +++++++++++++------ .../actions/voiceChatActions.ts | 6 +-- .../electron-sandbox/inlineChatQuickVoice.ts | 2 +- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index e68335616fa17..6598cfe4bf833 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { startsWithIgnoreCase } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; export const IVoiceChatService = createDecorator('voiceChatService'); @@ -18,7 +18,21 @@ export interface IVoiceChatService { readonly _serviceBrand: undefined; - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createVoiceChatSession(token: CancellationToken): IVoiceChatSession; +} + +export interface IVoiceChatTextEvent extends ISpeechToTextEvent { + + /** + * This property will be `true` when the text recognized + * so far only consists of agent prefixes (`@workspace`) + * and/or command prefixes (`@workspace /fix`). + */ + readonly waitingForInput?: boolean; +} + +export interface IVoiceChatSession extends IDisposable { + readonly onDidChange: Event; } export class VoiceChatService extends Disposable implements IVoiceChatService { @@ -42,7 +56,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { super(); } - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { @@ -63,33 +77,41 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); + const emitter = disposables.add(new Emitter()); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.AGENT_PREFIX)) { - let words = e.text.split(' '); + if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim())) { + const originalWords = e.text.split(' '); + let transformedWords: string[] | undefined; + + let waitingForInput = false; // Check for slash command - if (words.length >= 4) { - const slashCommandResult = phrases.get(words.slice(0, 4).join(' ').toLowerCase()); + if (originalWords.length >= 4) { + const slashCommandResult = phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); if (slashCommandResult) { - words = [slashCommandResult, ...words.slice(4)]; + transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + + waitingForInput = originalWords.length === 4; } } // Check for agent - if (words.length >= 2) { - const agentResult = phrases.get(words.slice(0, 2).join(' ').toLowerCase()); + if (!transformedWords && originalWords.length >= 2) { + const agentResult = phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); if (agentResult) { - words = [agentResult, ...words.slice(2)]; + transformedWords = [agentResult, ...originalWords.slice(2)]; + + waitingForInput = originalWords.length === 2; } } emitter.fire({ status: e.status, - text: words.join(' ') + text: (transformedWords ?? originalWords).join(' '), + waitingForInput }); break; 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 88fcb52e53388..d433ccfecad08 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -274,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const speechToTextSession = session.disposables.add(this.voiceChatService.createSpeechToTextSession(cts.token)); + const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token)); let inputValue = controller.getInput(); @@ -284,7 +284,7 @@ class VoiceChatSessions { } const acceptTranscriptionScheduler = session.disposables.add(new RunOnceScheduler(() => session.controller.acceptInput(), voiceChatTimeout)); - session.disposables.add(speechToTextSession.onDidChange(({ status, text }) => { + session.disposables.add(voiceChatSession.onDidChange(({ status, text, waitingForInput }) => { if (cts.token.isCancellationRequested) { return; } @@ -305,7 +305,7 @@ class VoiceChatSessions { if (text) { inputValue = [inputValue, text].join(' '); session.controller.updateInput(inputValue); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { acceptTranscriptionScheduler.schedule(); } } diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index aedef0cc8bd89..985ca40703b74 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -240,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._voiceChatService.createSpeechToTextSession(cts.token); + const session = this._voiceChatService.createVoiceChatSession(cts.token); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From da4271a18278cfab0e7564081220d400fe3bbdd8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 09:44:35 +0100 Subject: [PATCH 0083/1863] . --- .../contrib/chat/common/voiceChat.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 6598cfe4bf833..93481ed63c304 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -18,6 +18,12 @@ export interface IVoiceChatService { readonly _serviceBrand: undefined; + /** + * Similar to `ISpeechService.createSpeechToTextSession`, but with + * support for agent prefixes and command prefixes. For example, + * if the user says "at workspace slash fix this problem", the result + * will be "@workspace /fix this problem". + */ createVoiceChatSession(token: CancellationToken): IVoiceChatSession; } @@ -49,30 +55,42 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); + private phrases = this.createPhrases(); + constructor( @ISpeechService private readonly speechService: ISpeechService, @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); + + this.registerListeners(); } - createVoiceChatSession(token: CancellationToken): IVoiceChatSession { + private registerListeners(): void { + this._register(this.chatAgentService.onDidChangeAgents(() => this.phrases = this.createPhrases())); + } + + private createPhrases(): Map { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - phrases.set(agentPhrase, agentResult); + this.phrases.set(agentPhrase, agentResult); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - phrases.set(slashCommandPhrase, slashCommandResult); + this.phrases.set(slashCommandPhrase, slashCommandResult); } } } + return phrases; + } + + createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const session = this.speechService.createSpeechToTextSession(token); const disposables = new DisposableStore(); @@ -90,7 +108,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command if (originalWords.length >= 4) { - const slashCommandResult = phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); if (slashCommandResult) { transformedWords = [slashCommandResult, ...originalWords.slice(4)]; @@ -100,7 +118,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent if (!transformedWords && originalWords.length >= 2) { - const agentResult = phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); + const agentResult = this.phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); if (agentResult) { transformedWords = [agentResult, ...originalWords.slice(2)]; From 54ffc0e8a349da4ebbcfaa9c392cd5e3dd81fc4c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 10:06:02 +0100 Subject: [PATCH 0084/1863] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 93481ed63c304..cec3df65409fa 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -91,16 +91,21 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } createVoiceChatSession(token: CancellationToken): IVoiceChatSession { - const session = this.speechService.createSpeechToTextSession(token); - const disposables = new DisposableStore(); + let finishedPhraseDetection = false; + const emitter = disposables.add(new Emitter()); + const session = disposables.add(this.speechService.createSpeechToTextSession(token)); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if (e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim())) { + if ( + !finishedPhraseDetection && // only if we have not yet attempted phrase detection + e.text && + startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) + ) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; @@ -114,6 +119,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { waitingForInput = originalWords.length === 4; } + + finishedPhraseDetection = true; // only detect phrases in the beginning of the session } // Check for agent From 5e537b389ed2a30afd0beea99e019adbc317ae74 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 10:32:23 +0100 Subject: [PATCH 0085/1863] polishing the code --- extensions/git/src/commands.ts | 1 - .../standalone/browser/standaloneEditor.ts | 1 - src/vs/platform/list/browser/listService.ts | 1 - src/vs/workbench/api/common/extHostSCM.ts | 1 - .../bulkEdit/browser/preview/bulkEditPane.ts | 143 ++++++++---------- .../browser/preview/bulkEditPreview.ts | 1 - 6 files changed, 65 insertions(+), 83 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9c6c2e1991b83..3f46f698ce22f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3780,7 +3780,6 @@ export class CommandCenter { resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); } - // Command which is executed in order to open the multi diff editor, uring the passed in URI, the given title and the resources which are the modified resource and the original resource commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 10a75a4490422..059a4928862c6 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -100,7 +100,6 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options); } -// There is also a function which creates a multi file diff editor, but maybe we do not need it export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) { const instantiationService = StandaloneServices.initialize(override || {}); return new MultiDiffEditorWidget(domElement, {}, instantiationService); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 317af0fdf3a43..6857531cb6681 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -770,7 +770,6 @@ abstract class ResourceNavigator extends Disposable { this._open(element, preserveFocus, pinned, sideBySide, browserEvent); } - // We want to actually retrieve all of the elements, not just the element at hand private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { if (!element) { return; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 16c8268848bd8..ee2be0b1bcc24 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -416,7 +416,6 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _sourceControlHandle: number, private _id: string, private _label: string, - // The following appears to be adding multi diff editor support public readonly multiDiffEditorEnableViewChanges: boolean, private readonly _extension: IExtensionDescription, ) { } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 229662787263a..5716aa6765947 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -24,7 +23,7 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { basename, dirname } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/resources'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -38,7 +37,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const enum State { Data = 'data', @@ -80,7 +79,6 @@ export class BulkEditPane extends ViewPane { @IDialogService private readonly _dialogService: IDialogService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IStorageService private readonly _storageService: IStorageService, - @ICommandService private readonly commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IKeybindingService keybindingService: IKeybindingService, @@ -145,8 +143,7 @@ export class BulkEditPane extends ViewPane { ); this._disposables.add(this._tree.onContextMenu(this._onContextMenu, this)); - // Thing is when the tree is clicked, we want actually to show all of the files inside of the multi diff editor - this._disposables.add(this._tree.onDidOpen(e => this._openElementAsEditor(e))); + this._disposables.add(this._tree.onDidOpen(e => this._openElementInMultiDiffEditor(e))); // buttons const buttonsContainer = document.createElement('div'); @@ -226,7 +223,6 @@ export class BulkEditPane extends ViewPane { return Boolean(this._currentInput); } - // Presumably the method where we set the data to show in the tree refactors view private async _setTreeInput(input: BulkFileOperations) { const viewState = this._treeViewStates.get(this._treeDataSource.groupByFile); @@ -320,8 +316,11 @@ export class BulkEditPane extends ViewPane { } } - // In this function, we actually open the element as an editor, and this is where we could open a multi file diff editor - private async _openElementAsEditor(e: IOpenEvent): Promise { + // Issues with current implementation + // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one + // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere + // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor + private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; @@ -338,81 +337,69 @@ export class BulkEditPane extends ViewPane { return; } - console.log('options : ', JSON.stringify(options)); - - if (fileElement.edit.type & BulkFileOperationType.Delete) { - const previewUri = this._currentProvider!.asPreviewUri(fileElement.edit.uri); + let bulkFileOperations: BulkFileOperations | undefined = undefined; + let currentParent = fileElement.parent; + while (true) { + if (currentParent instanceof BulkFileOperations) { + bulkFileOperations = currentParent; + break; + } + currentParent = currentParent.parent; + } + const fileOperations = bulkFileOperations.fileOperations; + + const resources = []; + for (const operation of fileOperations) { + const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); + + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + leftResource = fileElement.edit.uri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + resources.push({ + originalUri: leftResource, + modifiedUri: previewUri + }); - console.log('fileElement.edit : ', fileElement.edit); + console.log('leftResource : ', JSON.stringify(leftResource)); console.log('previewUri : ', JSON.stringify(previewUri)); + } - const uri = previewUri; - - const label = localize('edt.title.del', "{0} (delete, refactor preview)", basename(fileElement.edit.uri)); - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = [{ - originalUri: undefined, - modifiedUri: previewUri - }]; - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { uri, label, resources }); + let typeLabel: string | undefined; + if (fileElement.edit.type & BulkFileOperationType.Rename) { + typeLabel = localize('rename', "rename"); + } else if (fileElement.edit.type & BulkFileOperationType.Create) { + typeLabel = localize('create', "create"); + } + let label: string; + if (typeLabel) { + label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); } else { - console.log('fileElement.edit ; ', fileElement.edit); - - let bulkFileOperations: BulkFileOperations | undefined = undefined; - let currentParent = fileElement.parent; - while (true) { - if (currentParent instanceof BulkFileOperations) { - bulkFileOperations = currentParent; - break; - } - currentParent = currentParent.parent; - } - const allBulkFileOperations = bulkFileOperations.fileOperations; - console.log('allBulkFileOperations : ', allBulkFileOperations); - - const resources: { originalUri: URI | undefined; modifiedUri: URI | undefined }[] = []; - - for (const operation of allBulkFileOperations) { - const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); - - let leftResource: URI | undefined; - try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; - } - - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } - - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); - } - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); - - resources.push({ - originalUri: leftResource, - modifiedUri: previewUri - }); - } - - // Issues with current implementation - // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one - // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere - // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor - const refactorSourceUri = URI.from({ scheme: 'refactor-preview' }); - this.commandService.executeCommand('_workbench.openMultiDiffEditor', { refactorSourceUri, label: 'Refactor Preview', resources }); + label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); } + + const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); + const description = 'Refactor Preview'; + + console.log('options : ', JSON.stringify(options)); + console.log('fileOperations : ', fileOperations); + console.log('fileElement.edit ; ', fileElement.edit); + console.log('multiDiffSource : ', multiDiffSource); + console.log('resources : ', resources); + console.log('label : ', label); + console.log('description : ', description); + + this._editorService.openEditor({ + multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), + label: label, + description: description, + options: options, + }); } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index c649d96c78cff..d9e8a1112a0d2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -307,7 +307,6 @@ export class BulkFileOperations { return result; } - // Getting the edits for a specific file getFileEdits(uri: URI): ISingleEditOperation[] { for (const file of this.fileOperations) { From 914f33d15bbdfef34291083f49cf4c2f04c2eef7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 10:43:31 +0100 Subject: [PATCH 0086/1863] updating correctly the right resource too --- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 5716aa6765947..9f7077d24ac2c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -350,22 +350,22 @@ export class BulkEditPane extends ViewPane { const resources = []; for (const operation of fileOperations) { - const previewUri = this._currentProvider!.asPreviewUri(operation.textEdits[0].textEdit.resource); - let leftResource: URI | undefined; + let leftResource: URI | undefined = operation.textEdits[0].textEdit.resource; + let rightResource: URI | undefined = undefined; try { - (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); - leftResource = fileElement.edit.uri; + (await this._textModelService.createModelReference(leftResource)).dispose(); + rightResource = this._currentProvider!.asPreviewUri(leftResource); } catch { leftResource = BulkEditPreviewProvider.emptyPreview; } resources.push({ originalUri: leftResource, - modifiedUri: previewUri + modifiedUri: rightResource }); console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('previewUri : ', JSON.stringify(previewUri)); + console.log('rightResource : ', JSON.stringify(rightResource)); } let typeLabel: string | undefined; From fbb7175b4c8d0da79c74abb3bd256e7bc27c602b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 13:05:22 +0100 Subject: [PATCH 0087/1863] api - rename makeRequest to makeChatRequest, updated todos, jsodc (#204819) --- src/vs/workbench/api/common/extHostChatProvider.ts | 2 +- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 12 +++++++++++- .../vscode.proposed.chatRequestAccess.d.ts | 13 ++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index d47cf51900612..be14e5557ec7c 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -219,7 +219,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); }, - makeRequest(messages, options, token) { + makeChatRequest(messages, options, token) { if (!that._accesslist.get(from)) { throw new Error('Access to chat has been revoked'); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 253347776e4da..bbdfe82206a0f 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -33,6 +33,8 @@ declare module 'vscode' { // */ // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + // agentId: string + // /** // * The result that was received from the chat agent. // */ @@ -45,6 +47,8 @@ declare module 'vscode' { */ history: ChatAgentHistoryEntry[]; + // location: + // TODO@API have "turns" // history2: (ChatAgentRequest | ChatAgentResponse)[]; } @@ -153,6 +157,7 @@ declare module 'vscode' { readonly followupPlaceholder?: string; } + // TODO@API NAME: w/o Sub just `ChatAgentCommand` etc pp export interface ChatAgentSubCommandProvider { /** @@ -253,8 +258,10 @@ declare module 'vscode' { followupProvider?: ChatAgentFollowupProvider; - // TODO@ + // TODO@API // notify(request: ChatResponsePart, reference: string): boolean; + // BETTER + // requestResponseStream(callback: (stream: ChatAgentResponseStream) => void, why?: string): void; // TODO@API // clear NEVER happens @@ -548,6 +555,8 @@ declare module 'vscode' { documents: ChatAgentDocumentContext[]; } + // TODO@API Remove a different type of `request` so that they can + // evolve at their own pace export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { @@ -573,6 +582,7 @@ declare module 'vscode' { /** * The detail level of this chat variable value. */ + // TODO@API maybe for round2 export enum ChatVariableLevel { Short = 1, Medium = 2, diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 1134553728c66..e1a151e77f43f 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -49,7 +49,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; + makeChatRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { @@ -79,10 +79,14 @@ declare module 'vscode' { /** * Request access to a language model. * - * *Note* that this function will throw an error when the user didn't grant access + * - *Note 1:* This function will throw an error when the user didn't grant access or when the + * requested language model is not available. * - * @param id The id of the language model, e.g `copilot` - * @returns A thenable that resolves to a language model access object, rejects is access wasn't granted + * - *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; @@ -94,7 +98,6 @@ declare module 'vscode' { /** * An event that is fired when the set of available language models changes. */ - //@API is this really needed? export const onDidChangeLanguageModels: Event; } } From 46054bc4377beb80277466c2a915e67e2138363b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 13:16:31 +0100 Subject: [PATCH 0088/1863] setting the height --- .../multiDiffEditorWidget.ts | 14 ++++++++- .../multiDiffEditorWidgetImpl.ts | 19 +++++++++++- .../bulkEdit/browser/preview/bulkEditPane.ts | 30 +++++++++++++++---- .../browser/multiDiffEditor.ts | 14 ++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 938edfad4b33d..349c26b5ee389 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,6 +44,18 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } + public setScrollState(scrollState: { top?: number; left?: number }): void { + this._widgetImpl.get().setScrollState(scrollState); + } + + public getTopOfElement(index: number): number { + return this._widgetImpl.get().getTopOfElement(index); + } + + public viewItems(): readonly VirtualizedViewItem[] { + return this._widgetImpl.get().viewItems(); + } + public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { return new MultiDiffEditorViewModel(model, this._instantiationService); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 129d91151b1ad..54818d55c3597 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -186,6 +186,21 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } + public getTopOfElement(index: number): number { + console.log('index : ', index); + const viewItems = this._viewItems.get(); + console.log('viewItems : ', viewItems); + let top = 0; + for (let i = 0; i < index - 1; i++) { + top += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + } + return top; + } + + public viewItems(): readonly VirtualizedViewItem[] { + return this._viewItems.get(); + } + public getViewState(): IMultiDiffEditorViewState { return { scrollState: { @@ -277,9 +292,10 @@ export interface IMultiDiffEditorViewState { interface IMultiDiffDocState { collapsed: boolean; selections?: ISelection[]; + uri?: URI; } -class VirtualizedViewItem extends Disposable { +export class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => @@ -336,6 +352,7 @@ class VirtualizedViewItem extends Disposable { return { collapsed: this.viewModel.collapsed.get(), selections: this.viewModel.lastTemplateData.get().selections, + uri: this.viewModel.originalUri }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9f7077d24ac2c..7a8faba7ea4b4 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -38,6 +38,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; const enum State { Data = 'data', @@ -68,7 +69,8 @@ export class BulkEditPane extends ViewPane { private _currentResolve?: (edit?: ResourceEdit[]) => void; private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; - + private _multiDiffEditor?: MultiDiffEditor; + private _fileOperations?: BulkFileOperation[]; constructor( options: IViewletViewOptions, @@ -317,9 +319,9 @@ export class BulkEditPane extends ViewPane { } // Issues with current implementation - // 1. Each time, this creates a new multi diff editor, we want it to reshow the same multi diff editor if there is one - // 2. The file naming does not look correct in the multi diff editor, there is a bug somewhere // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor + // Maybe we should somehow return the file operations directly instead of iterating upwards, the way that we currently do + // 4. Should jump instead to the part of the multi diff editor of interest, so no need to show it again, and can just scroll private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -348,6 +350,22 @@ export class BulkEditPane extends ViewPane { } const fileOperations = bulkFileOperations.fileOperations; + if (this._fileOperations === fileOperations) { + // Multi diff editor already open + const viewItems = this._multiDiffEditor?.viewItems(); + if (viewItems) { + const item = viewItems?.find(item => item.viewModel.originalUri?.toString() === fileElement.edit.uri.toString()); + if (item) { + const index = viewItems.indexOf(item); + const top = this._multiDiffEditor?.getTopOfElement(index); + console.log('top : ', top); + this._multiDiffEditor?.setScrollState({ top }); + } + } + return; + } + this._fileOperations = fileOperations; + const resources = []; for (const operation of fileOperations) { @@ -393,13 +411,13 @@ export class BulkEditPane extends ViewPane { console.log('label : ', label); console.log('description : ', description); - this._editorService.openEditor({ + this._multiDiffEditor = await this._editorService.openEditor({ multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), label: label, description: description, options: options, - }); + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 263668667598c..9361ee7e0efbf 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,6 +72,18 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { await super.setInput(input, options, context, token); this._viewModel = await input.getViewModel(); From c7b85d9d7aa5c2185528ed359a4a8b25ab1d5db4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 9 Feb 2024 04:28:44 -0800 Subject: [PATCH 0089/1863] polish feature renderers (#204821) --- .../api/common/configurationExtensionPoint.ts | 4 +- .../common/jsonValidationExtensionPoint.ts | 3 +- .../common/codeActionsExtensionPoint.ts | 5 +- .../browser/extensionFeaturesTab.ts | 47 +++++++------------ .../browser/media/extensionEditor.css | 5 -- .../actions/common/menusExtensionPoint.ts | 19 +++++--- .../common/extensionFeatures.ts | 4 +- .../language/common/languageService.ts | 3 +- .../themes/common/colorExtensionPoint.ts | 12 +++-- 9 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 57fa4cef7355d..496651fe769c7 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -415,9 +415,9 @@ class SettingsTableRenderer extends Disposable implements IExtensionFeatureTable const rows: IRowData[][] = contrib.sort((a, b) => a.localeCompare(b)) .map(key => { return [ - { data: key, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${key}\``), properties[key].markdownDescription ? new MarkdownString(properties[key].markdownDescription, false) : properties[key].description ?? '', - { data: `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : JSON.stringify(properties[key].default)}`, type: 'code' } + new MarkdownString().appendCodeblock('json', JSON.stringify(isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default, null, 2)), ]; }); diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index 94a2f00ff588c..1b82d305f1968 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -12,6 +12,7 @@ import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { MarkdownString } from 'vs/base/common/htmlContent'; interface IJSONValidationExtensionPoint { fileMatch: string | string[]; @@ -109,7 +110,7 @@ class JSONValidationDataRenderer extends Disposable implements IExtensionFeature const rows: IRowData[][] = contrib.map(v => { return [ - { data: Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${Array.isArray(v.fileMatch) ? v.fileMatch.join(', ') : v.fileMatch}\``), v.url, ]; }); diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts index 4e119df579a76..d8581f7d67c96 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts @@ -11,6 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; +import { MarkdownString } from 'vs/base/common/htmlContent'; enum CodeActionExtensionPointFields { languages = 'languages', @@ -100,9 +101,9 @@ class CodeActionsTableRenderer extends Disposable implements IExtensionFeatureTa .map(action => { return [ action.title, - { data: action.kind, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${action.kind}\``), action.description ?? '', - { data: [...action.languages], type: 'code' }, + new MarkdownString().appendMarkdown(`${action.languages.map(lang => `\`${lang}\``).join(' ')}`), ]; }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index a778ba34bdbd3..d09f329de49dd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -29,7 +29,6 @@ import { ThemeIcon } from 'vs/base/common/themables'; import Severity from 'vs/base/common/severity'; import { errorIcon, infoIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { SeverityIcon } from 'vs/platform/severityIcon/browser/severityIcon'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { OS } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; @@ -38,6 +37,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { Codicon } from 'vs/base/common/codicons'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { fromNow } from 'vs/base/common/date'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer { @@ -372,7 +372,6 @@ class ExtensionFeatureView extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionFeaturesManagementService private readonly extensionFeaturesManagementService: IExtensionFeaturesManagementService, @IDialogService private readonly dialogService: IDialogService, - @IKeybindingService private readonly keybindingService: IKeybindingService, ) { super(); @@ -470,36 +469,24 @@ class ExtensionFeatureView extends Disposable { if (typeof rowData === 'string') { return $('td', undefined, rowData); } - if (isMarkdownString(rowData)) { - const element = $('td', undefined); - this.renderMarkdown(rowData, element); - return element; - } - const data = Array.isArray(rowData.data) ? rowData.data : [rowData.data]; - if (rowData.type === 'code') { - return $('td', undefined, ...data.map(c => $('code', undefined, c))); - } else if (rowData.type === 'keybinding') { - return $('td', undefined, ...data.map(keybinding => { + const data = Array.isArray(rowData) ? rowData : [rowData]; + return $('td', undefined, ...data.map(item => { + const result: Node[] = []; + if (isMarkdownString(rowData)) { + const element = $('td', undefined); + this.renderMarkdown(rowData, element); + result.push(element); + } else if (item instanceof ResolvedKeybinding) { const element = $(''); const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles); - kbl.set(this.keybindingService.resolveUserBinding(keybinding)[0]); - return element; - })); - } else if (rowData.type === 'color') { - return $('td', undefined, ...data.map(colorReference => { - const result: Node[] = []; - if (colorReference && colorReference[0] === '#') { - const color = Color.fromHex(colorReference); - if (color) { - result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, '')); - } - } - result.push($('code', undefined, colorReference)); - return result; - }).flat()); - } else { - return $('td', undefined, rowData.data[0]); - } + kbl.set(item); + result.push(element); + } else if (item instanceof Color) { + result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(item) }, '')); + result.push($('code', undefined, Color.Format.CSS.formatHex(item))); + } + return result; + }).flat()); }) ); }))); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 93b10e5bb6e41..4243f5ebd44a6 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -874,11 +874,6 @@ align-items: center; } -.extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content pre { - white-space: pre-wrap; - word-wrap: break-word; -} - .extension-editor .subcontent.feature-contributions .extension-feature-content .feature-body .feature-body-content .feature-content.markdown .codicon { vertical-align: sub; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 981c5a0ed629f..a81d30aee5e10 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -22,6 +22,9 @@ import { IExtensionManifest, IKeyBinding } from 'vs/platform/extensions/common/e import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { platform } from 'vs/base/common/process'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; interface IAPIMenu { readonly key: string; @@ -996,6 +999,10 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable readonly type = 'table'; + constructor( + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { super(); } + shouldRender(manifest: IExtensionManifest): boolean { return !!manifest.contributes?.commands; } @@ -1005,7 +1012,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable const commands = rawCommands.map(c => ({ id: c.command, title: c.title, - keybindings: [] as string[], + keybindings: [] as ResolvedKeybinding[], menus: [] as string[] })); @@ -1062,10 +1069,10 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable const rows: IRowData[][] = commands.sort((a, b) => a.id.localeCompare(b.id)) .map(command => { return [ - { data: command.id, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${command.id}\``), typeof command.title === 'string' ? command.title : command.title.value, - { data: command.keybindings, type: 'keybinding' }, - { data: command.menus, type: 'code' }, + command.keybindings, + new MarkdownString().appendMarkdown(`${command.menus.map(menu => `\`${menu}\``).join(' ')}`), ]; }); @@ -1078,7 +1085,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable }; } - private resolveKeybinding(rawKeyBinding: IKeyBinding): string | undefined { + private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | undefined { let key: string | undefined; switch (platform) { @@ -1087,7 +1094,7 @@ class CommandsTableRenderer extends Disposable implements IExtensionFeatureTable case 'darwin': key = rawKeyBinding.mac; break; } - return key ?? rawKeyBinding.key; + return this._keybindingService.resolveUserBinding(key ?? rawKeyBinding.key)[0]; } } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts index e72d5eee7353d..2e02427e40f89 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionFeatures.ts @@ -12,6 +12,8 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import Severity from 'vs/base/common/severity'; import { IStringDictionary } from 'vs/base/common/collections'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { Color } from 'vs/base/common/color'; export namespace Extensions { export const ExtensionFeaturesRegistry = 'workbench.registry.extensionFeatures'; @@ -33,7 +35,7 @@ export interface IExtensionFeatureMarkdownRenderer extends IExtensionFeatureRend render(manifest: IExtensionManifest): IRenderedData; } -export type IRowData = string | IMarkdownString | { readonly data: string | string[]; readonly type: 'code' | 'keybinding' | 'color' }; +export type IRowData = string | IMarkdownString | ResolvedKeybinding | Color | ReadonlyArray; export interface ITableData { headers: string[]; diff --git a/src/vs/workbench/services/language/common/languageService.ts b/src/vs/workbench/services/language/common/languageService.ts index c7e93a09c44e0..c788168597a47 100644 --- a/src/vs/workbench/services/language/common/languageService.ts +++ b/src/vs/workbench/services/language/common/languageService.ts @@ -22,6 +22,7 @@ import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { index } from 'vs/base/common/arrays'; +import { MarkdownString } from 'vs/base/common/htmlContent'; export interface IRawLanguageExtensionPoint { id: string; @@ -181,7 +182,7 @@ class LanguageTableRenderer extends Disposable implements IExtensionFeatureTable .map(l => { return [ l.id, l.name, - { data: l.extensions, type: 'code' }, + new MarkdownString().appendMarkdown(`${l.extensions.map(e => `\`${e}\``).join(' ')}`), l.hasGrammar ? '✔︎' : '\u2014', l.hasSnippets ? '✔︎' : '\u2014' ]; diff --git a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts index 30f8cd249871a..9a7bf4aa503fd 100644 --- a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts @@ -12,6 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { MarkdownString } from 'vs/base/common/htmlContent'; interface IColorExtensionPoint { id: string; @@ -175,14 +176,17 @@ class ColorDataRenderer extends Disposable implements IExtensionFeatureTableRend nls.localize('defaultLight', "Light Default"), nls.localize('defaultHC', "High Contrast Default"), ]; + + const toColor = (colorReference: string): Color | undefined => colorReference[0] === '#' ? Color.fromHex(colorReference) : undefined; + const rows: IRowData[][] = colors.sort((a, b) => a.id.localeCompare(b.id)) .map(color => { return [ - { data: color.id, type: 'code' }, + new MarkdownString().appendMarkdown(`\`${color.id}\``), color.description, - { data: color.defaults.dark, type: 'color' }, - { data: color.defaults.light, type: 'color' }, - { data: color.defaults.highContrast, type: 'color' }, + toColor(color.defaults.dark) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.dark}\``), + toColor(color.defaults.light) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.light}\``), + toColor(color.defaults.highContrast) ?? new MarkdownString().appendMarkdown(`\`${color.defaults.highContrast}\``), ]; }); From 20c0f83f1b6e436f4db6888e621e2fff625f8b8c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:19:30 +0100 Subject: [PATCH 0090/1863] Git - experimental input validation using diagnostics (#204822) * Initial implementation * Add setting + code actions --- extensions/git/package.json | 6 ++ extensions/git/src/diagnostics.ts | 159 ++++++++++++++++++++++++++++++ extensions/git/src/main.ts | 7 ++ extensions/git/tsconfig.json | 1 + 4 files changed, 173 insertions(+) create mode 100644 extensions/git/src/diagnostics.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 88553d9112624..bee5fc15c87cc 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -28,6 +28,7 @@ "scmHistoryProvider", "scmMultiDiffEditor", "scmSelectedProvider", + "scmTextDocument", "scmValidation", "tabInputTextMerge", "timeline" @@ -2748,6 +2749,11 @@ "default": 50, "markdownDescription": "%config.inputValidationSubjectLength%" }, + "git.experimental.inputValidation": { + "type": "boolean", + "default": false, + "description": "%config.inputValidation%" + }, "git.detectSubmodules": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts new file mode 100644 index 0000000000000..f9b45a9b23ea5 --- /dev/null +++ b/extensions/git/src/diagnostics.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CodeAction, CodeActionKind, CodeActionProvider, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, Range, Selection, TextDocument, Uri, WorkspaceEdit, l10n, languages, workspace } from 'vscode'; +import { mapEvent, filterEvent, dispose } from './util'; + +export enum DiagnosticCodes { + empty_message = 'empty_message', + line_length = 'line_length' +} + +export class GitCommitInputBoxDiagnosticsManager { + + private readonly diagnostics: DiagnosticCollection; + private readonly severity = DiagnosticSeverity.Warning; + private readonly disposables: Disposable[] = []; + + constructor() { + this.diagnostics = languages.createDiagnosticCollection(); + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.validateTextDocument, this, this.disposables); + } + + public getDiagnostics(uri: Uri): ReadonlyArray { + return this.diagnostics.get(uri) ?? []; + } + + private validateTextDocument(document: TextDocument): void { + this.diagnostics.delete(document.uri); + + const config = workspace.getConfiguration('git'); + const inputValidation = config.get('experimental.inputValidation', false) === true; + if (!inputValidation) { + return; + } + + const diagnostics: Diagnostic[] = []; + + if (/^\s+$/.test(document.getText())) { + const documentRange = new Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end); + const diagnostic = new Diagnostic(documentRange, l10n.t('Current commit message only contains whitespace characters'), this.severity); + diagnostic.code = DiagnosticCodes.empty_message; + + diagnostics.push(diagnostic); + this.diagnostics.set(document.uri, diagnostics); + + return; + } + + const inputValidationLength = config.get('inputValidationLength', 50); + const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); + + for (let index = 0; index < document.lineCount; index++) { + const line = document.lineAt(index); + const threshold = index === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + + if (line.text.length > threshold) { + const diagnostic = new Diagnostic(line.range, l10n.t('{0} characters over {1} in current line', line.text.length - threshold, threshold), this.severity); + diagnostic.code = DiagnosticCodes.line_length; + + diagnostics.push(diagnostic); + } + } + + this.diagnostics.set(document.uri, diagnostics); + } + + dispose() { + dispose(this.disposables); + } +} + +export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider { + + private readonly disposables: Disposable[] = []; + + constructor(private readonly diagnosticsManager: GitCommitInputBoxDiagnosticsManager) { + this.disposables.push(languages.registerCodeActionsProvider({ scheme: 'vscode-scm' }, this)); + } + + provideCodeActions(document: TextDocument, range: Range | Selection): CodeAction[] { + const codeActions: CodeAction[] = []; + const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri); + + for (const diagnostic of diagnostics) { + if (!diagnostic.range.contains(range)) { + continue; + } + + switch (diagnostic.code) { + case DiagnosticCodes.empty_message: { + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.delete(document.uri, diagnostic.range); + + const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); + codeAction.diagnostics = [diagnostic]; + codeAction.edit = workspaceEdit; + + codeActions.push(codeAction); + break; + } + case DiagnosticCodes.line_length: { + const workspaceEdit = this.getWrapLineWorkspaceEdit(document, diagnostic.range); + const codeAction = new CodeAction(l10n.t('Hard wrap line'), CodeActionKind.QuickFix); + codeAction.diagnostics = [diagnostic]; + codeAction.edit = workspaceEdit; + + codeActions.push(codeAction); + break; + } + } + } + + return codeActions; + } + + private getWrapLineWorkspaceEdit(document: TextDocument, range: Range): WorkspaceEdit { + const config = workspace.getConfiguration('git'); + const inputValidationLength = config.get('inputValidationLength', 50); + const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); + const lineLengthThreshold = range.start.line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + + const lineSegments: string[] = []; + const lineText = document.lineAt(range.start.line).text; + + let position = 0; + while (lineText.length - position > lineLengthThreshold) { + const lastSpaceBeforeThreshold = lineText.lastIndexOf(' ', position + lineLengthThreshold); + + if (lastSpaceBeforeThreshold !== -1 && lastSpaceBeforeThreshold > position) { + lineSegments.push(lineText.substring(position, lastSpaceBeforeThreshold)); + position = lastSpaceBeforeThreshold + 1; + } else { + // Find first space after threshold + const firstSpaceAfterThreshold = lineText.indexOf(' ', position + lineLengthThreshold); + if (firstSpaceAfterThreshold !== -1) { + lineSegments.push(lineText.substring(position, firstSpaceAfterThreshold)); + position = firstSpaceAfterThreshold + 1; + } else { + lineSegments.push(lineText.substring(position)); + position = lineText.length; + } + } + } + if (position < lineText.length) { + lineSegments.push(lineText.substring(position)); + } + + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); + + return workspaceEdit; + } + + dispose() { + dispose(this.disposables); + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 5440795cce9cf..c40aedaec691e 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,6 +25,7 @@ import { createIPCServer, IPCServer } from './ipc/ipcServer'; import { GitEditor } from './gitEditor'; import { GitPostCommitCommandsProvider } from './postCommitCommands'; import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider'; +import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManager } from './diagnostics'; const deactivateTasks: { (): Promise }[] = []; @@ -118,6 +119,12 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); model.registerPostCommitCommandsProvider(postCommitCommandsProvider); + const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(); + disposables.push(diagnosticsManager); + + const codeActionsProvider = new GitCommitInputBoxCodeActionsProvider(diagnosticsManager); + disposables.push(codeActionsProvider); + checkGitVersion(info); commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0); diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 48b9d4227f047..d485d90421543 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -17,6 +17,7 @@ "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", "../../src/vscode-dts/vscode.proposed.scmMultiDiffEditor.d.ts", + "../../src/vscode-dts/vscode.proposed.scmTextDocument.d.ts", "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" From 808582dad06e722860e5169f0a43ff193b5625fa Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 9 Feb 2024 10:32:27 -0300 Subject: [PATCH 0091/1863] Bring back command buttons, remove command followups (#204548) * Revert "Revert "Replace chat "command followups" with command button content (#204512)"" This reverts commit e822ae92ea05e56540b0535e436cb043c01ffbbb. * Also add ChatResponseCommandButtonPart class * Fix * dts comments --- .../workbench/api/common/extHost.api.impl.ts | 3 +- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatAgents2.ts | 49 ++++++++-- .../workbench/api/common/extHostInlineChat.ts | 15 ++-- .../api/common/extHostTypeConverters.ts | 54 ++++++++--- src/vs/workbench/api/common/extHostTypes.ts | 7 ++ .../contrib/chat/browser/chatFollowups.ts | 3 +- .../contrib/chat/browser/chatInputPart.ts | 6 +- .../contrib/chat/browser/chatListRenderer.ts | 82 ++++++++--------- .../contrib/chat/browser/chatWidget.ts | 4 +- .../contrib/chat/browser/media/chat.css | 16 ++++ .../contrib/chat/common/chatAgents.ts | 4 +- .../contrib/chat/common/chatModel.ts | 32 +++++-- .../contrib/chat/common/chatService.ts | 26 +++--- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../contrib/chat/common/chatViewModel.ts | 22 ++--- .../inlineChat/browser/inlineChatWidget.ts | 89 +++++++++---------- .../contrib/inlineChat/common/inlineChat.ts | 22 ++++- .../vscode.proposed.chatAgents2.d.ts | 41 +++++---- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- .../vscode.proposed.interactive.d.ts | 2 +- 22 files changed, 303 insertions(+), 190 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5b672c3356a56..d899ada547e6c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); - const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); @@ -1662,6 +1662,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, + ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a34f7a83238fa..b7944086d302b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -53,10 +53,10 @@ import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/cal import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; -import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -1226,7 +1226,7 @@ export interface ExtHostChatAgentsShape2 { $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; - $provideSampleQuestions(handle: number, token: CancellationToken): Promise; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; } @@ -1252,7 +1252,7 @@ export type IInlineChatResponseDto = Dto; $provideResponse(handle: number, session: IInlineChatSession, request: IInlineChatRequest, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise; $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void; $releaseSession(handle: number, sessionId: number): void; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index d83e50c844245..191941e87d634 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -16,10 +17,11 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; @@ -35,7 +37,9 @@ class ChatAgentResponseStream { private readonly _extension: IExtensionDescription, private readonly _request: IChatAgentRequest, private readonly _proxy: MainThreadChatAgentsShape2, - @ILogService private readonly _logService: ILogService, + private readonly _logService: ILogService, + private readonly _commandsConverter: CommandsConverter, + private readonly _sessionDisposables: DisposableStore ) { } close() { @@ -99,6 +103,13 @@ class ChatAgentResponseStream { _report(dto); return this; }, + button(value) { + throwIfDone(this.anchor); + const part = new extHostTypes.ChatResponseCommandButtonPart(value); + const dto = typeConvert.ChatResponseCommandButtonPart.to(part, that._commandsConverter, that._sessionDisposables); + _report(dto); + return this; + }, progress(value) { throwIfDone(this.progress); const part = new extHostTypes.ChatResponseProgressPart(value); @@ -151,11 +162,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private readonly _previousResultMap: Map = new Map(); private readonly _resultsBySessionAndRequestId: Map> = new Map(); + private readonly _sessionDisposables: DisposableMap = new DisposableMap(); constructor( mainContext: IMainContext, private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, + private readonly commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } @@ -181,7 +194,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); - const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService); + // Init session disposables + let sessionDisposables = this._sessionDisposables.get(request.sessionId); + if (!sessionDisposables) { + sessionDisposables = new DisposableStore(); + this._sessionDisposables.set(request.sessionId, sessionDisposables); + } + + const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { const convertedHistory = await this.prepareHistory(agent, request, context); const task = agent.invoke( @@ -226,7 +246,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { || h.result; return { request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r))), + response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), result } satisfies vscode.ChatAgentHistoryEntry; }))); @@ -235,6 +255,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { $releaseSession(sessionId: string): void { this._previousResultMap.delete(sessionId); this._resultsBySessionAndRequestId.delete(sessionId); + this._sessionDisposables.deleteAndDispose(sessionId); } async $provideSlashCommands(handle: number, token: CancellationToken): Promise { @@ -295,7 +316,14 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // handled by $acceptFeedback return; } - agent.acceptAction(Object.freeze({ action: action.action, result })); + + if (action.action.kind === 'command') { + const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; + agent.acceptAction(Object.freeze({ action: commandAction, result })); + return; + } else { + agent.acceptAction(Object.freeze({ action: action.action, result })); + } } async $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise { @@ -317,7 +345,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await agent.provideWelcomeMessage(token); } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return; @@ -395,7 +423,10 @@ class ExtHostChatAgent { if (!followups) { return []; } - return followups.map(f => typeConvert.ChatFollowup.from(f)); + return followups + // Filter out "command followups" from older providers + .filter(f => !(f && 'commandId' in f)) + .map(f => typeConvert.ChatFollowup.from(f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -415,7 +446,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -424,7 +455,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatReplyFollowup.from(f)); + return content?.map(f => typeConvert.ChatFollowup.from(f)); } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 47ec20ccea2be..9e8478b98f31a 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -3,23 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { IPosition } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { IInlineChatSession, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInlineChatShape, IInlineChatResponseDto, IMainContext, MainContext, MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { IInlineChatFollowup, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import type * as vscode from 'vscode'; -import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { IRange } from 'vs/editor/common/core/range'; -import { IPosition } from 'vs/editor/common/core/position'; -import { raceCancellation } from 'vs/base/common/async'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; class ProviderWrapper { @@ -228,14 +227,14 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } } - async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { + async $provideFollowups(handle: number, sessionId: number, responseId: number, token: CancellationToken): Promise { const entry = this._inputProvider.get(handle); const sessionData = this._inputSessions.get(sessionId); const response = sessionData?.responses[responseId]; if (entry && response && entry.provider.provideFollowups) { const task = Promise.resolve(entry.provider.provideFollowups(sessionData.session, response, token)); const followups = await raceCancellation(task, token); - return followups?.map(typeConvert.ChatFollowup.from); + return followups?.map(typeConvert.ChatInlineFollowup.from); } return undefined; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index faafa59f8f07a..6e0cc73d248a6 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,9 +36,9 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -50,6 +50,7 @@ import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { basename } from 'vs/base/common/resources'; export namespace Command { @@ -2191,8 +2192,8 @@ export namespace DataTransfer { } } -export namespace ChatReplyFollowup { - export function from(followup: vscode.ChatAgentReplyFollowup | vscode.InteractiveEditorReplyFollowup): IChatReplyFollowup { +export namespace ChatFollowup { + export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { return { kind: 'reply', message: followup.message, @@ -2202,21 +2203,25 @@ export namespace ChatReplyFollowup { } } -export namespace ChatFollowup { - export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { - if (typeof followup === 'string') { - return { title: followup, message: followup, kind: 'reply' }; - } else if ('commandId' in followup) { - return { +export namespace ChatInlineFollowup { + export function from(followup: vscode.InteractiveEditorFollowup): IInlineChatFollowup { + if ('commandId' in followup) { + return { kind: 'command', title: followup.title ?? '', commandId: followup.commandId ?? '', when: followup.when ?? '', args: followup.args - }; + } satisfies IInlineChatCommandFollowup; } else { - return ChatReplyFollowup.from(followup); + return { + kind: 'reply', + message: followup.message, + title: followup.title, + tooltip: followup.tooltip, + } satisfies IInlineChatReplyFollowup; } + } } @@ -2422,6 +2427,21 @@ export namespace ChatResponseProgressPart { } } +export namespace ChatResponseCommandButtonPart { + export function to(part: vscode.ChatResponseCommandButtonPart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): Dto { + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + const command = commandsConverter.toInternal(part.value, commandDisposables) ?? { command: part.value.command, title: part.value.title }; + return { + kind: 'command', + command + }; + } + export function from(part: Dto, commandsConverter: CommandsConverter): vscode.ChatResponseCommandButtonPart { + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + return new types.ChatResponseCommandButtonPart(commandsConverter.fromInternal(part.command) ?? { command: part.command.id, title: part.command.title }); + } +} + export namespace ChatResponseReferencePart { export function to(part: vscode.ChatResponseReferencePart): Dto { return { @@ -2458,13 +2478,14 @@ export namespace ChatResponsePart { } - export function from(part: extHostProtocol.IChatProgressDto): vscode.ChatResponsePart { + export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); case 'reference': return ChatResponseReferencePart.from(part); case 'progressMessage': return ChatResponseProgressPart.from(part); case 'treeData': return ChatResponseFilesPart.from(part); + case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } return new types.ChatResponseTextPart(''); } @@ -2557,7 +2578,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2572,6 +2593,11 @@ export namespace ChatResponseProgress { }; case 'treeData': return { treeData: revive(progress.treeData) }; + case 'command': + // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore + return { + command: commandsConverter.fromInternal(progress.command) ?? { command: progress.command.id, title: progress.command.title }, + }; default: // Unknown type, eg something in history that was removed? Ignore return undefined; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 300a1a4c54946..9a392ffc2e32f 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4242,6 +4242,13 @@ export class ChatResponseProgressPart { } } +export class ChatResponseCommandButtonPart { + value: vscode.Command; + constructor(value: vscode.Command) { + this.value = value; + } +} + export class ChatResponseReferencePart { value: vscode.Uri | vscode.Location; constructor(value: vscode.Uri | vscode.Location) { diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 6c6e0d8f6d729..70e92d3f5b130 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -10,10 +10,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const $ = dom.$; -export class ChatFollowups extends Disposable { +export class ChatFollowups extends Disposable { constructor( container: HTMLElement, followups: T[], diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index eb906af265ff2..b69f086eb58bd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -37,7 +37,7 @@ import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatReplyFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -64,7 +64,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private _onDidBlur = this._register(new Emitter()); readonly onDidBlur = this._onDidBlur.event; - private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatReplyFollowup; response: IChatResponseViewModel | undefined }>()); + private _onDidAcceptFollowup = this._register(new Emitter<{ followup: IChatFollowup; response: IChatResponseViewModel | undefined }>()); readonly onDidAcceptFollowup = this._onDidAcceptFollowup.event; private inputEditorHeight = 0; @@ -339,7 +339,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - async renderFollowups(items: IChatReplyFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { + async renderFollowups(items: IChatFollowup[] | undefined, response: IChatResponseViewModel | undefined): Promise { if (!this.options.renderFollowups) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 9983b28dd4ecb..972fe2d9aeb3d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -63,7 +63,7 @@ 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'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentReference, IChatFollowup, IChatProgressMessage, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatProgressMessageRenderData, IChatRenderData, IChatResponseMarkdownRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -117,8 +117,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); - readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; + protected readonly _onDidClickFollowup = this._register(new Emitter()); + readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; protected readonly _onDidChangeItemHeight = this._register(new Emitter()); readonly onDidChangeItemHeight: Event = this._onDidChangeItemHeight.event; @@ -141,13 +141,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - this.chatService.notifyUserAction({ - providerId: element.providerId, - agentId: element.agent?.id, - sessionId: element.sessionId, - requestId: element.requestId, - action: { - kind: 'command', - command: followup, - } - }); - return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); - }, - templateData.contextKeyService)); - } - const newHeight = templateData.rowContainer.offsetHeight; const fireEvent = !element.currentRenderedHeight || element.currentRenderedHeight !== newHeight; element.currentRenderedHeight = newHeight; @@ -581,6 +559,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); + return { + dispose() { + disposables.dispose(); + }, + element: container + }; + } + private renderMarkdown(markdown: IMarkdownString, element: ChatTreeItem, templateData: IChatListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { const disposables = new DisposableStore(); let codeBlockIndex = 0; @@ -1029,17 +1028,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider followup.title).join(', ')); - } const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; let fileTreeCountHint = ''; switch (fileTreeCount) { @@ -1064,7 +1052,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider, i: number): boolean { return items.slice(i).every(isProgressMessage); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index ed99c1c6409c4..2e4fffac46517 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -30,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; 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 { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +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 } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -295,7 +295,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private async renderFollowups(items: IChatReplyFollowup[] | undefined, response?: IChatResponseViewModel): Promise { + private async renderFollowups(items: IChatFollowup[] | undefined, response?: IChatResponseViewModel): Promise { this.inputPart.renderFollowups(items, response); if (this.bodyDimension) { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index fdbd39a596129..52180a6065249 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -633,3 +633,19 @@ font-size: 14px; color: var(--vscode-debugIcon-startForeground) !important; } + +.interactive-item-container .chat-command-button { + display: flex; + margin-bottom: 16px; +} + +.interactive-item-container .chat-command-button .monaco-button { + text-align: left; + width: initial; + padding: 4px 8px; +} + +.interactive-item-container .chat-command-button .monaco-button .codicon { + margin-left: 0; + margin-top: 1px; +} diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 6b71960d11992..cd4e3b1b48931 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -35,7 +35,7 @@ export interface IChatAgent extends IChatAgentData { lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface IChatAgentCommand { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 951b8a1900187..5b09beef63be8 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -33,7 +33,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -43,7 +43,8 @@ export type IChatProgressResponseContent = | IChatAgentMarkdownContentWithVulnerability | IChatTreeData | IChatContentInlineReference - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export type IChatProgressRenderableResponseContent = Exclude; @@ -68,6 +69,8 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; + /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; readonly errorDetails?: IChatResponseErrorDetails; @@ -167,7 +170,7 @@ export class Response implements IResponse { } this._updateRepr(quiet); - } else if (progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else { this._responseParts.push(progress); this._updateRepr(quiet); } @@ -179,6 +182,8 @@ export class Response implements IResponse { return ''; } else if (part.kind === 'inlineReference') { return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } else if (part.kind === 'command') { + return part.command.title; } else { return part.content.value; } @@ -263,6 +268,11 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._progressMessages; } + private _isStale: boolean = false; + public get isStale(): boolean { + return this._isStale; + } + constructor( _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, @@ -276,6 +286,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel followups?: ReadonlyArray ) { super(); + + // If we are creating a response with some existing content, consider it stale + this._isStale = Array.isArray(_response) && (_response.length !== 0 || isMarkdownString(_response) && _response.value.length !== 0); + this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); @@ -377,7 +391,7 @@ export interface ISerializableChatRequestData { export interface IExportableChatData { providerId: string; - welcomeMessage: (string | IChatReplyFollowup[])[] | undefined; + welcomeMessage: (string | IChatFollowup[])[] | undefined; requests: ISerializableChatRequestData[]; requesterUsername: string; responderUsername: string; @@ -655,7 +669,7 @@ export class ChatModel extends Disposable implements IChatModel { if (progress.kind === 'vulnerability') { request.response.updateContent({ kind: 'markdownVuln', content: { value: progress.content }, vulnerabilities: progress.vulnerabilities }, quiet); - } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage') { + } else if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference' || progress.kind === 'markdownVuln' || progress.kind === 'progressMessage' || progress.kind === 'command') { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); @@ -781,12 +795,12 @@ export class ChatModel extends Disposable implements IChatModel { } } -export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[]; +export type IChatWelcomeMessageContent = IMarkdownString | IChatFollowup[]; export interface IChatWelcomeMessageModel { readonly id: string; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; readonly username: string; readonly avatarIconUri?: URI; @@ -803,7 +817,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { constructor( private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], - public readonly sampleQuestions: IChatReplyFollowup[] + public readonly sampleQuestions: IChatFollowup[] ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 9a9c1196e72d0..bf5c78b9ef29c 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Location, ProviderResult } from 'vs/editor/common/languages'; +import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -142,6 +142,11 @@ export interface IChatAgentMarkdownContentWithVulnerability { kind: 'markdownVuln'; } +export interface IChatCommandButton { + command: Command; + kind: 'command'; +} + export type IChatProgress = | IChatContent | IChatMarkdownContent @@ -152,30 +157,21 @@ export type IChatProgress = | IChatContentReference | IChatContentInlineReference | IChatAgentDetection - | IChatProgressMessage; + | IChatProgressMessage + | IChatCommandButton; export interface IChatProvider { readonly id: string; prepareSession(token: CancellationToken): ProviderResult; } -export interface IChatReplyFollowup { +export interface IChatFollowup { kind: 'reply'; message: string; title?: string; tooltip?: string; } -export interface IChatResponseCommandFollowup { - kind: 'command'; - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; -} - -export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup; - // Name has to match the one in vscode.d.ts for some reason export enum InteractiveSessionVoteDirection { Down = 0, @@ -218,12 +214,12 @@ export interface IChatTerminalAction { export interface IChatCommandAction { kind: 'command'; - command: IChatResponseCommandFollowup; + commandButton: IChatCommandButton; } export interface IChatFollowupAction { kind: 'followUp'; - followup: IChatReplyFollowup; + followup: IChatFollowup; } export interface IChatBugReportAction { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index caab172b0f488..eae790915c18c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -235,8 +235,8 @@ export class ChatService extends Disposable implements IChatService { newFile: !!action.action.newFile }); } else if (action.action.kind === 'command') { - const command = CommandsRegistry.getCommand(action.action.command.commandId); - const commandId = command ? action.action.command.commandId : 'INVALID'; + const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); + const commandId = command ? action.action.commandButton.command.id : 'INVALID'; this.telemetryService.publicLog2('interactiveSessionCommand', { providerId: action.providerId, commandId diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index e3712490d9ad4..d55a8b0c07f0b 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData } 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, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatProgressMessage, IChatFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { @@ -60,7 +60,7 @@ export interface IChatRequestViewModel { readonly dataId: string; readonly username: string; readonly avatarIconUri?: URI; - readonly message: IParsedChatRequest | IChatReplyFollowup; + readonly message: IParsedChatRequest | IChatFollowup; readonly messageText: string; currentRenderedHeight: number | undefined; } @@ -88,7 +88,7 @@ export interface IChatProgressMessageRenderData { isLast: boolean; } -export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData; +export type IChatRenderData = IChatResponseProgressFileTreeData | IChatResponseMarkdownRenderData | IChatProgressMessageRenderData | IChatCommandButton; export interface IChatResponseRenderData { renderedParts: IChatRenderData[]; } @@ -118,9 +118,9 @@ export interface IChatResponseViewModel { readonly progressMessages: ReadonlyArray; readonly isComplete: boolean; readonly isCanceled: boolean; + readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; - readonly replyFollowups?: IChatReplyFollowup[]; - readonly commandFollowups?: IChatResponseCommandFollowup[]; + readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; @@ -331,11 +331,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get replyFollowups() { - return this._model.followups?.filter((f): f is IChatReplyFollowup => f.kind === 'reply'); - } - - get commandFollowups() { - return this._model.followups?.filter((f): f is IChatResponseCommandFollowup => f.kind === 'command'); + return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); } get errorDetails() { @@ -350,6 +346,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.requestId; } + get isStale() { + return this._model.isStale; + } + renderData: IChatResponseRenderData | undefined = undefined; agentAvatarHasBeenRendered?: boolean; currentRenderedHeight: number | undefined; @@ -436,6 +436,6 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; - readonly sampleQuestions: IChatReplyFollowup[]; + readonly sampleQuestions: IChatFollowup[]; currentRenderedHeight?: number; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index b631e16714d88..aee5dace743a1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -3,66 +3,65 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./inlineChat'; -import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, ACTION_ACCEPT_CHANGES, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; +import * as aria from 'vs/base/browser/ui/aria/aria'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Lazy } from 'vs/base/common/lazy'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./inlineChat'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { IModelService } from 'vs/editor/common/services/model'; -import { URI } from 'vs/base/common/uri'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; -import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; +import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { LanguageSelector } from 'vs/editor/common/languageSelector'; -import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; -import { LineRange } from 'vs/editor/common/core/lineRange'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { FileKind } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; +import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; -import { assertType } from 'vs/base/common/types'; -import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; +import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { MenuId } from 'vs/platform/actions/common/actions'; -import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { Lazy } from 'vs/base/common/lazy'; -import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { ExpansionState, HunkData } 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'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -632,9 +631,9 @@ export class InlineChatWidget { return resultingAppender; } - updateFollowUps(items: IChatFollowup[], onFollowup: (followup: IChatFollowup) => void): void; + updateFollowUps(items: IInlineChatFollowup[], onFollowup: (followup: IInlineChatFollowup) => void): void; updateFollowUps(items: undefined): void; - updateFollowUps(items: IChatFollowup[] | undefined, onFollowup?: ((followup: IChatFollowup) => void)) { + updateFollowUps(items: IInlineChatFollowup[] | undefined, onFollowup?: ((followup: IInlineChatFollowup) => void)) { this._followUpDisposables.clear(); this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 4a4717a88ba7e..d339e9f5b077f 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { Event } from 'vs/base/common/event'; import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -20,7 +20,6 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { URI } from 'vs/base/common/uri'; export interface IInlineChatSlashCommand { @@ -98,6 +97,23 @@ export const enum InlineChatResponseFeedbackKind { Bug = 4 } +export interface IInlineChatReplyFollowup { + kind: 'reply'; + message: string; + title?: string; + tooltip?: string; +} + +export interface IInlineChatCommandFollowup { + kind: 'command'; + commandId: string; + args?: any[]; + title: string; // supports codicon strings + when?: string; +} + +export type IInlineChatFollowup = IInlineChatReplyFollowup | IInlineChatCommandFollowup; + export interface IInlineChatSessionProvider { debugName: string; @@ -108,7 +124,7 @@ export interface IInlineChatSessionProvider { provideResponse(item: IInlineChatSession, request: IInlineChatRequest, progress: IProgress, token: CancellationToken): ProviderResult; - provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; + provideFollowups?(session: IInlineChatSession, response: IInlineChatResponse, token: CancellationToken): ProviderResult; handleInlineChatResponseFeedback?(session: IInlineChatSession, response: IInlineChatResponse, kind: InlineChatResponseFeedbackKind): void; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index bbdfe82206a0f..cacd23e912f81 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; /** * The result that was received from the chat agent. @@ -173,19 +173,10 @@ declare module 'vscode' { provideSubCommands(token: CancellationToken): ProviderResult; } - // TODO@API This should become a progress type, and use vscode.Command - // TODO@API what's the when-property for? how about not returning it in the first place? - export interface ChatAgentCommandFollowup { - commandId: string; - args?: any[]; - title: string; // supports codicon strings - when?: string; - } - /** * A followup question suggested by the model. */ - export interface ChatAgentReplyFollowup { + export interface ChatAgentFollowup { /** * The message to send to the chat. */ @@ -202,8 +193,6 @@ declare module 'vscode' { title?: string; } - export type ChatAgentFollowup = ChatAgentCommandFollowup | ChatAgentReplyFollowup; - /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ @@ -345,6 +334,15 @@ declare module 'vscode' { */ anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + /** + * Push a command button part to this stream. Short-hand for + * `push(new ChatResponseCommandButtonPart(value, title))`. + * + * @param command A Command that will be executed when the button is clicked. + * @returns This stream. + */ + button(command: Command): ChatAgentResponseStream; + /** * Push a filetree part to this stream. Short-hand for * `push(new ChatResponseFileTreePart(value))`. @@ -437,8 +435,13 @@ declare module 'vscode' { constructor(value: Uri | Location); } + export class ChatResponseCommandButtonPart { + value: Command; + constructor(value: Command); + } + export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart - | ChatResponseProgressPart | ChatResponseReferencePart; + | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; /** * @deprecated use ChatAgentResponseStream instead @@ -446,7 +449,8 @@ declare module 'vscode' { export type ChatAgentContentProgress = | ChatAgentContent | ChatAgentFileTree - | ChatAgentInlineContentReference; + | ChatAgentInlineContentReference + | ChatAgentCommandButton; /** * @deprecated use ChatAgentResponseStream instead @@ -493,6 +497,13 @@ declare module 'vscode' { title?: string; } + /** + * Displays a {@link Command command} as a button in the chat response. + */ + export interface ChatAgentCommandButton { + command: Command; + } + /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 5d0a7b1afddb3..0b0b59c7c5544 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -118,7 +118,7 @@ declare module 'vscode' { export interface ChatAgentCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - command: ChatAgentCommandFollowup; + commandButton: ChatAgentCommandButton; } export interface ChatAgentSessionFollowupAction { diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index 333783f844af7..e40a66c044c76 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -9,7 +9,7 @@ declare module 'vscode' { export interface ChatAgentWelcomeMessageProvider { provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } export interface ChatAgent2 { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index f331fd8834380..bd580590d68f6 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -125,7 +125,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentReplyFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From 2c40be183e31f3d2d159673ecda11a4f22dbf78a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 14:56:31 +0100 Subject: [PATCH 0092/1863] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index cec3df65409fa..72fe5af468e6b 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -76,13 +76,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - this.phrases.set(agentPhrase, agentResult); + phrases.set(agentPhrase, agentResult); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - this.phrases.set(slashCommandPhrase, slashCommandResult); + phrases.set(slashCommandPhrase, slashCommandResult); } } } @@ -120,7 +120,9 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { waitingForInput = originalWords.length === 4; } - finishedPhraseDetection = true; // only detect phrases in the beginning of the session + if (e.status === SpeechToTextStatus.Recognized) { + finishedPhraseDetection = true; // only detect phrases in the beginning of the session + } } // Check for agent From de731a945992dae885190077ef337c1131650036 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 15:08:13 +0100 Subject: [PATCH 0093/1863] . --- src/vs/workbench/contrib/chat/common/voiceChat.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 72fe5af468e6b..84a511d42e5ba 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -55,7 +55,14 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private phrases = this.createPhrases(); + private _phrases: Map | undefined = undefined; + private get phrases(): Map { + if (!this._phrases) { + this._phrases = this.createPhrases(); + } + + return this._phrases; + } constructor( @ISpeechService private readonly speechService: ISpeechService, @@ -67,7 +74,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } private registerListeners(): void { - this._register(this.chatAgentService.onDidChangeAgents(() => this.phrases = this.createPhrases())); + this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); } private createPhrases(): Map { From c4455a4d5cdea06e1f532370811f3399de090778 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 9 Feb 2024 12:19:42 +0100 Subject: [PATCH 0094/1863] Code cleanup --- .../browser/inlineCompletionsModel.ts | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index dcd0dec6cac08..6657b08d18ac8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -194,11 +194,11 @@ export class InlineCompletionsModel extends Disposable { }); public readonly state = derivedOpts<{ - suggestItem: SuggestItemInfo | undefined; - inlineCompletion: InlineCompletionWithUpdatedRange | undefined; + edits: readonly SingleTextEdit[]; primaryGhostText: GhostTextOrReplacement; ghostTexts: readonly GhostTextOrReplacement[]; - edits: SingleTextEdit[]; + suggestItem: SuggestItemInfo | undefined; + inlineCompletion: InlineCompletionWithUpdatedRange | undefined; } | undefined>({ owner: this, equalityComparer: (a, b) => { @@ -212,29 +212,29 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { - const suggestCompletion = suggestItem.toSingleTextEdit().removeCommonPrefix(model); - const augmentedCompletion = this._computeAugmentedCompletion(suggestCompletion, reader); + const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); + const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); const isSuggestionPreviewEnabled = this._suggestPreviewEnabled.read(reader); - if (!isSuggestionPreviewEnabled && !augmentedCompletion) { return undefined; } + if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } - const edit = augmentedCompletion?.edit ?? suggestCompletion; - const editPreviewLength = augmentedCompletion ? augmentedCompletion.edit.text.length - suggestCompletion.text.length : 0; + const fullEdit = augmentation?.edit ?? suggestCompletionEdit; + const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); - const edits = [edit, ...this._getSecondaryEdits(this.textModel, positions, edit)]; + const edits = [fullEdit, ...this._getSecondaryEdits(this.textModel, positions, fullEdit)]; const ghostTexts = edits - .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], editPreviewLength)) + .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); - const primaryGhostText = ghostTexts[0] ?? new GhostText(edit.range.endLineNumber, []); - return { ghostTexts, primaryGhostText, inlineCompletion: augmentedCompletion?.completion, suggestItem, edits }; + const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); + return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { if (!this._isActive.read(reader)) { return undefined; } - const item = this.selectedInlineCompletion.read(reader); - if (!item) { return undefined; } + const inlineCompletion = this.selectedInlineCompletion.read(reader); + if (!inlineCompletion) { return undefined; } - const replacement = item.toSingleTextEdit(reader); + const replacement = inlineCompletion.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); const positions = this._positions.read(reader); const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; @@ -242,11 +242,11 @@ export class InlineCompletionsModel extends Disposable { .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } - return { ghostTexts, primaryGhostText: ghostTexts[0], inlineCompletion: item, suggestItem: undefined, edits }; + return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); - private _computeAugmentedCompletion(suggestCompletion: SingleTextEdit, reader: IReader | undefined) { + private _computeAugmentation(suggestCompletion: SingleTextEdit, reader: IReader | undefined) { const model = this.textModel; const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.read(reader); const candidateInlineCompletions = suggestWidgetInlineCompletions @@ -256,7 +256,7 @@ export class InlineCompletionsModel extends Disposable { const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => { let r = completion.toSingleTextEdit(reader); r = r.removeCommonPrefix(model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())); - return r.augments(suggestCompletion) ? { edit: r, completion } : undefined; + return r.augments(suggestCompletion) ? { completion, edit: r } : undefined; }); return augmentedCompletion; @@ -411,16 +411,17 @@ export class InlineCompletionsModel extends Disposable { } const firstPart = ghostText.parts[0]; - const position = new Position(ghostText.lineNumber, firstPart.column); - const text = firstPart.text; - const acceptUntilIndexExclusive = getAcceptUntilIndex(position, text); - - if (acceptUntilIndexExclusive === text.length && ghostText.parts.length === 1) { + const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); + const ghostTextVal = firstPart.text; + const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); + if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } + const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - const partialText = text.substring(0, acceptUntilIndexExclusive); + const positions = this._positions.get(); + const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. completion.source.addRef(); @@ -428,13 +429,10 @@ export class InlineCompletionsModel extends Disposable { this._isAcceptingPartially = true; try { editor.pushUndoStop(); - const replaceRange = Range.fromPositions(completion.range.getStartPosition(), position); - const newText = completion.insertText.substring( - 0, - firstPart.column - completion.range.startColumn + acceptUntilIndexExclusive); - const singleTextEdit = new SingleTextEdit(replaceRange, newText); - const positions = this._positions.get(); - const edits = [singleTextEdit, ...this._getSecondaryEdits(this.textModel, positions, singleTextEdit)]; + const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); + const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; + const primaryEdit = new SingleTextEdit(replaceRange, newText); + const edits = [primaryEdit, ...this._getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); editor.setSelections(selections, 'inlineCompletionPartialAccept'); @@ -443,7 +441,7 @@ export class InlineCompletionsModel extends Disposable { } if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(position, lengthOfText(partialText))); + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(ghostTextPos, lengthOfText(partialGhostTextVal))); // This assumes that the inline completion and the model use the same EOL style. const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); completion.source.provider.handlePartialAccept( @@ -458,7 +456,6 @@ export class InlineCompletionsModel extends Disposable { } private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); const replacedTextAfterPrimaryCursor = textModel @@ -477,7 +474,7 @@ export class InlineCompletionsModel extends Disposable { public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); - const augmentedCompletion = this._computeAugmentedCompletion(itemEdit, undefined); + const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); if (!augmentedCompletion) { return; } const inlineCompletion = augmentedCompletion.completion.inlineCompletion; From 80ef615f0b7f8d405d6774149a2e3052463076e6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 14:54:29 +0100 Subject: [PATCH 0095/1863] adding readonly --- .../contrib/inlineCompletions/browser/inlineCompletionsModel.ts | 2 +- src/vs/editor/contrib/inlineCompletions/browser/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 6657b08d18ac8..2faf2a0f8a911 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -486,7 +486,7 @@ export class InlineCompletionsModel extends Disposable { } } -function getEndPositionsAfterApplying(edits: SingleTextEdit[]): Position[] { +function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); const newRanges = sortPerm.inverse().apply(sortedNewRanges); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index cc24afd0eff6d..805de9a781a07 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -166,7 +166,7 @@ export class Permutation { return new Permutation(sortIndices); } - apply(arr: T[]): T[] { + apply(arr: readonly T[]): T[] { return arr.map((_, index) => arr[this._indexMap[index]]); } From c8d9a52cc67cb9592b30ff03b3ac4cbe5a4513ca Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 15:26:44 +0100 Subject: [PATCH 0096/1863] . --- .../contrib/chat/common/voiceChat.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 84a511d42e5ba..b3219d405a7fa 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { startsWithIgnoreCase } from 'vs/base/common/strings'; +import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -100,7 +100,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { createVoiceChatSession(token: CancellationToken): IVoiceChatSession { const disposables = new DisposableStore(); - let finishedPhraseDetection = false; + let detectedAgent = false; + let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); const session = disposables.add(this.speechService.createSpeechToTextSession(token)); @@ -109,7 +110,6 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if ( - !finishedPhraseDetection && // only if we have not yet attempted phrase detection e.text && startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) ) { @@ -119,26 +119,31 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let waitingForInput = false; // Check for slash command - if (originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).join(' ').toLowerCase()); + if (!detectedSlashCommand && originalWords.length >= 4) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (slashCommandResult) { transformedWords = [slashCommandResult, ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; - } - if (e.status === SpeechToTextStatus.Recognized) { - finishedPhraseDetection = true; // only detect phrases in the beginning of the session + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + detectedSlashCommand = true; + } } } - // Check for agent - if (!transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).join(' ').toLowerCase()); + // Check for agent (if not done already) + if (!detectedAgent && !transformedWords && originalWords.length >= 2) { + const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (agentResult) { transformedWords = [agentResult, ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; + + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + } } } @@ -161,4 +166,12 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { dispose: () => disposables.dispose() }; } + + private normalizeWord(word: string): string { + word = rtrim(word, '.'); + word = rtrim(word, ','); + word = rtrim(word, '?'); + + return word.toLowerCase(); + } } From bac6edbb5559f24268a84faa6261406741db3045 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:37:25 +0100 Subject: [PATCH 0097/1863] . --- .../chat/test/common/voiceChat.test.ts | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts new file mode 100644 index 0000000000000..0d7504e25c001 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -0,0 +1,196 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ProviderResult } from 'vs/editor/common/languages'; +import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; +import { VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; + +suite('VoiceChat', () => { + + class TestChatAgentCommand implements IChatAgentCommand { + constructor(readonly name: string, readonly description: string) { } + } + + class TestChatAgent implements IChatAgent { + constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } + invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } + metadata = {}; + } + + const agents: IChatAgent[] = [ + new TestChatAgent('workspace', [ + new TestChatAgentCommand('fix', 'fix'), + new TestChatAgentCommand('explain', 'explain') + ]), + new TestChatAgent('vscode', [ + new TestChatAgentCommand('search', 'search') + ]), + ]; + + class TestChatAgentService implements IChatAgentService { + _serviceBrand: undefined; + readonly onDidChangeAgents = Event.None; + registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } + invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { throw new Error(); } + getAgents(): Array { return agents; } + getAgent(id: string): IChatAgent | undefined { throw new Error(); } + getDefaultAgent(): IChatAgent | undefined { throw new Error(); } + getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } + hasAgent(id: string): boolean { throw new Error(); } + updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); } + } + + class TestSpeechService implements ISpeechService { + _serviceBrand: undefined; + + onDidRegisterSpeechProvider = Event.None; + onDidUnregisterSpeechProvider = Event.None; + + readonly hasSpeechProvider = true; + readonly hasActiveSpeechToTextSession = false; + readonly hasActiveKeywordRecognition = false; + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { throw new Error('Method not implemented.'); } + onDidStartSpeechToTextSession = Event.None; + onDidEndSpeechToTextSession = Event.None; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + return { + onDidChange: emitter.event, + dispose: () => { } + }; + } + + onDidStartKeywordRecognition = Event.None; + onDidEndKeywordRecognition = Event.None; + recognizeKeyword(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + } + + const disposables = new DisposableStore(); + const emitter = disposables.add(new Emitter()); + + teardown(() => { + disposables.clear(); + }); + + test('Agent and slash command detection', async () => { + const service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); + + let event: ISpeechToTextEvent | undefined; + let session: ISpeechToTextSession | undefined; + + function createSession() { + session?.dispose(); + + session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + disposables.add(session.onDidChange(e => { + event = e; + })); + } + + // Nothing to detect + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Started }); + assert.strictEqual(event?.status, SpeechToTextStatus.Started); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'Hello'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello World' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'Hello World'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Hello World' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, 'Hello World'); + + // Simple detection: Agent + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'At'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace'); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + // Simple detection: Agent with punctuation + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace help'); + + // Simple detection: Slash Command + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + + // Simple detection: Slash Command with punctuation + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@vscode /search help'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@vscode /search help'); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); +}); From d1f8fbceee80824ffa96abfaabfc0908253159d0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:40:33 +0100 Subject: [PATCH 0098/1863] . --- .../chat/test/common/voiceChat.test.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 0d7504e25c001..69391ca4df6e6 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -119,7 +119,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, 'Hello World'); - // Simple detection: Agent + // Agent createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); @@ -138,7 +138,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); - // Simple detection: Agent with punctuation + // Agent with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); @@ -159,7 +159,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); - // Simple detection: Slash Command + // Slash Command createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); @@ -170,7 +170,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); - // Simple detection: Slash Command with punctuation + // Slash Command with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); @@ -190,6 +190,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + + // Agent not detected twice + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace for at workspace'); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace for at workspace'); }); ensureNoDisposablesAreLeakedInTestSuite(); From 5c3082d584778ce6b695cee29b2d81fe8b9fe8d6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 16:40:42 +0100 Subject: [PATCH 0099/1863] remove N-1 compatibility trick (#204833) --- src/vs/workbench/api/common/extHostChatProvider.ts | 6 +----- src/vscode-dts/vscode.proposed.chatProvider.d.ts | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index be14e5557ec7c..b7a91c72e35b5 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -134,11 +134,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - if (data.provider.provideLanguageModelResponse) { - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } else { - return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); - } + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); } //#region --- making request diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 1d218bc8e004d..226416480c052 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -16,9 +16,7 @@ declare module 'vscode' { * Represents a large language model that accepts ChatML messages and produces a streaming response */ export interface ChatResponseProvider { - provideChatResponse(messages: ChatMessage[], options: { [name: string]: any }, progress: Progress, token: CancellationToken): Thenable; - - provideLanguageModelResponse?(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { From 5b8fe0ed3e34df8075a0e619fad72087cd6b6ddf Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 9 Feb 2024 16:47:22 +0100 Subject: [PATCH 0100/1863] Fix smoke test (#204836) fix smoke test --- test/automation/src/extensions.ts | 7 ++++++- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 1d0f97dadf346..41dc79d442da8 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -41,7 +41,12 @@ export class Extensions extends Viewlet { } async closeExtension(title: string): Promise { - await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + try { + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}, preview"] div.tab-actions a.action-label.codicon.codicon-close`); + } catch (e) { + this.code.logger.log(`Extension '${title}' not opened as preview. Trying without 'preview'.`); + await this.code.waitAndClick(`.tabs-container div.tab[aria-label="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + } } async installExtension(id: string, waitUntilEnabled: boolean): Promise { diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 8a7522c6ea77e..c78cbe8708973 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -12,7 +12,7 @@ export function setup(logger: Logger) { // Shared before/after handling installAllHandlers(logger); - it.skip('install and enable vscode-smoketest-check extension', async function () { + it('install and enable vscode-smoketest-check extension', async function () { const app = this.app as Application; await app.workbench.extensions.installExtension('ms-vscode.vscode-smoketest-check', true); From b59e74c076d1f59cfadabe2d5eab6b52fa4553a4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 16:47:48 +0100 Subject: [PATCH 0101/1863] . --- .../chat/test/common/voiceChat.test.ts | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 69391ca4df6e6..d844be3c5c822 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; suite('VoiceChat', () => { @@ -80,26 +80,32 @@ suite('VoiceChat', () => { } const disposables = new DisposableStore(); - const emitter = disposables.add(new Emitter()); + let emitter: Emitter; + + let service: VoiceChatService; + let event: IVoiceChatTextEvent | undefined; + let session: ISpeechToTextSession | undefined; + + function createSession() { + session?.dispose(); + + session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + disposables.add(session.onDidChange(e => { + event = e; + })); + } + + + setup(() => { + emitter = disposables.add(new Emitter()); + service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); + }); teardown(() => { disposables.clear(); }); test('Agent and slash command detection', async () => { - const service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); - - let event: ISpeechToTextEvent | undefined; - let session: ISpeechToTextSession | undefined; - - function createSession() { - session?.dispose(); - - session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); - disposables.add(session.onDidChange(e => { - event = e; - })); - } // Nothing to detect createSession(); @@ -110,14 +116,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, 'Hello'); + assert.strictEqual(event?.waitingForInput, undefined); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Hello World' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, 'Hello World'); + assert.strictEqual(event?.waitingForInput, undefined); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Hello World' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, 'Hello World'); + assert.strictEqual(event?.waitingForInput, undefined); // Agent createSession(); @@ -129,14 +138,17 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); // Agent with punctuation createSession(); @@ -144,20 +156,24 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.waitingForInput, false); // Slash Command createSession(); @@ -165,10 +181,12 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); // Slash Command with punctuation createSession(); @@ -176,20 +194,24 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.waitingForInput, false); // Agent not detected twice createSession(); @@ -197,10 +219,41 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.waitingForInput, false); + }); + + test('waiting for input', async () => { + + // Agent + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event.waitingForInput, true); + + // Slash Command + createSession(); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '@workspace /explain'); + assert.strictEqual(event.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace slash explain' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace /explain'); + assert.strictEqual(event.waitingForInput, true); }); ensureNoDisposablesAreLeakedInTestSuite(); From 7a54628f38cb9cf30c2e325a34765332c52ef6ac Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 9 Feb 2024 17:00:54 +0100 Subject: [PATCH 0102/1863] Move old content API types out (#204839) * api - move old content/progress API types out https://github.com/microsoft/vscode/issues/199908 * update docs --- .../vscode.proposed.chatAgents2.d.ts | 129 +----------------- .../vscode.proposed.chatAgents2Additions.d.ts | 108 ++++++++++++++- 2 files changed, 106 insertions(+), 131 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index cacd23e912f81..616f85ecc1788 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; + response: (ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; /** * The result that was received from the chat agent. @@ -384,11 +384,6 @@ declare module 'vscode' { * @param part A response part, rendered or metadata */ push(part: ChatResponsePart): ChatAgentResponseStream; - - /** - * @deprecated use above methods instread - */ - report(value: ChatAgentProgress): void; } // TODO@API @@ -443,128 +438,6 @@ declare module 'vscode' { export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentContentProgress = - | ChatAgentContent - | ChatAgentFileTree - | ChatAgentInlineContentReference - | ChatAgentCommandButton; - - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentMetadataProgress = - | ChatAgentUsedContext - | ChatAgentContentReference - | ChatAgentProgressMessage; - - /** - * @deprecated use ChatAgentResponseStream instead - */ - export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; - - /** - * Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM. - */ - export interface ChatAgentProgressMessage { - message: string; - } - - /** - * Indicates a piece of content that was used by the chat agent while processing the request. Will be displayed to the user. - */ - export interface ChatAgentContentReference { - /** - * The resource that was referenced. - */ - reference: Uri | Location; - } - - /** - * A reference to a piece of content that will be rendered inline with the markdown content. - */ - export interface ChatAgentInlineContentReference { - /** - * The resource being referenced. - */ - inlineReference: Uri | Location; - - /** - * An alternate title for the resource. - */ - title?: string; - } - - /** - * Displays a {@link Command command} as a button in the chat response. - */ - export interface ChatAgentCommandButton { - command: Command; - } - - /** - * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. - */ - export interface ChatAgentContent { - /** - * The content as a string of markdown source. - */ - content: string; - } - - /** - * Represents a tree, such as a file and directory structure, rendered in the chat response. - */ - export interface ChatAgentFileTree { - /** - * The root node of the tree. - */ - treeData: ChatAgentFileTreeData; - } - - /** - * Represents a node in a chat response tree. - */ - export interface ChatAgentFileTreeData { - /** - * A human-readable string describing this node. - */ - label: string; - - /** - * A Uri for this node, opened when it's clicked. - */ - // TODO@API why label and uri. Can the former be derived from the latter? - // TODO@API don't use uri but just names? This API allows to to build nonsense trees where the data structure doesn't match the uris - // path-structure. - uri: Uri; - - /** - * The type of this node. Defaults to {@link FileType.Directory} if it has {@link ChatAgentFileTreeData.children children}. - */ - // TODO@API cross API usage - type?: FileType; - - /** - * The children of this node. - */ - children?: ChatAgentFileTreeData[]; - } - - export interface ChatAgentDocumentContext { - uri: Uri; - version: number; - ranges: Range[]; - } - - /** - * Document references that should be used by the MappedEditsProvider. - */ - export interface ChatAgentUsedContext { - documents: ChatAgentDocumentContext[]; - } // TODO@API Remove a different type of `request` so that they can // evolve at their own pace diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 0b0b59c7c5544..49924b8ff0c4f 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -17,28 +17,130 @@ declare module 'vscode' { responseIsRedacted?: boolean; } - /** - * This is temporary until inline references are fully supported and adopted - */ + /** @deprecated */ export interface ChatAgentMarkdownContent { markdownContent: MarkdownString; } + // TODO@API fit this into the stream export interface ChatAgentDetectedAgent { agentName: string; command?: ChatAgentSubCommand; } + // TODO@API fit this into the stream export interface ChatAgentVulnerability { title: string; description: string; // id: string; // Later we will need to be able to link these across multiple content chunks. } + // TODO@API fit this into the stream export interface ChatAgentContent { vulnerabilities?: ChatAgentVulnerability[]; } + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentContentProgress = + | ChatAgentContent + | ChatAgentFileTree + | ChatAgentInlineContentReference + | ChatAgentCommandButton; + + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentMetadataProgress = + | ChatAgentUsedContext + | ChatAgentContentReference + | ChatAgentProgressMessage; + + /** + * @deprecated use ChatAgentResponseStream instead + */ + export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + + /** @deprecated */ + export interface ChatAgentProgressMessage { + message: string; + } + + /** @deprecated */ + + export interface ChatAgentContentReference { + /** + * The resource that was referenced. + */ + reference: Uri | Location; + } + + /** + * A reference to a piece of content that will be rendered inline with the markdown content. + */ + export interface ChatAgentInlineContentReference { + /** + * The resource being referenced. + */ + inlineReference: Uri | Location; + + /** + * An alternate title for the resource. + */ + title?: string; + } + + /** + * Displays a {@link Command command} as a button in the chat response. + */ + export interface ChatAgentCommandButton { + command: Command; + } + + /** + * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. + */ + export interface ChatAgentContent { + /** + * The content as a string of markdown source. + */ + content: string; + } + + /** @deprecated */ + export interface ChatAgentFileTree { + treeData: ChatAgentFileTreeData; + } + + /** @deprecated */ + export interface ChatAgentFileTreeData { + label: string; + uri: Uri; + type?: FileType; + children?: ChatAgentFileTreeData[]; + } + + + export interface ChatAgentDocumentContext { + uri: Uri; + version: number; + ranges: Range[]; + } + + // TODO@API fit this into the stream + export interface ChatAgentUsedContext { + documents: ChatAgentDocumentContext[]; + } + + export interface ChatAgentResponseStream { + /** + * @deprecated use above methods instread + */ + report(value: ChatAgentProgress): void; + } + + /** @deprecated */ export type ChatAgentExtendedProgress = ChatAgentProgress | ChatAgentMarkdownContent | ChatAgentDetectedAgent; From dad5b331eaee2d4e7f87061b412097c21e98ef81 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:07:17 -0600 Subject: [PATCH 0103/1863] 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 ffe0e56a1890a..56ebf9ddae51b 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 488815cf2589b..a6ea80d22d680 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 fc925b646bb1c..0579628b52163 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 72335480aee0a..1de53d6193085 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 b49aa829f7bf6..e9cdc25321298 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 0000000000000..bb9cbd24540cb --- /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 0000000000000..5b44b0a0cf7e8 --- /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 0104/1863] 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 bb9cbd24540cb..0f80320012996 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 5b44b0a0cf7e8..b6dbcfa67ee40 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 7bde02eb590c0f2cb4c6146f2c152315f55a8aa4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 17:31:29 +0100 Subject: [PATCH 0105/1863] . --- .../contrib/chat/common/voiceChat.ts | 95 +++++++++++-------- .../chat/test/common/voiceChat.test.ts | 15 ++- 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index b3219d405a7fa..9b974c89b5585 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -87,9 +87,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${agentPhrase} ${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); - const slashCommandResult = `${agentResult} ${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; + const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); + const slashCommandResult = `${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; phrases.set(slashCommandPhrase, slashCommandResult); + + const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); + const agentSlashCommandResult = `${agentResult} ${slashCommandResult}`; + phrases.set(agentSlashCommandPhrase, agentSlashCommandResult); } } } @@ -109,51 +113,66 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { switch (e.status) { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: - if ( - e.text && - startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()) - ) { - const originalWords = e.text.split(' '); - let transformedWords: string[] | undefined; - - let waitingForInput = false; - - // Check for slash command - if (!detectedSlashCommand && originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(4)]; - - waitingForInput = originalWords.length === 4; - - if (e.status === SpeechToTextStatus.Recognized) { - detectedAgent = true; - detectedSlashCommand = true; + if (e.text) { + const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()); + const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX].trim()); + if (startsWithAgent || startsWithSlashCommand) { + const originalWords = e.text.split(' '); + let transformedWords: string[] | undefined; + + let waitingForInput = false; + + // Check for agent + slash command + if (startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + if (slashCommandResult) { + transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + + waitingForInput = originalWords.length === 4; + + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + detectedSlashCommand = true; + } } } - } - // Check for agent (if not done already) - if (!detectedAgent && !transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (agentResult) { - transformedWords = [agentResult, ...originalWords.slice(2)]; + // Check for agent (if not done already) + if (startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { + const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (agentResult) { + transformedWords = [agentResult, ...originalWords.slice(2)]; - waitingForInput = originalWords.length === 2; + waitingForInput = originalWords.length === 2; - if (e.status === SpeechToTextStatus.Recognized) { - detectedAgent = true; + if (e.status === SpeechToTextStatus.Recognized) { + detectedAgent = true; + } } } - } - emitter.fire({ - status: e.status, - text: (transformedWords ?? originalWords).join(' '), - waitingForInput - }); + // Check for slash command (if not done already) + if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { + const slashCommandResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (slashCommandResult) { + transformedWords = [slashCommandResult, ...originalWords.slice(2)]; - break; + waitingForInput = originalWords.length === 2; + + if (e.status === SpeechToTextStatus.Recognized) { + detectedSlashCommand = true; + } + } + } + + emitter.fire({ + status: e.status, + text: (transformedWords ?? originalWords).join(' '), + waitingForInput + }); + + break; + } } default: emitter.fire(e); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d844be3c5c822..2f87f56857d34 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -178,6 +178,19 @@ suite('VoiceChat', () => { // Slash Command createSession(); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + // Agent + Slash Command + createSession(); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, '@vscode /search help'); @@ -188,7 +201,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, '@vscode /search help'); assert.strictEqual(event?.waitingForInput, false); - // Slash Command with punctuation + // Agent + Slash Command with punctuation createSession(); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); From ab4a6959f7298fbd463daf364309350e16c40812 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 10:44:29 -0600 Subject: [PATCH 0106/1863] 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 d30d230a2b71e..ec8c7cd1c474f 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 0f80320012996..46bef94963cc9 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 b6dbcfa67ee40..cb2db4bb1bef9 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 0107/1863] 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 a6ea80d22d680..8fc9e6be0ebb5 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 cb2db4bb1bef9..4f0c787fc6b49 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 d06f94f18e302ff3e301a5fc81fcd1c429f4afdc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 18:06:07 +0100 Subject: [PATCH 0108/1863] Async queue timeout (fix #204772) (#204842) --- src/vs/base/test/common/async.test.ts | 42 ++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 4c2e74ce39b13..f5bb0232a7f7b 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -651,26 +651,28 @@ suite('Async', () => { }); test('order is kept', function () { - const queue = new async.Queue(); - - const res: number[] = []; - - const f1 = () => Promise.resolve(true).then(() => res.push(1)); - const f2 = () => async.timeout(10).then(() => res.push(2)); - const f3 = () => Promise.resolve(true).then(() => res.push(3)); - const f4 = () => async.timeout(20).then(() => res.push(4)); - const f5 = () => async.timeout(0).then(() => res.push(5)); - - queue.queue(f1); - queue.queue(f2); - queue.queue(f3); - queue.queue(f4); - return queue.queue(f5).then(() => { - assert.strictEqual(res[0], 1); - assert.strictEqual(res[1], 2); - assert.strictEqual(res[2], 3); - assert.strictEqual(res[3], 4); - assert.strictEqual(res[4], 5); + return runWithFakedTimers({}, () => { + const queue = new async.Queue(); + + const res: number[] = []; + + const f1 = () => Promise.resolve(true).then(() => res.push(1)); + const f2 = () => async.timeout(10).then(() => res.push(2)); + const f3 = () => Promise.resolve(true).then(() => res.push(3)); + const f4 = () => async.timeout(20).then(() => res.push(4)); + const f5 = () => async.timeout(0).then(() => res.push(5)); + + queue.queue(f1); + queue.queue(f2); + queue.queue(f3); + queue.queue(f4); + return queue.queue(f5).then(() => { + assert.strictEqual(res[0], 1); + assert.strictEqual(res[1], 2); + assert.strictEqual(res[2], 3); + assert.strictEqual(res[3], 4); + assert.strictEqual(res[4], 5); + }); }); }); 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 0109/1863] 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 56ebf9ddae51b..6d291481c7f45 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 ac0903b8ae3eb..397ff3bc97ac2 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 46bef94963cc9..c0ee3d62f1bcc 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 8220518134e8fa144941f5c0ee19cd30a6504acf Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:35:14 +0100 Subject: [PATCH 0110/1863] fixing bug in get edits generation --- .../browser/inlineCompletionsModel.ts | 109 ++++++++++++++---- .../browser/inlineCompletionsModel.test.ts | 63 ++++++++++ 2 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 2faf2a0f8a911..b23c5718822da 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength } from 'vs/base/common/strings'; +import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -212,6 +212,7 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { + console.log('first if statement'); const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); @@ -219,29 +220,37 @@ export class InlineCompletionsModel extends Disposable { if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } const fullEdit = augmentation?.edit ?? suggestCompletionEdit; + console.log('fullEdit : ', fullEdit); const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); - const edits = [fullEdit, ...this._getSecondaryEdits(this.textModel, positions, fullEdit)]; + const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; + console.log('edits : ', edits); const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); + console.log('primaryGhostText : ', primaryGhostText); + console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { + console.log('second else statement'); if (!this._isActive.read(reader)) { return undefined; } const inlineCompletion = this.selectedInlineCompletion.read(reader); + console.log('inlineCompletion : ', inlineCompletion); if (!inlineCompletion) { return undefined; } const replacement = inlineCompletion.toSingleTextEdit(reader); const mode = this._inlineSuggestMode.read(reader); const positions = this._positions.read(reader); - const edits = [replacement, ...this._getSecondaryEdits(this.textModel, positions, replacement)]; + const edits = [replacement, ...getSecondaryEdits(this.textModel, positions, replacement)]; const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } + console.log('primaryGhostText : ', ghostTexts[0]); + console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); @@ -402,6 +411,7 @@ export class InlineCompletionsModel extends Disposable { return; } const ghostText = state.primaryGhostText; + console.log('ghostText : ', ghostText); const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -413,13 +423,16 @@ export class InlineCompletionsModel extends Disposable { const firstPart = ghostText.parts[0]; const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); const ghostTextVal = firstPart.text; + console.log('ghostTextPos : ', ghostTextPos); + console.log('ghostTextVal : ', ghostTextVal); const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); + console.log('acceptUntilIndexExclusive : ', acceptUntilIndexExclusive); if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - + console.log('partialGhostTextVal : ', partialGhostTextVal); const positions = this._positions.get(); const cursorPosition = positions[0]; @@ -430,9 +443,12 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); + console.log('replaceRange : ', replaceRange); const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; + console.log('newText : ', newText); const primaryEdit = new SingleTextEdit(replaceRange, newText); - const edits = [primaryEdit, ...this._getSecondaryEdits(this.textModel, positions, primaryEdit)]; + console.log('primaryEdit : ', primaryEdit); + const edits = [primaryEdit, ...getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); editor.setSelections(selections, 'inlineCompletionPartialAccept'); @@ -455,23 +471,6 @@ export class InlineCompletionsModel extends Disposable { } } - private _getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { - const primaryPosition = positions[0]; - const secondaryPositions = positions.slice(1); - const replacedTextAfterPrimaryCursor = textModel - .getLineContent(primaryPosition.lineNumber) - .substring(primaryPosition.column - 1, primaryEdit.range.endColumn - 1); - const secondaryEditText = primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); - return secondaryPositions.map(pos => { - const textAfterSecondaryCursor = this.textModel - .getLineContent(pos.lineNumber) - .substring(pos.column - 1); - const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - const range = Range.fromPositions(pos, pos.delta(0, l)); - return new SingleTextEdit(range, secondaryEditText); - }); - } - public handleSuggestAccepted(item: SuggestItemInfo) { const itemEdit = item.toSingleTextEdit().removeCommonPrefix(this.textModel); const augmentedCompletion = this._computeAugmentation(itemEdit, undefined); @@ -492,3 +491,69 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio const newRanges = sortPerm.inverse().apply(sortedNewRanges); return newRanges.map(range => range.getEndPosition()); } + +export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { + const primaryPosition = positions[0]; + const secondaryPositions = positions.slice(1); + console.log('positions : ', JSON.stringify(positions)); + // replaced text is not calculated correctly + // only works if primary edit line number + // Need to take all the that is replaced in the primary edit range + + const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); + console.log('replacedTextAfterPrimaryCursor : ', JSON.stringify(replacedTextAfterPrimaryCursor)); + console.log('primaryEdit : ', JSON.stringify(primaryEdit)); + + // There is an error below too, the secondary edit text is the text after the cursor to the right of it, that needs to be added + // in the test case we would want to add ') {\n\treturn 0;\n}' because we already have fib( written. + // Before it worked because we would have the cursor at the end of function fib(, now the cursor is on the line below it + // Or at the very least, we should insert 'return 0;\n}' because this is to the right of the cursor at the primary cursor position + // So need to find the primary position within the edit, and find all the text to the right of it. The primary position will not necessarily be on the first + // line of the edit text. + // We suppose that the primaryEdit.range always touches the primaryPosition in some manner + + // could find the offset of primary position, the offset of the primary edit start and find thus the secondary edit text + // const _offsetPrimaryPosition = textModel.getOffsetAt(primaryPosition); + // const _offsetPrimaryEditStart = textModel.getOffsetAt(primaryEdit.range.getStartPosition()); + // console.log('_offsetPrimaryPosition : ', _offsetPrimaryPosition); + // console.log('_offsetPrimaryEditStart : ', _offsetPrimaryEditStart); + + // Find offset in a different way + // Split the lines of the text, place it in the context of the whole text + // Find the position in the text where the initial position would be, exactly as is, find the offset, and take the substring + + const newCol = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const newLine = newCol === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column; + + let text = ''; + const _splitLines = splitLines(primaryEdit.text); + for (let i = newLine; i < _splitLines.length; i++) { + if (i === newLine) { + text += _splitLines[i].substring(newCol) + '\n'; + } else { + text += _splitLines[i] + '\n'; + } + } + console.log('text : ', text); + + const secondaryEditText = text; + // primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); + // console.log('secondaryEditText : ', JSON.stringify(secondaryEditText)); + return secondaryPositions.map(pos => { + console.log('pos : ', JSON.stringify(pos)); + // Maybe taking the substring on the line content specifically is not enough, so we need to actually take it until the range end, because that is the text we would replace + // the range end is not necessarily on the end of that line either + // const textAfterSecondaryCursor = textModel + // .getLineContent(pos.lineNumber) + // .substring(pos.column - 1); + + const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); + console.log('textAfterSecondaryCursor : ', textAfterSecondaryCursor); + const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); + console.log('l : ', l); + const range = Range.fromPositions(pos, pos.delta(0, l)); + console.log('range : ', JSON.stringify(range)); + return new SingleTextEdit(range, secondaryEditText); + }); +} + diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts new file mode 100644 index 0000000000000..22c3a0c198757 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Position } from 'vs/editor/common/core/position'; +import { getSecondaryEdits } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; +import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; +import { Range } from 'vs/editor/common/core/range'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; + +suite('inlineCompletionModel', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('getSecondaryEdits - basic', async function () { + + const textModel = createTextModel([ + 'function fib(', + 'function fib(' + ].join('\n')); + const positions = [ + new Position(1, 14), + new Position(2, 14) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 1, 14), 'function fib() {'); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(2, 14, 2, 14), + ') {' + )]); + textModel.dispose(); + }); + + test('getSecondaryEdits - cursor not on same line as primary edit', async function () { + + const textModel = createTextModel([ + 'function fib(', + '', + 'function fib(', + '' + ].join('\n')); + const positions = [ + new Position(2, 1), + new Position(4, 1) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 2, 1), [ + 'function fib() {', + ' return 0;', + '}' + ].join('\n')); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(4, 1, 4, 1), [ + ' return 0;', + '}' + ].join('\n') + )]); + console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); + textModel.dispose(); + }); +}); From bb78bb1d393e0acb32c509871732117e8e737003 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:37:19 +0100 Subject: [PATCH 0111/1863] removing some logs --- .../browser/inlineCompletionsModel.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index b23c5718822da..e4d474d8238cb 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -212,7 +212,6 @@ export class InlineCompletionsModel extends Disposable { const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { - console.log('first if statement'); const suggestCompletionEdit = suggestItem.toSingleTextEdit().removeCommonPrefix(model); const augmentation = this._computeAugmentation(suggestCompletionEdit, reader); @@ -220,25 +219,19 @@ export class InlineCompletionsModel extends Disposable { if (!isSuggestionPreviewEnabled && !augmentation) { return undefined; } const fullEdit = augmentation?.edit ?? suggestCompletionEdit; - console.log('fullEdit : ', fullEdit); const fullEditPreviewLength = augmentation ? augmentation.edit.text.length - suggestCompletionEdit.text.length : 0; const mode = this._suggestPreviewMode.read(reader); const positions = this._positions.read(reader); const edits = [fullEdit, ...getSecondaryEdits(this.textModel, positions, fullEdit)]; - console.log('edits : ', edits); const ghostTexts = edits .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], fullEditPreviewLength)) .filter(isDefined); const primaryGhostText = ghostTexts[0] ?? new GhostText(fullEdit.range.endLineNumber, []); - console.log('primaryGhostText : ', primaryGhostText); - console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText, ghostTexts, inlineCompletion: augmentation?.completion, suggestItem }; } else { - console.log('second else statement'); if (!this._isActive.read(reader)) { return undefined; } const inlineCompletion = this.selectedInlineCompletion.read(reader); - console.log('inlineCompletion : ', inlineCompletion); if (!inlineCompletion) { return undefined; } const replacement = inlineCompletion.toSingleTextEdit(reader); @@ -249,8 +242,6 @@ export class InlineCompletionsModel extends Disposable { .map((edit, idx) => edit.computeGhostText(model, mode, positions[idx], 0)) .filter(isDefined); if (!ghostTexts[0]) { return undefined; } - console.log('primaryGhostText : ', ghostTexts[0]); - console.log('ghostTexts : ', ghostTexts); return { edits, primaryGhostText: ghostTexts[0], ghostTexts, inlineCompletion, suggestItem: undefined }; } }); @@ -411,7 +402,6 @@ export class InlineCompletionsModel extends Disposable { return; } const ghostText = state.primaryGhostText; - console.log('ghostText : ', ghostText); const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { @@ -423,16 +413,13 @@ export class InlineCompletionsModel extends Disposable { const firstPart = ghostText.parts[0]; const ghostTextPos = new Position(ghostText.lineNumber, firstPart.column); const ghostTextVal = firstPart.text; - console.log('ghostTextPos : ', ghostTextPos); - console.log('ghostTextVal : ', ghostTextVal); const acceptUntilIndexExclusive = getAcceptUntilIndex(ghostTextPos, ghostTextVal); - console.log('acceptUntilIndexExclusive : ', acceptUntilIndexExclusive); if (acceptUntilIndexExclusive === ghostTextVal.length && ghostText.parts.length === 1) { this.accept(editor); return; } const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); - console.log('partialGhostTextVal : ', partialGhostTextVal); + const positions = this._positions.get(); const cursorPosition = positions[0]; @@ -443,11 +430,8 @@ export class InlineCompletionsModel extends Disposable { try { editor.pushUndoStop(); const replaceRange = Range.fromPositions(cursorPosition, ghostTextPos); - console.log('replaceRange : ', replaceRange); const newText = editor.getModel()!.getValueInRange(replaceRange) + partialGhostTextVal; - console.log('newText : ', newText); const primaryEdit = new SingleTextEdit(replaceRange, newText); - console.log('primaryEdit : ', primaryEdit); const edits = [primaryEdit, ...getSecondaryEdits(this.textModel, positions, primaryEdit)]; const selections = getEndPositionsAfterApplying(edits).map(p => Selection.fromPositions(p)); editor.executeEdits('inlineSuggestion.accept', edits.map(edit => EditOperation.replaceMove(edit.range, edit.text))); From 92143ea2ddfba4a0aea73f19b8a4374e18da1992 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 9 Feb 2024 18:40:01 +0100 Subject: [PATCH 0112/1863] adding one more test --- .../browser/inlineCompletionsModel.test.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index 22c3a0c198757..c6bc78de063cb 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -33,7 +33,7 @@ suite('inlineCompletionModel', () => { textModel.dispose(); }); - test('getSecondaryEdits - cursor not on same line as primary edit', async function () { + test('getSecondaryEdits - cursor not on same line as primary edit 1', async function () { const textModel = createTextModel([ 'function fib(', @@ -60,4 +60,35 @@ suite('inlineCompletionModel', () => { console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); + + test('getSecondaryEdits - cursor not on same line as primary edit 2', async function () { + + const textModel = createTextModel([ + 'class A {', + '', + 'class B {', + '', + 'function f() {}' + ].join('\n')); + const positions = [ + new Position(2, 1), + new Position(4, 1) + ]; + const primaryEdit = new SingleTextEdit(new Range(1, 1, 2, 1), [ + 'class A {', + ' public x: number = 0;', + ' public y: number = 0;', + '}' + ].join('\n')); + const secondaryEdits = getSecondaryEdits(textModel, positions, primaryEdit); + assert.deepStrictEqual(secondaryEdits, [new SingleTextEdit( + new Range(4, 1, 4, 1), [ + ' public x: number = 0;', + ' public y: number = 0;', + '}' + ].join('\n') + )]); + console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); + textModel.dispose(); + }); }); From 0d202eff3d87ae7cb3e943fb82fa9a98f6aeffcd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 9 Feb 2024 11:46:48 -0600 Subject: [PATCH 0113/1863] 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 c0ee3d62f1bcc..b9fab74a3a27b 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 4f0c787fc6b49..de48c32fcf38c 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 a2f2e5e2c392b6598c03545e93163541381b5c2a Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:51:56 -0600 Subject: [PATCH 0114/1863] fix findFiles useIgnoreFiles (#204845) --- src/vs/workbench/api/common/extHostWorkspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 65cee0c4faab6..d240127a4d456 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -462,7 +462,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac maxResults, useDefaultExcludes: useFileExcludes, useDefaultSearchExcludes: false, - useIgnoreFiles: true + useIgnoreFiles: false }, token); } 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 0115/1863] 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 0000000000000..e81dc6b47523b --- /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 c0ee3d62f1bcc..8881c8f664adc 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 4f0c787fc6b49..76482c5c987e0 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 0116/1863] 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 ee46f6df25e52..862f0183354ba 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 e81dc6b47523b..5c8676bbe6e14 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 c210f1c44944c..b9fab74a3a27b 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 d8c1d5180c763..7c67b161aba85 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 0117/1863] 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 7c67b161aba85..041ec3b9573d8 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 0118/1863] 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 8fc9e6be0ebb5..8fcb3c711720c 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 041ec3b9573d8..89c2518dd8e82 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 0119/1863] 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 b9fab74a3a27b..e54914120bd56 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 89c2518dd8e82..3a99cd5ebefa0 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 a6f0b5cda38e244b3517bdf095f4e49d1404e144 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Fri, 9 Feb 2024 10:46:38 -0800 Subject: [PATCH 0120/1863] Getting Started Accessibility fixes (#204848) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 9f171757fe72c..7c23216426b3d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -269,11 +269,13 @@ export class GettingStartedPage extends EditorPane { const badgeelements = assertIsDefined(getWindow(this.container).document.querySelectorAll(`[data-done-step-id="${step.id}"]`)); badgeelements.forEach(badgeelement => { if (step.done) { + badgeelement.setAttribute('aria-checked', 'true'); badgeelement.parentElement?.setAttribute('aria-checked', 'true'); badgeelement.classList.remove(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); badgeelement.classList.add('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); } else { + badgeelement.setAttribute('aria-checked', 'false'); badgeelement.parentElement?.setAttribute('aria-checked', 'false'); badgeelement.classList.remove('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); badgeelement.classList.add(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); @@ -1400,7 +1402,7 @@ export class GettingStartedPage extends EditorPane { 'x-dispatch': 'selectTask:' + step.id, 'data-step-id': step.id, 'aria-expanded': 'false', - 'aria-checked': '' + step.done, + 'aria-checked': step.done ? 'true' : 'false', 'role': 'button', }, codicon, 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 0121/1863] 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 5c8676bbe6e14..633b6778dfe35 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 0122/1863] 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 376627398acaa..ec2810e91fbe3 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 3b605a096d548..75f3ab3c8321a 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 3a99cd5ebefa0..de24d36312daf 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 0123/1863] 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 de24d36312daf..bef5d94f3d8bf 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 0124/1863] 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 4ccb53b0c804c..7b17465b2d910 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 bef5d94f3d8bf..fc85c9a5a50ab 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 0125/1863] 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 0579628b52163..9a11ee3252c12 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 e54914120bd56..6c44b62619c12 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 0126/1863] 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 9a11ee3252c12..5ed9e674af23b 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 f192601cda71416d2b4a03a2e4e6de4a2e1c9d95 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 9 Feb 2024 11:21:18 -0800 Subject: [PATCH 0127/1863] testing: fix single new child not appearing (#204850) * testing: fix single new child not appearing Fixes #204805 * add test case --- .../browser/explorerProjections/index.ts | 10 ++++- .../explorerProjections/treeProjection.ts | 11 ++++-- .../hierarchalByLocation.test.ts | 38 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts index e3e327ee8c074..29f03a52d113d 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/index.ts @@ -142,7 +142,15 @@ export type TestExplorerTreeElement = TestItemTreeElement | TestTreeErrorMessage export const testIdentityProvider: IIdentityProvider = { getId(element) { - return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand); + // For "not expandable" elements, whether they have children is part of the + // ID so they're rerendered if that changes (#204805) + const expandComponent = element instanceof TestTreeErrorMessage + ? 'error' + : element.test.expand === TestItemExpandState.NotExpandable + ? !!element.children.size + : element.test.expand; + + return element.treeId + '\0' + expandComponent; } }; diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts index 2e0374be9cd0f..d7f385457ae68 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts @@ -302,9 +302,14 @@ export class TreeProjection extends Disposable implements ITestTreeProjection { treeElement.parent?.children.add(treeElement); this.items.set(treeElement.test.item.extId, treeElement); - // The first element will cause the root to be shown - const affectsRootElement = treeElement.depth === 1 && treeElement.parent?.children.size === 1; - this.changedParents.add(affectsRootElement ? null : treeElement.parent); + // The first element will cause the root to be shown. The first element of + // a parent may need to re-render it for #204805. + const affectsParent = treeElement.parent?.children.size === 1; + const affectedParent = affectsParent ? treeElement.parent.parent : treeElement.parent; + this.changedParents.add(affectedParent); + if (affectedParent?.depth === 0) { + this.changedParents.add(null); + } if (treeElement.depth === 0 || isCollapsedInSerializedTestTree(this.lastState, treeElement.test.item.extId) === false) { this.expandElement(treeElement, 0); diff --git a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts index 97b6ef85520b9..cebf9e01a5dac 100644 --- a/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts +++ b/src/vs/workbench/contrib/testing/test/browser/explorerProjections/hierarchalByLocation.test.ts @@ -229,5 +229,43 @@ suite('Workbench - Testing Explorer Hierarchal by Location Projection', () => { }); + test('fixes #204805', async () => { + harness.flush(); + harness.pushDiff({ + op: TestDiffOpType.Remove, + itemId: 'ctrlId', + }, { + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId']), 'ctrl').toTestItem() }, + }, { + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a']), 'a').toTestItem() }, + }); + + assert.deepStrictEqual(harness.flush(), [ + { e: 'a' } + ]); + + harness.pushDiff({ + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a', 'b']), 'b').toTestItem() }, + }); + harness.flush(); + harness.tree.expandAll(); + assert.deepStrictEqual(harness.tree.getRendered(), [ + { e: 'a', children: [{ e: 'b' }] } + ]); + + harness.pushDiff({ + op: TestDiffOpType.Add, + item: { controllerId: 'ctrlId', expand: TestItemExpandState.NotExpandable, item: new TestTestItem(new TestId(['ctrlId', 'a', 'b', 'c']), 'c').toTestItem() }, + }); + harness.flush(); + harness.tree.expandAll(); + assert.deepStrictEqual(harness.tree.getRendered(), [ + { e: 'a', children: [{ e: 'b', children: [{ e: 'c' }] }] } + ]); + }); + }); From 2697c604f3ccf73757fe16441fde8f9838d59a3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 9 Feb 2024 20:34:21 +0100 Subject: [PATCH 0128/1863] voice - improve phrase detection (#204855) --- .../contrib/chat/common/voiceChat.ts | 85 ++++++++++++------- .../actions/voiceChatActions.ts | 2 +- .../chat/test/common/voiceChat.test.ts | 77 +++++++++-------- .../electron-sandbox/inlineChatQuickVoice.ts | 2 +- 4 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 9b974c89b5585..8d277ba99319b 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -14,6 +14,10 @@ import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workb export const IVoiceChatService = createDecorator('voiceChatService'); +export interface IVoiceChatSessionOptions { + readonly usesAgents?: boolean; +} + export interface IVoiceChatService { readonly _serviceBrand: undefined; @@ -24,7 +28,7 @@ export interface IVoiceChatService { * if the user says "at workspace slash fix this problem", the result * will be "@workspace /fix this problem". */ - createVoiceChatSession(token: CancellationToken): IVoiceChatSession; + createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession; } export interface IVoiceChatTextEvent extends ISpeechToTextEvent { @@ -41,6 +45,17 @@ export interface IVoiceChatSession extends IDisposable { readonly onDidChange: Event; } +interface IPhraseValue { + readonly agent: string; + readonly command?: string; +} + +enum PhraseTextType { + AGENT = 1, + COMMAND = 2, + AGENT_AND_COMMAND = 3 +} + export class VoiceChatService extends Disposable implements IVoiceChatService { readonly _serviceBrand: undefined; @@ -49,14 +64,14 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly COMMAND_PREFIX = chatSubcommandLeader; private static readonly PHRASES = { - [VoiceChatService.AGENT_PREFIX]: 'at ', - [VoiceChatService.COMMAND_PREFIX]: 'slash ' + [VoiceChatService.AGENT_PREFIX]: 'at', + [VoiceChatService.COMMAND_PREFIX]: 'slash' }; private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private _phrases: Map | undefined = undefined; - private get phrases(): Map { + private _phrases: Map | undefined = undefined; + private get phrases(): Map { if (!this._phrases) { this._phrases = this.createPhrases(); } @@ -77,23 +92,20 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); } - private createPhrases(): Map { - const phrases = new Map(); + private createPhrases(): Map { + const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { - const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]}${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); - const agentResult = `${VoiceChatService.AGENT_PREFIX}${agent.id}`; - phrases.set(agentPhrase, agentResult); + const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + phrases.set(agentPhrase, { agent: agent.id }); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]}${slashCommand.name}`.toLowerCase(); - const slashCommandResult = `${VoiceChatService.COMMAND_PREFIX}${slashCommand.name}`; - phrases.set(slashCommandPhrase, slashCommandResult); + const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); + phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); - const agentSlashCommandResult = `${agentResult} ${slashCommandResult}`; - phrases.set(agentSlashCommandPhrase, agentSlashCommandResult); + phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name }); } } } @@ -101,7 +113,22 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { return phrases; } - createVoiceChatSession(token: CancellationToken): IVoiceChatSession { + private toText(value: IPhraseValue, type: PhraseTextType, options: IVoiceChatSessionOptions): string { + if (type === PhraseTextType.COMMAND && options.usesAgents) { + type = PhraseTextType.AGENT_AND_COMMAND; // rewrite `/fix` to `@workspace /foo` in this case + } + + switch (type) { + case PhraseTextType.AGENT: + return `${VoiceChatService.AGENT_PREFIX}${value.agent}`; + case PhraseTextType.COMMAND: + return `${VoiceChatService.COMMAND_PREFIX}${value.command}`; + case PhraseTextType.AGENT_AND_COMMAND: + return `${VoiceChatService.AGENT_PREFIX}${value.agent} ${VoiceChatService.COMMAND_PREFIX}${value.command}`; + } + } + + createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession { const disposables = new DisposableStore(); let detectedAgent = false; @@ -114,8 +141,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if (e.text) { - const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX].trim()); - const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX].trim()); + const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]); + const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]); if (startsWithAgent || startsWithSlashCommand) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; @@ -123,10 +150,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let waitingForInput = false; // Check for agent + slash command - if (startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(4)]; + if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { + const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND, options), ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; @@ -138,10 +165,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { } // Check for agent (if not done already) - if (startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { - const agentResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (agentResult) { - transformedWords = [agentResult, ...originalWords.slice(2)]; + if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { + const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.AGENT, options), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; @@ -153,9 +180,9 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command (if not done already) if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { - const slashCommandResult = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); - if (slashCommandResult) { - transformedWords = [slashCommandResult, ...originalWords.slice(2)]; + const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + if (phrase) { + transformedWords = [this.toText(phrase, PhraseTextType.COMMAND, options), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; 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 d433ccfecad08..3d826bcaba8f5 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -274,7 +274,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token)); + const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' })); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 2f87f56857d34..d90f5b4e3ec21 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,7 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; suite('VoiceChat', () => { @@ -86,16 +86,15 @@ suite('VoiceChat', () => { let event: IVoiceChatTextEvent | undefined; let session: ISpeechToTextSession | undefined; - function createSession() { + function createSession(options: IVoiceChatSessionOptions) { session?.dispose(); - session = disposables.add(service.createVoiceChatSession(CancellationToken.None)); + session = disposables.add(service.createVoiceChatSession(CancellationToken.None, options)); disposables.add(session.onDidChange(e => { event = e; })); } - setup(() => { emitter = disposables.add(new Emitter()); service = disposables.add(new VoiceChatService(new TestSpeechService(), new TestChatAgentService())); @@ -105,10 +104,18 @@ suite('VoiceChat', () => { disposables.clear(); }); - test('Agent and slash command detection', async () => { + test('Agent and slash command detection (useAgents: false)', async () => { + testAgentsAndSlashCommandsDetection({ usesAgents: false }); + }); + + test('Agent and slash command detection (useAgents: true)', async () => { + testAgentsAndSlashCommandsDetection({ usesAgents: true }); + }); + + function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { // Nothing to detect - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Started }); assert.strictEqual(event?.status, SpeechToTextStatus.Started); @@ -129,7 +136,7 @@ suite('VoiceChat', () => { assert.strictEqual(event?.waitingForInput, undefined); // Agent - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -137,113 +144,113 @@ suite('VoiceChat', () => { emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace'); - assert.strictEqual(event?.waitingForInput, true); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'At workspace'); + assert.strictEqual(event?.waitingForInput, options.usesAgents); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); assert.strictEqual(event?.waitingForInput, false); // Agent with punctuation - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace, help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At Workspace. help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At Workspace. help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace help'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At Workspace. help'); assert.strictEqual(event?.waitingForInput, false); // Slash Command - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'Slash fix' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace /fix' : '/fix'); assert.strictEqual(event?.waitingForInput, true); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'Slash fix' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace /fix' : '/fix'); assert.strictEqual(event?.waitingForInput, true); // Agent + Slash Command - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code slash search help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code slash search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code slash search help'); assert.strictEqual(event?.waitingForInput, false); // Agent + Slash Command with punctuation - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code, slash search, help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code, slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code, slash search, help'); assert.strictEqual(event?.waitingForInput, false); - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At code. slash, search help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code. slash, search help'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At code. slash search, help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@vscode /search help'); + assert.strictEqual(event?.text, options.usesAgents ? '@vscode /search help' : 'At code. slash search, help'); assert.strictEqual(event?.waitingForInput, false); // Agent not detected twice - createSession(); + createSession(options); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); - assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace, for at workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); - assert.strictEqual(event?.text, '@workspace for at workspace'); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); - }); + } test('waiting for input', async () => { // Agent - createSession(); + createSession({ usesAgents: true }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -256,7 +263,7 @@ suite('VoiceChat', () => { assert.strictEqual(event.waitingForInput, true); // Slash Command - createSession(); + createSession({ usesAgents: true }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 985ca40703b74..60fea0a352c29 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -240,7 +240,7 @@ export class InlineChatQuickVoice implements IEditorContribution { let message: string | undefined; let preview: string | undefined; - const session = this._voiceChatService.createVoiceChatSession(cts.token); + const session = this._voiceChatService.createVoiceChatSession(cts.token, { usesAgents: false }); const listener = session.onDidChange(e => { if (cts.token.isCancellationRequested) { From c9215c87ba4f6cd579dff6bbbe541cc97881cf30 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:02:24 +0100 Subject: [PATCH 0129/1863] Git - fix commit action button when detached/rebase (#204857) --- extensions/git/src/actionButton.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index d29bfcb23222b..494972276ac0e 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -128,7 +128,7 @@ export class ActionButton { command: 'git.commit', title: l10n.t('{0} Continue', '$(check)'), tooltip: this.state.isCommitInProgress ? l10n.t('Continuing Rebase...') : l10n.t('Continue Rebase'), - arguments: [this.repository.sourceControl, ''] + arguments: [this.repository.sourceControl, null] }; } @@ -138,7 +138,7 @@ export class ActionButton { command: 'git.commit', title: l10n.t('{0} Commit', '$(check)'), tooltip: this.state.isCommitInProgress ? l10n.t('Committing Changes...') : l10n.t('Commit Changes'), - arguments: [this.repository.sourceControl, ''] + arguments: [this.repository.sourceControl, null] }; } From f10f059a541ab999c6863e755c66ea7af1468086 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 12:19:16 -0800 Subject: [PATCH 0130/1863] Leverage AuthenticationProviders for gating Language Model Access (#204859) * Leverage AuthenticationProviders for gating Language Model Access Since we already have the Auth Stack which has a concept of "Managing Trusted Extensions" we can initially use that for gating Language Model Access so that when an extension asks for Language Model Access, they have to see a dialog first. * Support multiple models and create AuthProviders on Core side --- .../api/browser/mainThreadChatProvider.ts | 104 +++++++++++++++++- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostAuthentication.ts | 6 +- .../api/common/extHostChatProvider.ts | 31 +++++- .../browser/authenticationService.ts | 18 ++- .../authentication/common/authentication.ts | 21 ++++ 6 files changed, 166 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index dcf7e7ab3e096..1a1d046357afa 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -12,8 +13,10 @@ import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadChatProvider) export class MainThreadChatProvider implements MainThreadChatProviderShape { @@ -28,6 +31,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { @IChatProviderService private readonly _chatProviderService: IChatProviderService, @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @IExtensionService private readonly _extensionService: IExtensionService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); @@ -54,6 +59,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } } })); + dipsosables.add(this._registerAuthenticationProvider(identifier)); dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), @@ -93,4 +99,100 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return task; } + + private _registerAuthenticationProvider(identifier: string): IDisposable { + const disposables = new DisposableStore(); + // This needs to be done in both MainThread & ExtHost ChatProvider + const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + identifier; + // This is what will be displayed in the UI and the account used for managing access via Auth UI + const authAccountId = identifier; + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, authAccountId)); + disposables.add(toDisposable(() => { + this._authenticationService.unregisterAuthenticationProvider(authProviderId); + })); + disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { + if (e.providerId === authProviderId) { + if (e.event.removed?.length) { + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const extensionsToUpdateAccess = []; + for (const allowed of allowedExtensions) { + const extension = await this._extensionService.getExtension(allowed.id); + this._authenticationService.updateAllowedExtension(authProviderId, authAccountId, allowed.id, allowed.name, false); + if (extension) { + extensionsToUpdateAccess.push({ + extension: extension.identifier, + enabled: false + }); + } + } + this._proxy.$updateAccesslist(extensionsToUpdateAccess); + } + } + })); + disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const accessList = []; + for (const allowedExtension of allowedExtensions) { + const extension = await this._extensionService.getExtension(allowedExtension.id); + if (extension) { + accessList.push({ + extension: extension.identifier, + enabled: allowedExtension.allowed ?? true + }); + } + } + this._proxy.$updateAccesslist(accessList); + })); + return disposables; + } +} + +// The fake AuthenticationProvider that will be used to gate access to the Language Model. There will be one per provider. +class LanguageModelAccessAuthProvider implements IAuthenticationProvider { + supportsMultipleAccounts = false; + label = 'Language Model'; + + // Important for updating the UI + private _onDidChangeSessions: Emitter = new Emitter(); + onDidChangeSessions: Event = this._onDidChangeSessions.event; + + private _session: AuthenticationSession | undefined; + + constructor(readonly id: string, private readonly accountName: string) { } + + async getSessions(scopes?: string[] | undefined): Promise { + // If there are no scopes and no session that means no extension has requested a session yet + // and the user is simply opening the Account menu. In that case, we should not return any "sessions". + if (scopes === undefined && !this._session) { + return []; + } + if (this._session) { + return [this._session]; + } + return [await this.createSession(scopes || [], {})]; + } + async createSession(scopes: string[], options: IAuthenticationProviderCreateSessionOptions): Promise { + this._session = this._createFakeSession(scopes); + this._onDidChangeSessions.fire({ added: [this._session], changed: [], removed: [] }); + return this._session; + } + removeSession(sessionId: string): Promise { + if (this._session) { + this._onDidChangeSessions.fire({ added: [], changed: [], removed: [this._session!] }); + this._session = undefined; + } + return Promise.resolve(); + } + + private _createFakeSession(scopes: string[]): AuthenticationSession { + return { + id: 'fake-session', + account: { + id: this.id, + label: this.accountName, + }, + accessToken: 'fake-access-token', + scopes, + }; + } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d899ada547e6c..a820c66779643 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -207,7 +207,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); - const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); + const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService, extHostAuthentication)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); @@ -1399,7 +1399,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, requestLanguageModelAccess(id, options) { checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.requestLanguageModelAccess(extension.identifier, id, options); + return extHostChatProvider.requestLanguageModelAccess(extension, id, options); }, get languageModels() { checkProposedApiEnabled(extension, 'chatRequestAccess'); diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index ca07cbfef64d5..84ddbd6fa5508 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -8,6 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; interface ProviderWithMetadata { label: string; @@ -106,7 +107,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { } $onDidChangeAuthenticationSessions(id: string, label: string) { - this._onDidChangeSessions.fire({ provider: { id, label } }); + // Don't fire events for the internal auth providers + if (!id.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) { + this._onDidChangeSessions.fire({ provider: { id, label } }); + } return Promise.resolve(); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index b7a91c72e35b5..eae0f221ffa65 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -11,9 +11,12 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { AsyncIterableSource } from 'vs/base/common/async'; import { Emitter, Event } 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'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -100,6 +103,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { constructor( mainContext: IMainContext, private readonly _logService: ILogService, + private readonly _extHostAuthentication: ExtHostAuthentication, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); } @@ -186,13 +190,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeAccess.fire(updated); } - async requestLanguageModelAccess(from: ExtensionIdentifier, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + async requestLanguageModelAccess(extension: Readonly, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + const from = extension.identifier; // check if the extension is in the access list and allowed to make chat requests if (this._accesslist.get(from) === false) { throw new Error('Extension is NOT allowed to make chat requests'); } - const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, options?.justification); + const justification = options?.justification; + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); if (!metadata) { if (!this._accesslist.get(from)) { @@ -200,6 +206,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } throw new Error(`Language model '${languageModelId}' NOT found`); } + await this._checkAuthAccess(extension, languageModelId, justification); const that = this; @@ -244,4 +251,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { data.res.handleFragment(chunk); } } + + // BIG HACK: Using AuthenticationProviders to check access to Language Models + private async _checkAuthAccess(from: Readonly, languageModelId: string, detail?: string): Promise { + // This needs to be done in both MainThread & ExtHost ChatProvider + const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + languageModelId; + const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); + if (!session) { + try { + await this._extHostAuthentication.getSession(from, providerId, [], { + forceNewSession: { + detail: detail ?? localize('chatAccess', "To allow access to the '{0}' language model", languageModelId), + } + }); + } catch (err) { + throw new Error('Access to language model has not been granted'); + } + } + } } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 78963a374a120..78e2a0c41375c 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -24,7 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationService, AllowedExtension } from 'vs/workbench/services/authentication/common/authentication'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IExtensionFeatureTableRenderer, IRenderedData, ITableData, IRowData, IExtensionFeaturesRegistry, Extensions } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -107,15 +107,6 @@ export async function getCurrentAuthenticationSessionInfo( return undefined; } -interface AllowedExtension { - id: string; - name: string; - allowed?: boolean; - lastUsed?: number; - // If true, this comes from the product.json - trusted?: boolean; -} - // OAuth2 spec prohibits space in a scope, so use that to join them. const SCOPESLIST_SEPARATOR = ' '; @@ -245,6 +236,9 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidChangeDeclaredProviders: Emitter = this._register(new Emitter()); readonly onDidChangeDeclaredProviders: Event = this._onDidChangeDeclaredProviders.event; + private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>()); + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event; + constructor( @IActivityService private readonly activityService: IActivityService, @IExtensionService private readonly extensionService: IExtensionService, @@ -816,7 +810,8 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - private readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { + // TODO: pull this stuff out into its own service + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] { let trustedExtensions: AllowedExtension[] = []; try { const trustedExtensionSrc = this.storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); @@ -926,6 +921,7 @@ export class AuthenticationService extends Disposable implements IAuthentication .filter((item): item is TrustedExtensionsQuickPickItem => item.type !== 'separator') .map(i => i.extension); this.storageService.store(`${authProvider.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); + this._onDidChangeExtensionSessionAccess.fire({ providerId: authProvider.id, accountName }); quickPick.hide(); })); diff --git a/src/vs/workbench/services/authentication/common/authentication.ts b/src/vs/workbench/services/authentication/common/authentication.ts index 0ff2179bfe2ae..eaeaffc56ac37 100644 --- a/src/vs/workbench/services/authentication/common/authentication.ts +++ b/src/vs/workbench/services/authentication/common/authentication.ts @@ -5,6 +5,11 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +/** + * Use this if you don't want the onDidChangeSessions event to fire in the extension host + */ +export const INTERNAL_AUTH_PROVIDER_PREFIX = '__'; + export interface AuthenticationSessionAccount { label: string; id: string; @@ -34,6 +39,20 @@ export interface IAuthenticationCreateSessionOptions { activateImmediate?: boolean; } +export interface AllowedExtension { + id: string; + name: string; + /** + * If true or undefined, the extension is allowed to use the account + * If false, the extension is not allowed to use the account + * TODO: undefined shouldn't be a valid value, but it is for now + */ + allowed?: boolean; + lastUsed?: number; + // If true, this comes from the product.json + trusted?: boolean; +} + export const IAuthenticationService = createDecorator('IAuthenticationService'); export interface IAuthenticationService { @@ -58,6 +77,7 @@ export interface IAuthenticationService { readonly onDidUnregisterAuthenticationProvider: Event; readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>; + readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>; // TODO completely remove this property declaredProviders: AuthenticationProviderInformation[]; @@ -70,6 +90,7 @@ export interface IAuthenticationService { removeSession(providerId: string, sessionId: string): Promise; manageTrustedExtensionsForAccount(providerId: string, accountName: string): Promise; + readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[]; removeAccountSessions(providerId: string, accountName: string, sessions: AuthenticationSession[]): Promise; } From 3883134f5355a8b84f7c1604c37bd6f58aaf6f5e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 9 Feb 2024 12:58:02 -0800 Subject: [PATCH 0131/1863] correctly assign context to the tree view items (#204856) --- .../debug/browser/debug.contribution.ts | 4 +-- .../workbench/contrib/debug/common/debug.ts | 1 + .../notebookVariableCommands.ts | 2 +- .../notebookVariablesDataSource.ts | 1 + .../notebookVariablesView.ts | 36 +++++++++++-------- ...ode.proposed.notebookVariableProvider.d.ts | 3 ++ 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a30c4d62dd8df..d16e80950444c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -48,7 +48,7 @@ import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statu import { ADD_TO_WATCH_ID, BREAK_WHEN_VALUE_CHANGES_ID, BREAK_WHEN_VALUE_IS_ACCESSED_ID, BREAK_WHEN_VALUE_IS_READ_ID, COPY_EVALUATE_PATH_ID, COPY_VALUE_ID, SET_VARIABLE_ID, VIEW_MEMORY_ID, VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ADD_WATCH_ID, ADD_WATCH_LABEL, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_HAS_DEBUGGED, CONTEXT_IN_DEBUG_MODE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_VARIABLE_VALUE, CONTEXT_WATCH_ITEM_TYPE, DEBUG_PANEL_ID, DISASSEMBLY_VIEW_ID, EDITOR_CONTRIBUTION_ID, IDebugService, INTERNAL_CONSOLE_OPTIONS_SCHEMA, LOADED_SCRIPTS_VIEW_ID, REPL_VIEW_ID, State, VARIABLES_VIEW_ID, VIEWLET_ID, WATCH_VIEW_ID, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; import { DebugVisualizerService, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; @@ -197,7 +197,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, VIEW_MEMORY_ID, nls.localize registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_EXPRESSION_COMMAND_ID, nls.localize('removeWatchExpression', "Remove Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, 'inline', icons.watchExpressionRemove); registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, 20, undefined, undefined, 'z_commands'); -registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20); +registerDebugViewMenuItem(MenuId.NotebookVariablesContext, COPY_NOTEBOOK_VARIABLE_VALUE_ID, COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, 20, CONTEXT_VARIABLE_VALUE); // Touch Bar if (isMacintosh) { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 06d374cbb80e9..c59762d498bcd 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -87,6 +87,7 @@ export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey('variable export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue', false, { type: 'string', description: nls.localize('variableValue', "Value of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_TYPE = new RawContextKey('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_NAME = new RawContextKey('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_LANGUAGE = new RawContextKey('variableLanguage', false, { type: 'string', description: nls.localize('variableLanguage', "Language of the variable source, present for debug visualization clauses.") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index 1e955f90bb276..168480825c377 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -18,7 +18,7 @@ registerAction2(class extends Action2 { id: COPY_NOTEBOOK_VARIABLE_VALUE_ID, title: COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, f1: false, - precondition: ContextKeyExpr.has('value') + precondition: ContextKeyExpr.has('value'), }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 2ca03e7798e59..7fdb7141760db 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -21,6 +21,7 @@ export interface INotebookVariableElement { readonly name: string; readonly value: string; readonly type?: string; + readonly language?: string; readonly indexedChildrenCount: number; readonly indexStart?: number; readonly hasNamedChildren: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index d7c5246450c44..f0fc23bfa4fdd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } 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'; @@ -25,6 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -33,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string }; export class NotebookVariablesView extends ViewPane { @@ -42,7 +43,6 @@ export class NotebookVariablesView extends ViewPane { private tree: WorkbenchAsyncDataTree | undefined; private activeNotebook: NotebookTextModel | undefined; - private readonly menu: IMenu; private readonly dataSource: NotebookVariableDataSource; private updateScheduler: RunOnceScheduler; @@ -63,7 +63,7 @@ export class NotebookVariablesView extends ViewPane { @ICommandService protected commandService: ICommandService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, - @IMenuService menuService: IMenuService + @IMenuService private readonly menuService: IMenuService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -73,7 +73,6 @@ export class NotebookVariablesView extends ViewPane { this.setActiveNotebook(); - this.menu = menuService.createMenu(MenuId.NotebookVariablesContext, contextKeyService); this.dataSource = new NotebookVariableDataSource(this.notebookKernelService); this.updateScheduler = new RunOnceScheduler(() => this.tree?.updateChildren(), 100); } @@ -102,22 +101,31 @@ export class NotebookVariablesView extends ViewPane { } private onContextMenu(e: ITreeContextMenuEvent): any { + if (!e.element) { + return; + } const element = e.element; - const context = { - type: element?.type - }; const arg: contextMenuArg = { - source: element?.notebook.uri.toString(), - value: element?.value, - ...context + source: element.notebook.uri.toString(), + value: element.value, + type: element.type, + language: element.language }; const actions: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg, shouldForwardArgs: true }, actions); + + const overlayedContext = this.contextKeyService.createOverlay([ + [CONTEXT_VARIABLE_NAME.key, element.name], + [CONTEXT_VARIABLE_VALUE.key, element.value], + [CONTEXT_VARIABLE_TYPE.key, element.type], + [CONTEXT_VARIABLE_LANGUAGE.key, element.language] + ]); + const menu = this.menuService.createMenu(MenuId.NotebookVariablesContext, overlayedContext); + createAndFillInContextMenuActions(menu, { arg, shouldForwardArgs: true }, actions); + menu.dispose(); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, - getActionsContext: () => context, + getActions: () => actions }); } diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 46258e0432c49..515818d3a1b23 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -41,6 +41,9 @@ declare module 'vscode' { /** The type of the variable's value */ type?: string; + + /** The language of the variable's value */ + language?: string; } } From e06f8b6d5ef68475c98c759b51ce70a2dd68cde1 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 14:19:26 -0800 Subject: [PATCH 0132/1863] Fix #204820 (#204867) This shouldn't have returned undefined. Fixes #204820 --- .../services/authentication/browser/authenticationService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 78e2a0c41375c..bc8d6860538f0 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -426,7 +426,9 @@ export class AuthenticationService extends Disposable implements IAuthentication isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { const trustedExtensionAuthAccess = this.productService.trustedExtensionAuthAccess; if (Array.isArray(trustedExtensionAuthAccess)) { - return trustedExtensionAuthAccess.includes(extensionId) ?? undefined; + if (trustedExtensionAuthAccess.includes(extensionId)) { + return true; + } } else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionId)) { return true; } From 2fa5c6538ef9f464a7ef07a916214cacfe1d3036 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 14:29:21 -0800 Subject: [PATCH 0133/1863] Bump distro (#204868) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 51d5b93310abb..6dc97a406e515 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "0a14a7cde028e801c7aea8415afac7ddf3c9a0bd", + "distro": "848d09154925ef27343af358097ed5435450160e", "author": { "name": "Microsoft Corporation" }, From ca858e1da32ad0f47b05abd9c48e98258498db6b Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 15:26:23 -0800 Subject: [PATCH 0134/1863] Check for newline instead of an arbitrary length for showing hover hint (#204873) This should show the hint less in scenarios where we don't need it. --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 16b52782c26d3..79e8b3d6aa150 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1283,7 +1283,7 @@ export class QuickInputHoverDelegate implements IHoverDelegate { : typeof options.content === 'string' ? options.content : options.content.value - ).length > 20; + ).includes('\n'); return this.hoverService.showHover({ ...options, persistence: { From 559785c7111cb2104d76b1ad507a1d91f58579ac Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 15:40:36 -0800 Subject: [PATCH 0135/1863] Better wording of Trusted Extension tooltip (#204875) ref https://github.com/microsoft/vscode/pull/204785#issuecomment-1935612459 --- .../services/authentication/browser/authenticationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index bc8d6860538f0..524b7402f0457 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -898,7 +898,7 @@ export class AuthenticationService extends Disposable implements IAuthentication : nls.localize('notUsed', "Has not used this account"); let tooltip: string | undefined; if (extension.trusted) { - tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and has access to this account"); + tooltip = nls.localize('trustedExtensionTooltip', "This extension is trusted by Microsoft and\nalways has access to this account"); } return { label: extension.name, From f140bf63099aa782a08f983c95065f5ae92eb0bb Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 16:24:20 -0800 Subject: [PATCH 0136/1863] Render icons in the default description & detail hovers (#204882) Fixes https://github.com/microsoft/vscode/issues/204818 --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 2 +- .../quickinput/browser/quickInputList.ts | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 0bb344bfbb6df..f95a11c9c4448 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -22,7 +22,7 @@ export interface IIconLabelCreationOptions { export interface IIconLabelValueOptions { title?: string | ITooltipMarkdownString; - descriptionTitle?: string; + descriptionTitle?: string | ITooltipMarkdownString; suffix?: string; hideIcon?: boolean; extraClasses?: readonly string[]; diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index d8871456aee56..de219a800bb5e 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -35,6 +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'; const $ = dom.$; @@ -314,10 +315,23 @@ class ListElementRenderer implements IListRenderer Date: Fri, 9 Feb 2024 16:47:29 -0800 Subject: [PATCH 0137/1863] Adopt view zone api for notebook cell chat --- .../chat/cellChatActions.ts | 174 ++--- .../chat/notebookChatController.ts} | 604 ++++++++++-------- .../view/cellParts/chat/cellChatPart.ts | 25 +- 3 files changed, 383 insertions(+), 420 deletions(-) rename src/vs/workbench/contrib/notebook/browser/{view/cellParts => controller}/chat/cellChatActions.ts (77%) rename src/vs/workbench/contrib/notebook/browser/{view/cellParts/chat/cellChatController.ts => controller/chat/notebookChatController.ts} (54%) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts similarity index 77% rename from src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts rename to src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 575a5ddedeb35..8d6325c6b2677 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -15,11 +14,9 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { insertNewCell } from 'vs/workbench/contrib/notebook/browser/controller/insertCellActions'; -import { CellEditState, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; +import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -46,12 +43,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.acceptInput(); + NotebookChatController.get(context.notebookEditor)?.acceptInput(); } }); @@ -115,9 +107,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const editor = context.notebookEditor; - const activeCell = context.cell; - await editor.focusNotebookCell(activeCell, 'editor'); + await NotebookChatController.get(context.notebookEditor)?.focusNext(); } }); @@ -146,14 +136,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const activeCell = context.cell; - // Navigate to cell chat widget if it exists - const controller = NotebookCellChatController.get(activeCell); - if (controller && controller.isWidgetVisible()) { - controller.focusWidget(); - return; - } - + const index = context.notebookEditor.getCellIndex(context.cell); + await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'above'); } }); @@ -182,29 +166,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const editor = context.notebookEditor; - const activeCell = context.cell; - - const idx = editor.getCellIndex(activeCell); - if (typeof idx !== 'number') { - return; - } - - if (idx >= editor.getLength() - 1) { - // last one - return; - } - - const targetCell = editor.cellAt(idx + 1); - - if (targetCell) { - // Navigate to cell chat widget if it exists - const controller = NotebookCellChatController.get(targetCell); - if (controller && controller.isWidgetVisible()) { - controller.focusWidget(); - return; - } - } + const index = context.notebookEditor.getCellIndex(context.cell); + await NotebookChatController.get(context.notebookEditor)?.focusNearestWidget(index, 'below'); } }); @@ -225,12 +188,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.cancelCurrentRequest(false); + NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false); } }); @@ -250,12 +208,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.dismiss(false); + NotebookChatController.get(context.notebookEditor)?.dismiss(); } }); @@ -285,12 +238,7 @@ registerAction2(class extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.acceptSession(); + NotebookChatController.get(context.notebookEditor)?.acceptSession(); } }); @@ -315,15 +263,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - // todo discard - ctrl.dismiss(true); - // focus on the cell editor container - context.notebookEditor.focusNotebookCell(context.cell, 'container'); + NotebookChatController.get(context.notebookEditor)?.discard(); } }); @@ -343,12 +283,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Helpful); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Helpful); } }); @@ -368,12 +303,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); } }); @@ -393,12 +323,7 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const ctrl = NotebookCellChatController.get(context.cell); - if (!ctrl) { - return; - } - - ctrl.feedbackLast(InlineChatResponseFeedbackKind.Bug); + NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Bug); } }); @@ -458,7 +383,22 @@ registerAction2(class extends NotebookAction { override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, ...args: any[]): IInsertCellWithChatArgs | undefined { const [firstArg] = args; if (!firstArg) { - return undefined; + const notebookEditor = getEditorFromArgsOrActivePane(accessor); + if (!notebookEditor) { + return undefined; + } + + const activeCell = notebookEditor.getActiveCell(); + if (!activeCell) { + return undefined; + } + + return { + cell: activeCell, + notebookEditor, + input: undefined, + autoSend: undefined + }; } if (typeof firstArg !== 'object' || typeof firstArg.index !== 'number') { @@ -481,33 +421,9 @@ registerAction2(class extends NotebookAction { } async runWithContext(accessor: ServicesAccessor, context: IInsertCellWithChatArgs) { - let newCell: ICellViewModel | null = null; - if (!context.cell) { - // insert at the top - const languageService = accessor.get(ILanguageService); - newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); - } else { - newCell = insertNewCell(accessor, context, CellKind.Code, 'below', true); - } - - if (!newCell) { - return; - } - - await context.notebookEditor.focusNotebookCell(newCell, 'container'); - const ctrl = NotebookCellChatController.get(newCell); - if (!ctrl) { - return; - } - - context.notebookEditor.getCellsInRange().forEach(cell => { - const cellCtrl = NotebookCellChatController.get(cell); - if (cellCtrl) { - cellCtrl.dismiss(false); - } - }); - - ctrl.show(context.input, context.autoSend); + const index = Math.max(0, context.cell ? context.notebookEditor.getCellIndex(context.cell) + 1 : 0); + context.notebookEditor.focusContainer(); + NotebookChatController.get(context.notebookEditor)?.run(index, context.input, context.autoSend); } }); @@ -537,26 +453,8 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const languageService = accessor.get(ILanguageService); - const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); - - if (!newCell) { - return; - } - await context.notebookEditor.focusNotebookCell(newCell, 'container'); - const ctrl = NotebookCellChatController.get(newCell); - if (!ctrl) { - return; - } - - context.notebookEditor.getCellsInRange().forEach(cell => { - const cellCtrl = NotebookCellChatController.get(cell); - if (cellCtrl) { - cellCtrl.dismiss(false); - } - }); - - ctrl.show(); + context.notebookEditor.focusContainer(); + NotebookChatController.get(context.notebookEditor)?.run(0, '', false); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts similarity index 54% rename from src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts rename to src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 0e05e8cae1436..86aadb52b8d08 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -3,25 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, WindowIntervalTimer } from 'vs/base/browser/dom'; -import { CancelablePromise, Queue, createCancelablePromise, raceCancellationError } from 'vs/base/common/async'; +import { Dimension, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { CancelablePromise, Queue, createCancelablePromise, disposableTimeout, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; 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 { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { TextEdit } from 'vs/editor/common/languages'; -import { ICursorStateComputer } from 'vs/editor/common/model'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -29,16 +33,21 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { AsyncProgress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; +import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; +import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + + +import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); @@ -48,257 +57,263 @@ export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); -interface ICellChatPart { - activeCell: ICellViewModel | undefined; -} +const WIDGET_MARGIN_BOTTOM = 16; -export class NotebookCellChatController extends Disposable { - private static _cellChatControllers = new WeakMap(); +class NotebookChatWidget extends Disposable implements INotebookViewZone { + private _afterModelPosition: number; - static get(cell: ICellViewModel): NotebookCellChatController | undefined { - return NotebookCellChatController._cellChatControllers.get(cell); + set afterModelPosition(afterModelPosition: number) { + this._afterModelPosition = afterModelPosition; } - private _sessionCtor: CancelablePromise | undefined; - private _activeSession?: Session; - private readonly _ctxHasActiveRequest: IContextKey; - private _isVisible: boolean = false; - private _strategy: EditStrategy | undefined; - - private _inlineChatListener: IDisposable | undefined; - private _widget: InlineChatWidget | undefined; - private _toolbar: MenuWorkbenchToolBar | undefined; - private readonly _ctxVisible: IContextKey; - private readonly _ctxCellWidgetFocused: IContextKey; - private readonly _ctxLastResponseType: IContextKey; - private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); - constructor( - private readonly _notebookEditor: INotebookEditorDelegate, - private readonly _chatPart: ICellChatPart, - private readonly _cell: ICellViewModel, - private readonly _partContainer: HTMLElement, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, - @ICommandService private readonly _commandService: ICommandService, - @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, - ) { - super(); - - NotebookCellChatController._cellChatControllers.set(this._cell, this); - this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); - this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(_contextKeyService); - this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); - this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); - - this._register(this._cell.onDidChangeEditorAttachState(() => { - const editor = this._getCellEditor(); - this._inlineChatListener?.dispose(); + get afterModelPosition(): number { + return this._afterModelPosition; + } - if (!editor) { - return; - } + private _heightInPx: number; - if (!this._widget && this._isVisible) { - this._initialize(editor); - } + set heightInPx(heightInPx: number) { + this._heightInPx = heightInPx; + } - const inlineChatController = InlineChatController.get(editor); - if (inlineChatController) { - this._inlineChatListener = inlineChatController.onWillStartSession(() => { - this.dismiss(false); - }); - } - })); + get heightInPx(): number { + return this._heightInPx; } - private _initialize(editor: IActiveCodeEditor) { - this._widget = this._instantiationService.createInstance(InlineChatWidget, editor, { - menuId: MENU_CELL_CHAT_INPUT, - widgetMenuId: MENU_CELL_CHAT_WIDGET, - statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, - feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK - }); + private _editingCell: CellViewModel | null = null; - this._widgetDisposableStore.add(this._widget.onDidChangeHeight(() => { - this._updateHeight(); - })); + constructor( + private readonly _notebookEditor: INotebookEditor, + readonly id: string, + readonly domNode: HTMLElement, + readonly widgetContainer: HTMLElement, + readonly inlineChatWidget: InlineChatWidget, + readonly parentEditor: CodeEditorWidget, + afterModelPosition: number, + heightInPx: number, + private readonly _languageService: ILanguageService, + ) { + super(); - this._widgetDisposableStore.add(this._notebookExecutionStateService.onDidChangeExecution(e => { - if (e.notebook.toString() !== this._notebookEditor.textModel?.uri.toString()) { - return; - } + this._afterModelPosition = afterModelPosition; + this._heightInPx = heightInPx; - if (e.type === NotebookExecutionType.cell && e.affectsCell(this._cell.uri) && e.changed === undefined /** complete */) { - // check if execution is successfull - const { lastRunSuccess } = this._cell.internalMetadata; - if (lastRunSuccess) { - this._strategy?.createSnapshot(); - } - } + this._register(inlineChatWidget.onDidChangeHeight(() => { + this.heightInPx = inlineChatWidget.getHeight() + WIDGET_MARGIN_BOTTOM; + this._notebookEditor.changeViewZones(accessor => { + accessor.layoutZone(id); + }); + this._layoutWidget(inlineChatWidget, widgetContainer); })); + this._layoutWidget(inlineChatWidget, widgetContainer); + } - this._partContainer.appendChild(this._widget.domNode); + focus() { + this.inlineChatWidget.focus(); } - public override dispose(): void { - if (this._isVisible) { - // detach the chat widget - this._widget?.reset(); - this._sessionCtor?.cancel(); - this._sessionCtor = undefined; + async getEditingCellEditor() { + if (this._editingCell) { + await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); + return this._notebookEditor.activeCodeEditor; } - try { - if (this._widget) { - this._partContainer.removeChild(this._widget.domNode); - } - - } catch (_ex) { - // might not be attached + if (!this._notebookEditor.hasModel()) { + return undefined; } - // dismiss since we can't restore the widget properly now - this.dismiss(false); - this._widget?.dispose(); - this._inlineChatListener?.dispose(); - this._toolbar?.dispose(); - this._inlineChatListener = undefined; - this._ctxHasActiveRequest.reset(); - this._ctxVisible.reset(); - NotebookCellChatController._cellChatControllers.delete(this._cell); - super.dispose(); - } + this._editingCell = insertCell(this._languageService, this._notebookEditor, this._afterModelPosition, CellKind.Code, 'above'); + + if (!this._editingCell) { + return undefined; + } - isWidgetVisible() { - return this._isVisible; + await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); + return this._notebookEditor.activeCodeEditor; } - layout() { - if (this._isVisible && this._widget) { - const width = this._notebookEditor.getLayoutInfo().width - (/** margin */ 16 + 6) - (/** padding */ 6 * 2); - const height = this._widget.getHeight(); - this._widget.layout(new Dimension(width, height)); + async discardChange() { + if (this._notebookEditor.hasModel() && this._editingCell) { + // remove the cell from the notebook + runDeleteAction(this._notebookEditor, this._editingCell); } } - private _updateHeight() { - const surrounding = 6 * 2 /** padding */ + 6 /** cell chat widget margin bottom */ + 2 /** border */; - const heightWithPadding = this._isVisible && this._widget - ? (this._widget.getHeight() - 8 /** shadow */ - 18 /** padding */ - 6 /** widget's internal margin top */ + surrounding) - : 0; + private _layoutWidget(inlineChatWidget: InlineChatWidget, widgetContainer: HTMLElement) { + const layoutConfiguration = this._notebookEditor.notebookOptions.getLayoutConfiguration(); + const rightMargin = layoutConfiguration.cellRightMargin; + const leftMargin = this._notebookEditor.notebookOptions.getCellEditorContainerLeftMargin(); + const maxWidth = !inlineChatWidget.showsAnyPreview() ? 640 : Number.MAX_SAFE_INTEGER; + const width = Math.min(maxWidth, this._notebookEditor.getLayoutInfo().width - leftMargin - rightMargin); - if (this._cell.chatHeight === heightWithPadding) { - return; - } - - this._cell.chatHeight = heightWithPadding; - this._partContainer.style.height = `${heightWithPadding - surrounding}px`; + inlineChatWidget.layout(new Dimension(width, 80 + WIDGET_MARGIN_BOTTOM)); + inlineChatWidget.domNode.style.width = `${width}px`; + widgetContainer.style.left = `${leftMargin}px`; } - async show(input?: string, autoSend?: boolean) { - this._isVisible = true; - if (!this._widget) { - const editor = this._getCellEditor(); - if (editor) { - this._initialize(editor); - } - } + override dispose() { + this._notebookEditor.changeViewZones(accessor => { + accessor.removeZone(this.id); + }); + this.domNode.remove(); + super.dispose(); + } +} - this._partContainer.style.display = 'flex'; - this._widget?.focus(); - this._widget?.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); - this._ctxVisible.set(true); - this._ctxCellWidgetFocused.set(true); - this._updateHeight(); - - this._sessionCtor = createCancelablePromise(async token => { - if (this._cell.editorAttached) { - const editor = this._getCellEditor(); - if (editor) { - await this._startSession(editor, token); - } - } else { - await Event.toPromise(Event.once(this._cell.onDidChangeEditorAttachState)); - if (token.isCancellationRequested) { - return; - } +export class NotebookChatController extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.chatController'; + static counter: number = 0; - const editor = this._getCellEditor(); - if (editor) { - await this._startSession(editor, token); - } - } + public static get(editor: INotebookEditor): NotebookChatController | null { + return editor.getContribution(NotebookChatController.id); + } + private _strategy: EditStrategy | undefined; + private _sessionCtor: CancelablePromise | undefined; + private _activeSession?: Session; + private readonly _ctxHasActiveRequest: IContextKey; + private readonly _ctxCellWidgetFocused: IContextKey; + private readonly _ctxLastResponseType: IContextKey; + private _widget: NotebookChatWidget | undefined; + constructor( + private readonly _notebookEditor: INotebookEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ICommandService private readonly _commandService: ICommandService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, + @IModelService private readonly _modelService: IModelService, + @ILanguageService private readonly _languageService: ILanguageService, - if (this._widget) { - this._widget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); - this._widget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); - this._widget.focus(); - } + ) { + super(); + this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); + this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); + this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + } - if (this._widget && input) { - this._widget.value = input; + run(index: number, input: string | undefined, autoSend: boolean | undefined): void { + if (this._widget) { + if (this._widget.afterModelPosition === index) { + // this._chatZone + // chatZone focus + } else { + const window = getWindow(this._widget.domNode); + this._widget.dispose(); + this._widget = undefined; - if (autoSend) { - this.acceptInput(); - } + scheduleAtNextAnimationFrame(window, () => { + this._createWidget(index, input, autoSend); + }); } - }); - } - async focusWidget() { - this._widget?.focus(); - } - - private _getCellEditor() { - const editors = this._notebookEditor.codeEditors.find(editor => editor[0] === this._chatPart.activeCell); - if (!editors || !editors[1].hasModel()) { return; } - const editor = editors[1]; - return editor; + this._createWidget(index, input, autoSend); + // TODO: reveal widget to the center if it's out of the viewport } - private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } + private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + const viewZoneContainer = document.createElement('div'); + viewZoneContainer.classList.add('monaco-editor'); + const widgetContainer = document.createElement('div'); + widgetContainer.style.position = 'absolute'; + viewZoneContainer.appendChild(widgetContainer); + + const fakeParentEditorElement = document.createElement('div'); + + const fakeParentEditor = this._instantiationService.createInstance( + CodeEditorWidget, + fakeParentEditorElement, + { + }, + { isSimpleWidget: true } + ); - const session = await this._inlineChatSessionService.createSession( - editor, - { editMode: EditMode.Live }, - token + const inputBoxPath = `/notebook-chat-input0-${NotebookChatController.counter++}`; + const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); + const result: ITextModel = this._modelService.createModel('', null, inputUri, false); + fakeParentEditor.setModel(result); + + const inlineChatWidget = this._instantiationService.createInstance( + InlineChatWidget, + fakeParentEditor, + { + menuId: MENU_CELL_CHAT_INPUT, + widgetMenuId: MENU_CELL_CHAT_WIDGET, + statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, + feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK + } ); + inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); + inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); + widgetContainer.appendChild(inlineChatWidget.domNode); + + this._notebookEditor.changeViewZones(accessor => { + const id = accessor.addZone({ + afterModelPosition: index, + heightInPx: 80 + WIDGET_MARGIN_BOTTOM, + domNode: viewZoneContainer + }); + + this._widget = new NotebookChatWidget( + this._notebookEditor, + id, + viewZoneContainer, + widgetContainer, + inlineChatWidget, + fakeParentEditor, + index, + 80 + WIDGET_MARGIN_BOTTOM, + this._languageService + ); + + disposableTimeout(() => { + this._ctxCellWidgetFocused.set(true); + this._widget?.focus(); + }, 0, this._store); + + this._sessionCtor = createCancelablePromise(async token => { + + if (fakeParentEditor.hasModel()) { + this._startSession(fakeParentEditor, token); + + if (this._widget) { + this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); + this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); + this._widget.focus(); + } - if (!session) { - return; - } + if (this._widget && input) { + this._widget.inlineChatWidget.value = input; - this._activeSession = session; - this._strategy = new EditStrategy(session); + if (autoSend) { + this.acceptInput(); + } + } + } + }); + }); } async acceptInput() { assertType(this._activeSession); assertType(this._widget); - this._activeSession.addInput(new SessionPrompt(this._widget.value)); + this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); assertType(this._activeSession.lastInput); - const value = this._activeSession.lastInput.value; - const editors = this._notebookEditor.codeEditors.find(editor => editor[0] === this._chatPart.activeCell); - if (!editors || !editors[1].hasModel()) { + const editor = this._widget.parentEditor; + const model = editor.getModel(); + + if (!editor.hasModel() || !model) { return; } - const editor = editors[1]; - this._ctxHasActiveRequest.set(true); - this._widget?.updateProgress(true); + this._widget?.inlineChatWidget.updateProgress(true); const request: IInlineChatRequest = { requestId: generateUuid(), @@ -307,10 +322,12 @@ export class NotebookCellChatController extends Disposable { selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, live: true, - previewDocument: editor.getModel().uri, + previewDocument: model.uri, withIntentDetection: true, // TODO: don't hard code but allow in corresponding UI to run without intent detection? }; + //TODO: update progress in a newly inserted cell below the widget instead of the fake editor + const requestCts = new CancellationTokenSource(); const progressEdits: TextEdit[][] = []; const progressiveEditsQueue = new Queue(); @@ -325,8 +342,8 @@ export class NotebookCellChatController extends Disposable { } if (data.message) { - this._widget?.updateToolbar(false); - this._widget?.updateInfo(data.message); + this._widget?.inlineChatWidget.updateToolbar(false); + this._widget?.inlineChatWidget.updateInfo(data.message); } if (data.edits?.length) { @@ -341,7 +358,7 @@ export class NotebookCellChatController extends Disposable { // making changes goes into a queue because otherwise the async-progress time will // influence the time it takes to receive the changes and progressive typing will // become infinitely fast - await this._makeChanges(editor, data.edits!, data.editsShouldBeInstant + await this._makeChanges(data.edits!, data.editsShouldBeInstant ? undefined : { duration: progressiveEditsAvgDuration.value, token: progressiveEditsCts.token } ); @@ -353,10 +370,10 @@ export class NotebookCellChatController extends Disposable { let response: ReplyResponse | ErrorResponse | EmptyResponse; try { - this._widget?.updateChatMessage(undefined); - this._widget?.updateFollowUps(undefined); - this._widget?.updateProgress(true); - this._widget?.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); + this._widget?.inlineChatWidget.updateChatMessage(undefined); + this._widget?.inlineChatWidget.updateFollowUps(undefined); + this._widget?.inlineChatWidget.updateProgress(true); + this._widget?.inlineChatWidget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); this._ctxHasActiveRequest.set(true); const reply = await raceCancellationError(Promise.resolve(task), requestCts.token); @@ -372,7 +389,7 @@ export class NotebookCellChatController extends Disposable { const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); const replyResponse = response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), progressEdits, request.requestId); for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { - await this._makeChanges(editor, replyResponse.allLocalEdits[i], undefined); + await this._makeChanges(replyResponse.allLocalEdits[i], undefined); } if (this._activeSession?.provider.provideFollowups) { @@ -380,9 +397,9 @@ export class NotebookCellChatController extends Disposable { const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, replyResponse.raw, followupCts.token); if (followups && this._widget) { const widget = this._widget; - widget.updateFollowUps(followups, async followup => { + widget.inlineChatWidget.updateFollowUps(followups, async followup => { if (followup.kind === 'reply') { - widget.value = followup.message; + widget.inlineChatWidget.value = followup.message; this.acceptInput(); } else { await this.acceptSession(); @@ -396,70 +413,51 @@ export class NotebookCellChatController extends Disposable { response = new ErrorResponse(e); } finally { this._ctxHasActiveRequest.set(false); - this._widget?.updateProgress(false); - this._widget?.updateInfo(''); - this._widget?.updateToolbar(true); + this._widget?.inlineChatWidget.updateProgress(false); + this._widget?.inlineChatWidget.updateInfo(''); + this._widget?.inlineChatWidget.updateToolbar(true); } this._ctxHasActiveRequest.set(false); - this._widget?.updateProgress(false); - this._widget?.updateInfo(''); - this._widget?.updateToolbar(true); + this._widget?.inlineChatWidget.updateProgress(false); + this._widget?.inlineChatWidget.updateInfo(''); + this._widget?.inlineChatWidget.updateToolbar(true); this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); this._ctxLastResponseType.set(response instanceof ReplyResponse ? response.raw.type : undefined); } - async cancelCurrentRequest(discard: boolean) { - if (discard) { - this._strategy?.cancel(); - } - + private async _startSession(editor: IActiveCodeEditor, token: CancellationToken) { if (this._activeSession) { this._inlineChatSessionService.releaseSession(this._activeSession); } - this._activeSession = undefined; + const session = await this._inlineChatSessionService.createSession( + editor, + { editMode: EditMode.Live }, + token + ); + + if (!session) { + return; + } + + this._activeSession = session; + this._strategy = new EditStrategy(session); } - async acceptSession() { + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { assertType(this._activeSession); assertType(this._strategy); + assertType(this._widget); - const editor = this._getCellEditor(); - assertType(editor); - - try { - await this._strategy.apply(editor); - } catch (_err) { } - - this._inlineChatSessionService.releaseSession(this._activeSession); - this.dismiss(false); - } + const editor = await this._widget.getEditingCellEditor(); - async dismiss(discard: boolean) { - this._isVisible = false; - this._partContainer.style.display = 'none'; - this.cancelCurrentRequest(discard); - this._ctxCellWidgetFocused.set(false); - this._ctxVisible.set(false); - this._ctxLastResponseType.reset(); - this._widget?.reset(); - this._updateHeight(); - } - - async feedbackLast(kind: InlineChatResponseFeedbackKind) { - if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); - this._widget?.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); + if (!editor || !editor.hasModel()) { + return; } - } - private async _makeChanges(editor: IActiveCodeEditor, edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { - assertType(this._activeSession); - assertType(this._strategy); - - const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, edits); + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits); // this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); if (moreMinimalEdits?.length === 0) { @@ -484,9 +482,91 @@ export class NotebookCellChatController extends Disposable { // this._ignoreModelContentChanged = false; } } + + async acceptSession() { + assertType(this._activeSession); + assertType(this._strategy); + + const editor = this._widget?.parentEditor; + if (!editor?.hasModel()) { + return; + } + + try { + await this._strategy.apply(editor); + } catch (_err) { } + + this._inlineChatSessionService.releaseSession(this._activeSession); + this.dismiss(); + } + + async focusNext() { + if (!this._widget) { + return; + } + + const index = this._widget.afterModelPosition; + const cell = this._notebookEditor.cellAt(index); + if (!cell) { + return; + } + + await this._notebookEditor.focusNotebookCell(cell, 'editor'); + } + + focusNearestWidget(index: number, direction: 'above' | 'below') { + switch (direction) { + case 'above': + if (this._widget?.afterModelPosition === index) { + this._widget.focus(); + } + break; + case 'below': + if (this._widget?.afterModelPosition === index + 1) { + this._widget.focus(); + } + break; + default: + break; + } + } + + + async cancelCurrentRequest(discard: boolean) { + if (discard) { + this._strategy?.cancel(); + } + + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } + + this._activeSession = undefined; + } + + discard() { + this._strategy?.cancel(); + this._widget?.discardChange(); + } + + async feedbackLast(kind: InlineChatResponseFeedbackKind) { + if (this._activeSession?.lastExchange && this._activeSession.lastExchange.response instanceof ReplyResponse) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); + this._widget?.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 }); + } + } + + + dismiss() { + this._ctxCellWidgetFocused.set(false); + this._sessionCtor?.cancel(); + this._sessionCtor = undefined; + this._widget?.dispose(); + this._widget = undefined; + } } -class EditStrategy { +export class EditStrategy { private _editCount: number = 0; constructor( @@ -558,3 +638,7 @@ class EditStrategy { } } } + + +registerNotebookContribution(NotebookChatController.id, NotebookChatController); + diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts index 461dc85c66419..059f5ab7b1417 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatPart.ts @@ -3,54 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; -import { NotebookCellChatController } from 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatController'; - -import 'vs/workbench/contrib/notebook/browser/view/cellParts/chat/cellChatActions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class CellChatPart extends CellContentPart { - private _controller: NotebookCellChatController | undefined; + // private _controller: NotebookCellChatController | undefined; get activeCell() { return this.currentCell; } constructor( - private readonly _notebookEditor: INotebookEditorDelegate, - private readonly _partContainer: HTMLElement, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + _notebookEditor: INotebookEditorDelegate, + _partContainer: HTMLElement, ) { super(); } override didRenderCell(element: ICellViewModel): void { - this._controller?.dispose(); - const enabled = this._configurationService.getValue(NotebookSetting.cellChat); - if (enabled) { - this._controller = this._instantiationService.createInstance(NotebookCellChatController, this._notebookEditor, this, element, this._partContainer); - } - super.didRenderCell(element); } override unrenderCell(element: ICellViewModel): void { - this._controller?.dispose(); - this._controller = undefined; super.unrenderCell(element); } override updateInternalLayoutNow(element: ICellViewModel): void { - this._controller?.layout(); } override dispose() { - this._controller?.dispose(); - this._controller = undefined; super.dispose(); } } From 75b82a187f428406d6f0d4210ec9c7a58a5240b5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Feb 2024 17:35:00 -0800 Subject: [PATCH 0138/1863] Handle all chat actions with the new floating widget. --- .../controller/chat/cellChatActions.ts | 33 ++++++++++--------- .../chat/notebook.chat.contribution.ts | 6 ++++ .../controller/chat/notebookChatContext.ts | 16 +++++++++ .../controller/chat/notebookChatController.ts | 13 ++------ .../notebook/browser/notebook.contribution.ts | 1 + .../notebook/browser/view/notebookCellList.ts | 5 +++ .../browser/view/notebookCellListView.ts | 19 ++++++++++- 7 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 8d6325c6b2677..5959e33fcd715 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,7 +14,8 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS, NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -87,7 +88,7 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -106,7 +107,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { await NotebookChatController.get(context.notebookEditor)?.focusNext(); } }); @@ -171,7 +172,7 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -187,12 +188,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.cancelCurrentRequest(false); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -207,7 +208,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.dismiss(); } }); @@ -237,12 +238,12 @@ registerAction2(class extends NotebookAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.acceptSession(); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -262,12 +263,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.discard(); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.feedbackHelpful', @@ -282,12 +283,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Helpful); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.feedbackUnhelpful', @@ -302,12 +303,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Unhelpful); } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super({ id: 'notebook.cell.reportIssueForBug', @@ -322,7 +323,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.feedbackLast(InlineChatResponseFeedbackKind.Bug); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts new file mode 100644 index 0000000000000..995340fb19188 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts new file mode 100644 index 0000000000000..af75c65626df3 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * 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 CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); +export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); +export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); +export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); +export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); +export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); +export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 86aadb52b8d08..99b982368a2a9 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -26,9 +26,8 @@ import { ICursorStateComputer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; -import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AsyncProgress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; @@ -41,21 +40,13 @@ import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inline import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import 'vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions'; - -export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); -export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); -export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); -export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); -export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); -export const MENU_CELL_CHAT_WIDGET_FEEDBACK = MenuId.for('cellChatWidget.feedback'); -export const MENU_CELL_CHAT_WIDGET_TOOLBAR = MenuId.for('cellChatWidget.toolbar'); const WIDGET_MARGIN_BOTTOM = 16; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index bcc27365df5ec..cf5cf21758746 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -67,6 +67,7 @@ import 'vs/workbench/contrib/notebook/browser/controller/editActions'; import 'vs/workbench/contrib/notebook/browser/controller/cellOutputActions'; import 'vs/workbench/contrib/notebook/browser/controller/apiActions'; import 'vs/workbench/contrib/notebook/browser/controller/foldingController'; +import 'vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution'; // Editor Contribution import 'vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 62cf6f3b7665d..e613e8af3e619 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -592,6 +592,11 @@ export class NotebookCellList extends WorkbenchList implements ID return modelIndex; } + if (modelIndex >= this.hiddenRangesPrefixSum.getTotalSum()) { + // it's already after the last hidden range + return this.hiddenRangesPrefixSum.getTotalSum(); + } + return this.hiddenRangesPrefixSum.getIndexOf(modelIndex).index; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index c2752fe8103d6..8ff1afea0c167 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -49,6 +49,15 @@ export class NotebookCellsLayout implements IRangeMap { this._size = this._paddingTop; } + getWhitespaces(): IWhitespace[] { + return this._whitespace; + } + + restoreWhitespace(items: IWhitespace[]) { + this._whitespace = items; + this._size = this._paddingTop + this._items.reduce((total, item) => total + item.size, 0) + this._whitespace.reduce((total, ws) => total + ws.size, 0); + } + /** */ splice(index: number, deleteCount: number, items?: IItem[] | undefined): void { @@ -240,7 +249,15 @@ export class NotebookCellListView extends ListView { } protected override createRangeMap(paddingTop: number): IRangeMap { - return new NotebookCellsLayout(paddingTop); + const existingMap = this.rangeMap as NotebookCellsLayout | undefined; + if (existingMap) { + const layout = new NotebookCellsLayout(paddingTop); + layout.restoreWhitespace(existingMap.getWhitespaces()); + return layout; + } else { + return new NotebookCellsLayout(paddingTop); + } + } insertWhitespace(afterPosition: number, size: number): string { From 4be946d5d9a7a6d45d6657712b42697e96a30b3f Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 9 Feb 2024 17:56:44 -0800 Subject: [PATCH 0139/1863] Update slash commands --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 99b982368a2a9..a336ad2c6ea8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -274,6 +274,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); + this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); this._widget.focus(); } @@ -304,6 +305,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } this._ctxHasActiveRequest.set(true); + this._widget.inlineChatWidget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._widget?.inlineChatWidget.updateProgress(true); const request: IInlineChatRequest = { From 56c54e7fef7f168e9d6eab921301fc91479b5b19 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 9 Feb 2024 19:46:02 -0800 Subject: [PATCH 0140/1863] Trim camelCase filter instead of returning null (#204885) Fixes https://github.com/microsoft/vscode/issues/204757 --- src/vs/base/common/filters.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index f7f0a43972f51..03cb85813cc83 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -280,8 +280,9 @@ export function matchesCamelCase(word: string, camelCaseWord: string): IMatch[] return null; } + // TODO: Consider removing this check if (camelCaseWord.length > 60) { - return null; + camelCaseWord = camelCaseWord.substring(0, 60); } const analysis = analyzeCamelCaseWord(camelCaseWord); From b49c1c1170bb947bc6a36c0aacd3a9e4db0c518a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 10:40:01 -0300 Subject: [PATCH 0141/1863] Add ChatAgentResult2#metadata (#204851) * Support serializable metadata on 'result' object from chat agent * Fix 'errorDetails' on the VM * Fix acceptAction, get rid of generic parameter on ChatAgent * Use result metadata for followups * Use serialized result for history * Don't share metadata between agents * Add a test for result metadata * Fix build --- .../src/singlefolder-tests/chat.test.ts | 35 ++++++- .../api/browser/mainThreadChatAgents2.ts | 8 +- .../workbench/api/common/extHost.protocol.ts | 6 +- .../api/common/extHostChatAgents2.ts | 93 ++++++++----------- .../api/common/extHostTypeConverters.ts | 11 ++- .../browser/actions/chatCodeblockActions.ts | 5 + .../chat/browser/actions/chatTitleActions.ts | 3 + .../contrib/chat/browser/chatQuick.ts | 4 +- .../contrib/chat/browser/chatWidget.ts | 3 +- .../contrib/chat/common/chatAgents.ts | 12 +-- .../contrib/chat/common/chatModel.ts | 48 ++++++---- .../contrib/chat/common/chatParserTypes.ts | 15 +++ .../contrib/chat/common/chatService.ts | 14 +-- .../contrib/chat/common/chatServiceImpl.ts | 46 ++++----- .../contrib/chat/common/chatViewModel.ts | 13 ++- .../__snapshots__/Chat_can_deserialize.0.snap | 12 +-- .../__snapshots__/Chat_can_serialize.1.snap | 10 +- .../chat/test/common/chatService.test.ts | 9 +- .../chat/test/common/voiceChat.test.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 23 ++--- .../vscode.proposed.chatAgents2Additions.d.ts | 6 +- .../vscode.proposed.defaultChatAgent.d.ts | 2 +- 22 files changed, 215 insertions(+), 165 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index fd23f14d5076b..cdb80bbb50e5c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; +import { CancellationToken, chat, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; suite('chat', () => { @@ -67,4 +67,37 @@ suite('chat', () => { assert.strictEqual(request.prompt, 'hi [#myVar](values:myVar)'); assert.strictEqual(request.variables['myVar'][0].value, 'myValue'); }); + + test('result metadata is returned to the followup provider', async () => { + disposables.push(interactive.registerInteractiveSessionProvider('provider', { + prepareSession: (_token: CancellationToken): ProviderResult => { + return { + requester: { name: 'test' }, + responder: { name: 'test' }, + }; + }, + })); + + const deferred = new DeferredPromise(); + const agent = chat.createChatAgent('agent', (_request, _context, _progress, _token) => { + return { metadata: { key: 'value' } }; + }); + agent.isDefault = true; + agent.subCommandProvider = { + provideSubCommands: (_token) => { + return [{ name: 'hello', description: 'Hello' }]; + } + }; + agent.followupProvider = { + provideFollowups(result, _token) { + deferred.complete(result); + return []; + }, + }; + disposables.push(agent); + + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + const result = await deferred.p; + assert.deepStrictEqual(result.metadata, { key: 'value' }); + }); }); diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 0f1263149b9ff..cdd867d016068 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -59,9 +59,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA for (const [handle, agent] of this._agents) { if (agent.name === e.agentId) { if (e.action.kind === 'vote') { - this._proxy.$acceptFeedback(handle, e.sessionId, e.requestId, e.action.direction); + this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action.direction); } else { - this._proxy.$acceptAction(handle, e.sessionId, e.requestId, e); + this._proxy.$acceptAction(handle, e.result || {}, e); } break; } @@ -87,12 +87,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.delete(request.requestId); } }, - provideFollowups: async (sessionId, token): Promise => { + provideFollowups: async (result, token): Promise => { if (!this._agents.get(handle)?.hasFollowups) { return []; } - return this._proxy.$provideFollowups(handle, sessionId, token); + return this._proxy.$provideFollowups(handle, result, token); }, get lastSlashCommands() { return lastSlashCommands; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b7944086d302b..4b49c05c2d7d9 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1221,9 +1221,9 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise; - $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; - $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; + $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise; + $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; + $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined>; $provideSampleQuestions(handle: number, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 191941e87d634..284caf0032b6a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -157,11 +157,9 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private static _idPool = 0; - private readonly _agents = new Map>(); + private readonly _agents = new Map(); private readonly _proxy: MainThreadChatAgentsShape2; - private readonly _previousResultMap: Map = new Map(); - private readonly _resultsBySessionAndRequestId: Map> = new Map(); private readonly _sessionDisposables: DisposableMap = new DisposableMap(); constructor( @@ -173,9 +171,9 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { const handle = ExtHostChatAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); this._proxy.$registerAgent(handle, name, {}); @@ -183,10 +181,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } async $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { - // Clear the previous result so that $acceptFeedback or $acceptAction during a request will be ignored. - // We may want to support sending those during a request. - this._previousResultMap.delete(request.sessionId); - const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -203,7 +197,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistory(agent, request, context); + const convertedHistory = await this.prepareHistory(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, @@ -212,21 +206,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { ); return await raceCancellation(Promise.resolve(task).then((result) => { - if (result) { - this._previousResultMap.set(request.sessionId, result); - let sessionResults = this._resultsBySessionAndRequestId.get(request.sessionId); - if (!sessionResults) { - sessionResults = new Map(); - this._resultsBySessionAndRequestId.set(request.sessionId, sessionResults); + if (result?.metadata) { + try { + JSON.stringify(result.metadata); + } catch (err) { + const msg = `result.metadata MUST be JSON.stringify-able. Got error: ${err.message}`; + this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] ${msg}`, agent.extension); + return { errorDetails: { message: msg }, timings: stream.timings }; } - sessionResults.set(request.requestId, result); - - return { errorDetails: result.errorDetails, timings: stream.timings }; - } else { - this._previousResultMap.delete(request.sessionId); } - - return undefined; + return { errorDetails: result?.errorDetails, timings: stream.timings, metadata: result?.metadata }; }), token); } catch (e) { @@ -239,22 +228,22 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistory(agent: ExtHostChatAgent, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { + private async prepareHistory(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { return coalesce(await Promise.all(context.history .map(async h => { - const result = request.agentId === h.request.agentId && this._resultsBySessionAndRequestId.get(request.sessionId)?.get(h.request.requestId) - || h.result; + const ehResult = typeConvert.ChatAgentResult.to(h.result); + const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + ehResult : + { ...ehResult, metadata: undefined }; return { request: typeConvert.ChatAgentRequest.to(h.request), response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), - result + result, } satisfies vscode.ChatAgentHistoryEntry; }))); } $releaseSession(sessionId: string): void { - this._previousResultMap.delete(sessionId); - this._resultsBySessionAndRequestId.delete(sessionId); this._sessionDisposables.deleteAndDispose(sessionId); } @@ -267,30 +256,23 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.provideSlashCommands(token); } - $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise { + $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } - const result = this._previousResultMap.get(sessionId); - if (!result) { - return Promise.resolve([]); - } - - return agent.provideFollowups(result, token); + const ehResult = typeConvert.ChatAgentResult.to(result); + return agent.provideFollowups(ehResult, token); } - $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { + $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); - if (!result) { - return; - } + const ehResult = typeConvert.ChatAgentResult.to(result); let kind: extHostTypes.ChatAgentResultFeedbackKind; switch (vote) { case InteractiveSessionVoteDirection.Down: @@ -300,29 +282,28 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { kind = extHostTypes.ChatAgentResultFeedbackKind.Helpful; break; } - agent.acceptFeedback(reportIssue ? Object.freeze({ result, kind, reportIssue }) : Object.freeze({ result, kind })); + agent.acceptFeedback(reportIssue ? + Object.freeze({ result: ehResult, kind, reportIssue }) : + Object.freeze({ result: ehResult, kind })); } - $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void { + $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); - if (!result) { - return; - } if (action.action.kind === 'vote') { // handled by $acceptFeedback return; } + const ehResult = typeConvert.ChatAgentResult.to(result); if (action.action.kind === 'command') { const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; - agent.acceptAction(Object.freeze({ action: commandAction, result })); + agent.acceptAction(Object.freeze({ action: commandAction, result: ehResult })); return; } else { - agent.acceptAction(Object.freeze({ action: action.action, result })); + agent.acceptAction(Object.freeze({ action: action.action, result: ehResult })); } } @@ -355,10 +336,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } -class ExtHostChatAgent { +class ExtHostChatAgent { private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; - private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; + private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -367,7 +348,7 @@ class ExtHostChatAgent { private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; - private _onDidReceiveFeedback = new Emitter>(); + private _onDidReceiveFeedback = new Emitter(); private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; @@ -381,7 +362,7 @@ class ExtHostChatAgent { private readonly _callback: vscode.ChatAgentExtendedHandler, ) { } - acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { this._onDidReceiveFeedback.fire(feedback); } @@ -415,7 +396,7 @@ class ExtHostChatAgent { })); } - async provideFollowups(result: TResult, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -458,7 +439,7 @@ class ExtHostChatAgent { return content?.map(f => typeConvert.ChatFollowup.from(f)); } - get apiAgent(): vscode.ChatAgent2 { + get apiAgent(): vscode.ChatAgent2 { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -630,7 +611,7 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatAgent2; + } satisfies vscode.ChatAgent2; } invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6e0cc73d248a6..5886cc9bfc386 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -34,7 +34,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; -import { IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -2628,6 +2628,15 @@ export namespace ChatAgentCompletionItem { } } +export namespace ChatAgentResult { + export function to(result: IChatAgentResult): vscode.ChatAgentResult2 { + return { + errorDetails: result.errorDetails, + metadata: result.metadata, + }; + } +} + export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 0076b4eb1f8ae..592ee91e25658 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -108,6 +108,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -151,6 +152,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -325,6 +327,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'insert', codeBlockIndex: context.codeBlockIndex, @@ -370,6 +373,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'insert', codeBlockIndex: context.codeBlockIndex, @@ -464,6 +468,7 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, + result: context.element.result, action: { kind: 'runInTerminal', codeBlockIndex: context.codeBlockIndex, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index f98c6fc65247b..8204a71dea17f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -54,6 +54,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, @@ -93,6 +94,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, @@ -131,6 +133,7 @@ export function registerChatTitleActions() { agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, + result: item.result, action: { kind: 'bug' } diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index a23257610dc32..6e06936d15d01 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -289,13 +289,13 @@ class QuickChat extends Disposable { } for (const request of this.model.getRequests()) { - if (request.response?.response.value || request.response?.errorDetails) { + 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, - errorDetails: request.response.errorDetails, + result: request.response.result, followups: request.response.followups }); } else if (request.message) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2e4fffac46517..13bd6f3d7727e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -455,7 +455,8 @@ export class ChatWidget extends Disposable implements IChatWidget { providerId: this.viewModel.providerId, sessionId: this.viewModel.sessionId, requestId: e.response.requestId, - agentId: e.response?.agent?.id, + agentId: e.response.agent?.id, + result: e.response.result, action: { kind: 'followUp', followup: e.followup diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index cd4e3b1b48931..2da3567a20e68 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -31,7 +31,7 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - provideFollowups?(sessionId: string, token: CancellationToken): Promise; + provideFollowups?(result: IChatAgentResult, token: CancellationToken): Promise; lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; @@ -89,13 +89,13 @@ export interface IChatAgentRequest { } export interface IChatAgentResult { - // delete, keep while people are still using the previous API - followUp?: IChatFollowup[]; errorDetails?: IChatResponseErrorDetails; timings?: { firstProgress?: number; totalElapsed: number; }; + /** Extra properties that the agent can use to identify a result */ + readonly metadata?: { readonly [key: string]: any }; } export const IChatAgentService = createDecorator('chatAgentService'); @@ -105,7 +105,7 @@ export interface IChatAgentService { readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - getFollowups(id: string, sessionId: string, token: CancellationToken): Promise; + getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise; getAgents(): Array; getAgent(id: string): IChatAgent | undefined; getDefaultAgent(): IChatAgent | undefined; @@ -184,7 +184,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return await data.agent.invoke(request, progress, history, token); } - async getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { + async getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id}`); @@ -194,6 +194,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return []; } - return data.agent.provideFollowups(sessionId, token); + return data.agent.provideFollowups(result, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 5b09beef63be8..68b5b843ee367 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -14,9 +14,9 @@ import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatContent, IChatContentInlineReference, IChatContentReference, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext, IChatCommandButton } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { @@ -73,7 +73,7 @@ export interface IChatResponseModel { readonly isStale: boolean; readonly vote: InteractiveSessionVoteDirection | undefined; readonly followups?: IChatFollowup[] | undefined; - readonly errorDetails?: IChatResponseErrorDetails; + readonly result?: IChatAgentResult; setVote(vote: InteractiveSessionVoteDirection): void; } @@ -227,8 +227,8 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._response; } - public get errorDetails(): IChatResponseErrorDetails | undefined { - return this._errorDetails; + public get result(): IChatAgentResult | undefined { + return this._result; } public get providerId(): string { @@ -282,7 +282,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, - private _errorDetails?: IChatResponseErrorDetails, + private _result?: IChatAgentResult, followups?: ReadonlyArray ) { super(); @@ -321,13 +321,13 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._onDidChange.fire(); } - setErrorDetails(errorDetails?: IChatResponseErrorDetails): void { - this._errorDetails = errorDetails; + setResult(result: IChatAgentResult): void { + this._result = result; this._onDidChange.fire(); } - complete(errorDetails?: IChatResponseErrorDetails): void { - if (errorDetails?.responseIsRedacted) { + complete(): void { + if (this._result?.errorDetails?.responseIsRedacted) { this._response.clear(); } @@ -380,7 +380,8 @@ export interface ISerializableChatRequestData { response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; slashCommand?: IChatAgentCommand; - responseErrorDetails: IChatResponseErrorDetails | undefined; + // responseErrorDetails: IChatResponseErrorDetails | undefined; + result?: IChatAgentResult; // Optional for backcompat followups: ReadonlyArray | undefined; isCanceled: boolean | undefined; vote: InteractiveSessionVoteDirection | undefined; @@ -561,13 +562,18 @@ export class ChatModel extends Disposable implements IChatModel { typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); + // Only old messages don't have variableData const variableData: IChatRequestVariableData = raw.variableData ?? { message: parsedRequest.text, variables: {} }; const request = new ChatRequestModel(this, parsedRequest, variableData); - if (raw.response || raw.responseErrorDetails) { + if (raw.response || raw.result || (raw as any).responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format revive(raw.agent) : undefined; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); + + // Port entries from old format + const result = 'responseErrorDetails' in raw ? + { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, result, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.applyReference(revive(raw.usedContext)); } @@ -700,7 +706,7 @@ export class ChatModel extends Disposable implements IChatModel { } } - setResponse(request: ChatRequestModel, rawResponse: IChatResponse): void { + setResponse(request: ChatRequestModel, result: IChatAgentResult): void { if (!this._session) { throw new Error('completeResponse: No session'); } @@ -709,15 +715,15 @@ export class ChatModel extends Disposable implements IChatModel { request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } - request.response.setErrorDetails(rawResponse.errorDetails); + request.response.setResult(result); } - completeResponse(request: ChatRequestModel, errorDetails: IChatResponseErrorDetails | undefined): void { + completeResponse(request: ChatRequestModel): void { if (!request.response) { throw new Error('Call setResponse before completeResponse'); } - request.response.complete(errorDetails); + request.response.complete(); } setFollowups(request: ChatRequestModel, followups: IChatFollowup[] | undefined): void { @@ -748,8 +754,12 @@ export class ChatModel extends Disposable implements IChatModel { } }), requests: this._requests.map((r): ISerializableChatRequestData => { + const message = { + ...r.message, + parts: r.message.parts.map(p => p && 'toJSON' in p ? (p.toJSON as Function)() : p) + }; return { - message: r.message, + message, variableData: r.variableData, response: r.response ? r.response.response.value.map(item => { @@ -763,7 +773,7 @@ export class ChatModel extends Disposable implements IChatModel { } }) : undefined, - responseErrorDetails: r.response?.errorDetails, + result: r.response?.result, followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 65e4cf2a8fb61..f65c12199b8f0 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -78,6 +78,21 @@ export class ChatRequestAgentPart implements IParsedChatRequestPart { get promptText(): string { return ''; } + + /** + * Don't stringify all the agent methods, just data. + */ + toJSON(): any { + return { + kind: this.kind, + range: this.range, + editorRange: this.editorRange, + agent: { + id: this.agent.id, + metadata: this.agent.metadata + } + }; + } } /** diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bf5c78b9ef29c..665ecb9eb290b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -12,7 +12,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { Command, Location, ProviderResult } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -40,15 +40,6 @@ export interface IChatResponseErrorDetails { responseIsRedacted?: boolean; } -export interface IChatResponse { - session: IChat; - errorDetails?: IChatResponseErrorDetails; - timings?: { - firstProgress?: number; - totalElapsed: number; - }; -} - export interface IChatResponseProgressFileTreeData { label: string; uri: URI; @@ -234,6 +225,7 @@ export interface IChatUserActionEvent { agentId: string | undefined; sessionId: string; requestId: string; + result: IChatAgentResult | undefined; } export interface IChatDynamicRequest { @@ -250,7 +242,7 @@ export interface IChatDynamicRequest { export interface IChatCompleteResponse { message: string | ReadonlyArray; - errorDetails?: IChatResponseErrorDetails; + result?: IChatAgentResult; followups?: IChatFollowup[]; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index eae790915c18c..1957228f12cae 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,13 +20,13 @@ 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 { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, 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 } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -516,7 +516,7 @@ export class ChatService extends Disposable implements IChatService { this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); } - let rawResponse: IChatResponse | null | undefined; + let rawResult: IChatAgentResult | null | undefined; let agentOrCommandFollowups: Promise | undefined = undefined; const defaultAgent = this.chatAgentService.getDefaultAgent(); @@ -536,7 +536,7 @@ export class ChatService extends Disposable implements IChatService { variables: request.variableData.variables, command: request.response.slashCommand?.name }; - history.push({ request: historyRequest, response: request.response.response.value, result: { errorDetails: request.response.errorDetails } }); + history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; @@ -554,13 +554,8 @@ export class ChatService extends Disposable implements IChatService { }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); - rawResponse = { - session: model.session!, - errorDetails: agentResult.errorDetails, - timings: agentResult.timings - }; - agentOrCommandFollowups = agentResult?.followUp ? Promise.resolve(agentResult.followUp) : - this.chatAgentService.getFollowups(agent.id, sessionId, CancellationToken.None); + rawResult = agentResult; + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, CancellationToken.None); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands @@ -577,7 +572,7 @@ export class ChatService extends Disposable implements IChatService { progressCallback(p); }), history, token); agentOrCommandFollowups = Promise.resolve(commandResult?.followUp); - rawResponse = { session: model.session! }; + rawResult = {}; } else { throw new Error(`Cannot handle request`); @@ -586,36 +581,36 @@ export class ChatService extends Disposable implements IChatService { if (token.isCancellationRequested) { return; } else { - if (!rawResponse) { + if (!rawResult) { this.trace('sendRequest', `Provider returned no response for session ${model.sessionId}`); - rawResponse = { session: model.session!, errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; + rawResult = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; } - const result = rawResponse.errorDetails?.responseIsFiltered ? 'filtered' : - rawResponse.errorDetails && gotProgress ? 'errorWithOutput' : - rawResponse.errorDetails ? 'error' : + const result = rawResult.errorDetails?.responseIsFiltered ? 'filtered' : + rawResult.errorDetails && gotProgress ? 'errorWithOutput' : + rawResult.errorDetails ? 'error' : 'success'; this.telemetryService.publicLog2('interactiveSessionProviderInvoked', { providerId: provider.id, - timeToFirstProgress: rawResponse.timings?.firstProgress, - totalTime: rawResponse.timings?.totalElapsed, + timeToFirstProgress: rawResult.timings?.firstProgress, + totalTime: rawResult.timings?.totalElapsed, result, requestType, agent: agentPart?.agent.id ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); - model.setResponse(request, rawResponse); + model.setResponse(request, rawResult); this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`); // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { model.setFollowups(request, followups); - model.completeResponse(request, rawResponse?.errorDetails); + model.completeResponse(request); }); } else { - model.completeResponse(request, rawResponse?.errorDetails); + model.completeResponse(request); } } } finally { @@ -674,14 +669,11 @@ export class ChatService extends Disposable implements IChatService { model.acceptResponseProgress(request, part, true); } } - model.setResponse(request, { - session: model.session!, - errorDetails: response.errorDetails, - }); + model.setResponse(request, response.result || {}); if (response.followups !== undefined) { model.setFollowups(request, response.followups); } - model.completeResponse(request, response.errorDetails); + model.completeResponse(request); } cancelCurrentRequestForSession(sessionId: string): void { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index d55a8b0c07f0b..bd2c325d728f9 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; +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'; @@ -122,6 +122,7 @@ export interface IChatResponseViewModel { readonly vote: InteractiveSessionVoteDirection | undefined; readonly replyFollowups?: IChatFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; + readonly result?: IChatAgentResult; readonly contentUpdateTimings?: IChatLiveUpdateData; renderData?: IChatResponseRenderData; agentAvatarHasBeenRendered?: boolean; @@ -203,7 +204,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { if (typeof responseIdx === 'number' && responseIdx >= 0) { const items = this._items.splice(responseIdx, 1); const item = items[0]; - if (isResponseVM(item)) { + if (item instanceof ChatResponseViewModel) { item.dispose(); } } @@ -334,8 +335,12 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.followups?.filter((f): f is IChatFollowup => f.kind === 'reply'); } - get errorDetails() { - return this._model.errorDetails; + get result() { + return this._model.result; + } + + get errorDetails(): IChatResponseErrorDetails | undefined { + return this.result?.errorDetails; } get vote() { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index e9ee90ae0210f..cd6fa9b8139e9 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -10,6 +10,7 @@ text: "@ChatProviderWithUsedContext test request", parts: [ { + kind: "agent", range: { start: 0, endExclusive: 28 @@ -22,11 +23,8 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { }, - provideSlashCommands: [Function provideSlashCommands], - invoke: [Function invoke] - }, - kind: "agent" + metadata: { } + } }, { range: { @@ -49,8 +47,8 @@ variables: { } }, response: [ ], - responseErrorDetails: undefined, - followups: [ ], + result: { metadata: { metadataKey: "value" } }, + followups: undefined, isCanceled: false, vote: undefined, agent: { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 0c270a4d7f307..8420b35111ed4 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -9,6 +9,7 @@ message: { parts: [ { + kind: "agent", range: { start: 0, endExclusive: 28 @@ -21,11 +22,8 @@ }, agent: { id: "ChatProviderWithUsedContext", - metadata: { }, - provideSlashCommands: [Function provideSlashCommands], - invoke: [Function invoke] - }, - kind: "agent" + metadata: { } + } }, { range: { @@ -49,7 +47,7 @@ variables: { } }, response: [ ], - responseErrorDetails: undefined, + result: { metadata: { metadataKey: "value" } }, followups: undefined, isCanceled: false, vote: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 4bd2322fc6eec..37ef4f4b4bd6e 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -75,7 +75,10 @@ const chatAgentWithUsedContext: IChatAgent = { kind: 'usedContext' }); - return {}; + return { metadata: { metadataKey: 'value' } }; + }, + async provideFollowups(sessionId, token) { + return [{ kind: 'reply', message: 'Something else', tooltip: 'a tooltip' }]; }, }; @@ -110,6 +113,9 @@ suite('Chat', () => { async invoke(request, progress, history, token) { return {}; }, + async provideSlashCommands(token) { + return []; + }, } as IChatAgent; testDisposables.add(chatAgentService.registerAgent(agent)); }); @@ -224,6 +230,7 @@ suite('Chat', () => { const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); + await response.responseCompletePromise; assert.strictEqual(model.getRequests().length, 1); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d90f5b4e3ec21..b986779094a46 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -44,7 +44,7 @@ suite('VoiceChat', () => { readonly onDidChangeAgents = Event.None; registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } - getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } getAgents(): Array { return agents; } getAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 616f85ecc1788..467256275bad5 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -86,9 +86,10 @@ declare module 'vscode' { */ errorDetails?: ChatAgentErrorDetails; - // TODO@API - // add CATCH-all signature [name:string]: string|boolean|number instead of `T extends...` - // readonly metadata: { readonly [key: string]: any }; + /** + * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. + */ + readonly metadata?: { readonly [key: string]: any }; } /** @@ -109,12 +110,12 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatAgentResult2Feedback { /** * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, * and it can be extended with arbitrary properties if needed. */ - readonly result: TResult; + readonly result: ChatAgentResult2; /** * The kind of feedback that was received. @@ -196,16 +197,16 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface ChatAgentFollowupProvider { + export interface ChatAgentFollowupProvider { /** * * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: TResult, token: CancellationToken): ProviderResult; + provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * The short name by which this agent is referred to in the UI, e.g `workspace`. @@ -244,7 +245,7 @@ declare module 'vscode' { /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: ChatAgentFollowupProvider; + followupProvider?: ChatAgentFollowupProvider; // TODO@API @@ -268,7 +269,7 @@ declare module 'vscode' { * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was * previously returned from this chat agent. */ - onDidReceiveFeedback: Event>; + onDidReceiveFeedback: Event; /** * Dispose this agent and free resources @@ -452,7 +453,7 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; /** * Register a variable which can be used in a chat request to any agent. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 49924b8ff0c4f..ad7a4d3c7968c 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - export interface ChatAgent2 { + export interface ChatAgent2 { onDidPerformAction: Event; supportIssueReporting?: boolean; } @@ -152,7 +152,7 @@ declare module 'vscode' { report(value: ChatAgentExtendedProgress): void; }; - export interface ChatAgent2 { + export interface ChatAgent2 { /** * Provide a set of variables that can only be used with this agent. */ @@ -179,7 +179,7 @@ declare module 'vscode' { /** * Create a chat agent with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; } /* diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts index e40a66c044c76..3a4c675a96eb1 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { provideSampleQuestions?(token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * When true, this agent is invoked by default when no other agent is being invoked */ From 28cc5986c2727c09c8f65cc9a3ca1cdeca0e82e8 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 16:47:40 -0300 Subject: [PATCH 0142/1863] Properly cancel provideFollowups request (#204909) And don't block the request on followups completing --- .../contrib/chat/common/chatServiceImpl.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1957228f12cae..4e1222c1a0cd4 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -158,6 +158,8 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidUnregisterProvider = this._register(new Emitter<{ providerId: string }>()); public readonly onDidUnregisterProvider = this._onDidUnregisterProvider.event; + private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap()); + constructor( @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, @@ -463,7 +465,16 @@ export class ChatService extends Disposable implements IChatService { return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; } + private refreshFollowupsCancellationToken(sessionId: string): CancellationToken { + this._sessionFollowupCancelTokens.get(sessionId)?.cancel(); + const newTokenSource = new CancellationTokenSource(); + this._sessionFollowupCancelTokens.set(sessionId, newTokenSource); + + return newTokenSource.token; + } + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { + const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; @@ -555,7 +566,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; - agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, CancellationToken.None); + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands @@ -603,14 +614,11 @@ export class ChatService extends Disposable implements IChatService { model.setResponse(request, rawResult); this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`); - // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 + model.completeResponse(request); if (agentOrCommandFollowups) { agentOrCommandFollowups.then(followups => { model.setFollowups(request, followups); - model.completeResponse(request); }); - } else { - model.completeResponse(request); } } } finally { From b2f1748501a9924b68bd00ce582f1dfb97fddfed Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 10 Feb 2024 18:58:18 -0300 Subject: [PATCH 0143/1863] Rename "subcommand" to "command" (#204911) * Rename "subcommand" to "command" * Fix build --- .../src/singlefolder-tests/chat.test.ts | 10 +++--- .../api/common/extHostChatAgents2.ts | 18 +++++----- .../api/common/extHostTypeConverters.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 35 +++++++++---------- .../vscode.proposed.chatAgents2Additions.d.ts | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index cdb80bbb50e5c..871fb8572ff1a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -37,8 +37,8 @@ suite('chat', () => { return null; }); agent.isDefault = true; - agent.subCommandProvider = { - provideSubCommands: (_token) => { + agent.commandProvider = { + provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; @@ -50,7 +50,7 @@ suite('chat', () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); const request = await deferred.p; - assert.deepStrictEqual(request.subCommand, 'hello'); + assert.deepStrictEqual(request.command, 'hello'); assert.strictEqual(request.prompt, 'friend'); }); @@ -83,8 +83,8 @@ suite('chat', () => { return { metadata: { key: 'value' } }; }); agent.isDefault = true; - agent.subCommandProvider = { - provideSubCommands: (_token) => { + agent.commandProvider = { + provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 284caf0032b6a..558061ddbe86d 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -338,7 +338,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _subCommandProvider: vscode.ChatAgentSubCommandProvider | undefined; + private _commandProvider: vscode.ChatAgentCommandProvider | undefined; private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; @@ -379,10 +379,10 @@ class ExtHostChatAgent { } async provideSlashCommands(token: CancellationToken): Promise { - if (!this._subCommandProvider) { + if (!this._commandProvider) { return []; } - const result = await this._subCommandProvider.provideSubCommands(token); + const result = await this._commandProvider.provideCommands(token); if (!result) { return []; } @@ -462,7 +462,7 @@ class ExtHostChatAgent { 'dark' in this._iconPath ? this._iconPath.dark : undefined, themeIcon: this._iconPath instanceof extHostTypes.ThemeIcon ? this._iconPath : undefined, - hasSlashCommands: this._subCommandProvider !== undefined, + hasSlashCommands: this._commandProvider !== undefined, hasFollowups: this._followupProvider !== undefined, isDefault: this._isDefault, isSecondary: this._isSecondary, @@ -501,11 +501,11 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, - get subCommandProvider() { - return that._subCommandProvider; + get commandProvider() { + return that._commandProvider; }, - set subCommandProvider(v) { - that._subCommandProvider = v; + set commandProvider(v) { + that._commandProvider = v; updateMetadataSoon(); }, get followupProvider() { @@ -606,7 +606,7 @@ class ExtHostChatAgent { , dispose() { disposed = true; - that._subCommandProvider = undefined; + that._commandProvider = undefined; that._followupProvider = undefined; that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 5886cc9bfc386..e1e05919528df 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2610,7 +2610,7 @@ export namespace ChatAgentRequest { return { prompt: request.message, variables: ChatVariable.objectTo(request.variables), - subCommand: request.command, + command: request.command, agentId: request.agentId, }; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 467256275bad5..5c12b9d4fd5f1 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -123,12 +123,12 @@ declare module 'vscode' { readonly kind: ChatAgentResultFeedbackKind; } - export interface ChatAgentSubCommand { + export interface ChatAgentCommand { /** * A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. * - * **Note**: The name should be unique among the subCommands provided by this agent. + * **Note**: The name should be unique among the commands provided by this agent. */ readonly name: string; @@ -138,40 +138,39 @@ declare module 'vscode' { readonly description: string; /** - * When the user clicks this subCommand in `/help`, this text will be submitted to this subCommand + * When the user clicks this command in `/help`, this text will be submitted to this command */ readonly sampleRequest?: string; /** * Whether executing the command puts the * chat into a persistent mode, where the - * subCommand is prepended to the chat input. + * command is prepended to the chat input. */ readonly shouldRepopulate?: boolean; /** * Placeholder text to render in the chat input - * when the subCommand has been repopulated. + * when the command has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? readonly followupPlaceholder?: string; } - // TODO@API NAME: w/o Sub just `ChatAgentCommand` etc pp - export interface ChatAgentSubCommandProvider { + export interface ChatAgentCommandProvider { /** - * Returns a list of subCommands that its agent is capable of handling. A subCommand + * Returns a list of commands that its agent is capable of handling. A command * can be selected by the user and will then be passed to the {@link ChatAgentHandler handler} - * via the {@link ChatAgentRequest.subCommand subCommand} property. + * via the {@link ChatAgentRequest.command command} property. * * * @param token A cancellation token. - * @returns A list of subCommands. The lack of a result can be signaled by returning `undefined`, `null`, or + * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ - provideSubCommands(token: CancellationToken): ProviderResult; + provideCommands(token: CancellationToken): ProviderResult; } /** @@ -238,9 +237,9 @@ declare module 'vscode' { } | ThemeIcon; /** - * This provider will be called to retrieve the agent's subCommands. + * This provider will be called to retrieve the agent's commands. */ - subCommandProvider?: ChatAgentSubCommandProvider; + commandProvider?: ChatAgentCommandProvider; /** * This provider will be called once after each request to retrieve suggested followup questions. @@ -258,7 +257,7 @@ declare module 'vscode' { // onDidClearResult(value: TResult): void; /** - * When the user clicks this agent in `/help`, this text will be submitted to this subCommand + * When the user clicks this agent in `/help`, this text will be submitted to this command */ sampleRequest?: string; @@ -280,10 +279,10 @@ declare module 'vscode' { export interface ChatAgentRequest { /** - * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentSubCommand.name subCommand} + * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentCommand.name command} * are not part of the prompt. * - * @see {@link ChatAgentRequest.subCommand} + * @see {@link ChatAgentRequest.command} */ prompt: string; @@ -293,9 +292,9 @@ declare module 'vscode' { agentId: string; /** - * The name of the {@link ChatAgentSubCommand subCommand} that was selected for this request. + * The name of the {@link ChatAgentCommand command} that was selected for this request. */ - subCommand?: string; + command?: string; variables: Record; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index ad7a4d3c7968c..22bf5f7dbd89a 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -25,7 +25,7 @@ declare module 'vscode' { // TODO@API fit this into the stream export interface ChatAgentDetectedAgent { agentName: string; - command?: ChatAgentSubCommand; + command?: ChatAgentCommand; } // TODO@API fit this into the stream From 2aae82a102da66e566842ff9177bceeb99873970 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Sat, 10 Feb 2024 14:00:35 -0800 Subject: [PATCH 0144/1863] Include extension ID in variable item context (#204879) * remove broken precondition * include extension ID in context --- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostNotebookKernels.ts | 3 ++- src/vs/workbench/contrib/debug/common/debug.ts | 1 + .../notebookVariables/notebookVariableCommands.ts | 2 -- .../notebookVariables/notebookVariablesDataSource.ts | 1 + .../contrib/notebookVariables/notebookVariablesView.ts | 10 ++++++---- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4b49c05c2d7d9..7626fb4364f1c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1121,6 +1121,7 @@ export interface VariablesResult { value: string; hasNamedChildren: boolean; indexedChildrenCount: number; + extensionId: string; } export interface MainThreadNotebookKernelsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 265b29c33d364..e73b409421a52 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -458,7 +458,8 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { value: result.variable.value, type: result.variable.type, hasNamedChildren: result.hasNamedChildren, - indexedChildrenCount: result.indexedChildrenCount + indexedChildrenCount: result.indexedChildrenCount, + extensionId: obj.extensionId.value, }; this.variableStore[variable.id] = result.variable; this._proxy.$receiveVariable(requestId, variable); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index c59762d498bcd..d16c7c8e0cddd 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -88,6 +88,7 @@ export const CONTEXT_VARIABLE_VALUE = new RawContextKey('variableValue' export const CONTEXT_VARIABLE_TYPE = new RawContextKey('variableType', false, { type: 'string', description: nls.localize('variableType', "Type of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_NAME = new RawContextKey('variableName', false, { type: 'string', description: nls.localize('variableName', "Name of the variable, present for debug visualization clauses.") }); export const CONTEXT_VARIABLE_LANGUAGE = new RawContextKey('variableLanguage', false, { type: 'string', description: nls.localize('variableLanguage', "Language of the variable source, present for debug visualization clauses.") }); +export const CONTEXT_VARIABLE_EXTENSIONID = new RawContextKey('variableExtensionId', false, { type: 'string', description: nls.localize('variableExtensionId', "Extension ID of the variable source, present for debug visualization clauses.") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); export const CONTEXT_MULTI_SESSION_DEBUG = new RawContextKey('multiSessionDebug', false, { type: 'boolean', description: nls.localize('multiSessionDebug', "True when there is more than 1 active debug session.") }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index 168480825c377..fcdd865e65e54 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { contextMenuArg } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; @@ -18,7 +17,6 @@ registerAction2(class extends Action2 { id: COPY_NOTEBOOK_VARIABLE_VALUE_ID, title: COPY_NOTEBOOK_VARIABLE_VALUE_LABEL, f1: false, - precondition: ContextKeyExpr.has('value'), }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index 7fdb7141760db..c233e02a8a013 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -26,6 +26,7 @@ export interface INotebookVariableElement { readonly indexStart?: number; readonly hasNamedChildren: boolean; readonly notebook: NotebookTextModel; + readonly extensionId?: string; } export class NotebookVariableDataSource implements IAsyncDataSource { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index f0fc23bfa4fdd..3e4cf3cf7fc8c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_VARIABLE_EXTENSIONID, CONTEXT_VARIABLE_LANGUAGE, CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE } from 'vs/workbench/contrib/debug/common/debug'; import { INotebookScope, INotebookVariableElement, NotebookVariableDataSource } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource'; import { NotebookVariableAccessibilityProvider, NotebookVariableRenderer, NotebookVariablesDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesTree'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -34,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { @@ -110,7 +110,8 @@ export class NotebookVariablesView extends ViewPane { source: element.notebook.uri.toString(), value: element.value, type: element.type, - language: element.language + language: element.language, + extensionId: element.extensionId }; const actions: IAction[] = []; @@ -118,7 +119,8 @@ export class NotebookVariablesView extends ViewPane { [CONTEXT_VARIABLE_NAME.key, element.name], [CONTEXT_VARIABLE_VALUE.key, element.value], [CONTEXT_VARIABLE_TYPE.key, element.type], - [CONTEXT_VARIABLE_LANGUAGE.key, element.language] + [CONTEXT_VARIABLE_LANGUAGE.key, element.language], + [CONTEXT_VARIABLE_EXTENSIONID.key, element.extensionId] ]); const menu = this.menuService.createMenu(MenuId.NotebookVariablesContext, overlayedContext); createAndFillInContextMenuActions(menu, { arg, shouldForwardArgs: true }, actions); From c19383a66d9d69de67d60e0d5774cd9212179240 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 11 Feb 2024 07:39:43 +0100 Subject: [PATCH 0145/1863] Git - add file decoration provider for incoming changes (#204919) * Initial implementation of a file decoration provider and quick diff provider * Refactor file decoration provider * Add incomingChanges to history provider * Move decoration provider * Move things around * Add separate color for renamed incoming change * Remove include that is not needed --- extensions/git/package.json | 40 +++++++++ extensions/git/package.nls.json | 4 + extensions/git/src/decorationProvider.ts | 108 +++++++++++++++++++++-- extensions/git/src/historyProvider.ts | 39 +++++--- 4 files changed, 173 insertions(+), 18 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index bee5fc15c87cc..a36b08c0baa56 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3176,6 +3176,46 @@ "highContrast": "#8db9e2", "highContrastLight": "#1258a7" } + }, + { + "id": "gitDecoration.incomingAddedForegroundColor", + "description": "%colors.incomingAdded%", + "defaults": { + "light": "#587c0c", + "dark": "#81b88b", + "highContrast": "#1b5225", + "highContrastLight": "#374e06" + } + }, + { + "id": "gitDecoration.incomingDeletedForegroundColor", + "description": "%colors.incomingDeleted%", + "defaults": { + "light": "#ad0707", + "dark": "#c74e39", + "highContrast": "#c74e39", + "highContrastLight": "#ad0707" + } + }, + { + "id": "gitDecoration.incomingRenamedForegroundColor", + "description": "%colors.incomingRenamed%", + "defaults": { + "light": "#007100", + "dark": "#73C991", + "highContrast": "#73C991", + "highContrastLight": "#007100" + } + }, + { + "id": "gitDecoration.incomingModifiedForegroundColor", + "description": "%colors.incomingModified%", + "defaults": { + "light": "#895503", + "dark": "#E2C08D", + "highContrast": "#E2C08D", + "highContrastLight": "#895503" + } } ], "configurationDefaults": { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5cc838d25c326..388d5387261e4 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -286,6 +286,10 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", + "colors.incomingAdded": "Color for added incoming resource.", + "colors.incomingDeleted": "Color for deleted incoming resource.", + "colors.incomingRenamed": "Color for renamed incoming resource.", + "colors.incomingModified": "Color for modified incoming resource.", "view.workbench.scm.missing.windows": { "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index c630f00c71234..943af377eb735 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor } from 'vscode'; +import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor, l10n } from 'vscode'; import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource } from './util'; -import { GitErrorCodes, Status } from './api/git'; +import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; +import { Change, GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -153,6 +153,100 @@ class GitDecorationProvider implements FileDecorationProvider { } } +class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { + + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + + private decorations = new Map(); + private readonly disposables: Disposable[] = []; + + constructor(private readonly repository: Repository) { + this.disposables.push(window.registerFileDecorationProvider(this)); + repository.historyProvider.onDidChangeCurrentHistoryItemGroupBase(this.onDidChangeCurrentHistoryItemGroupBase, this, this.disposables); + } + + private async onDidChangeCurrentHistoryItemGroupBase(): Promise { + const newDecorations = new Map(); + await this.collectIncomingChangesFileDecorations(newDecorations); + const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); + + this.decorations = newDecorations; + this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); + } + + private async collectIncomingChangesFileDecorations(bucket: Map): Promise { + for (const change of await this.getIncomingChanges()) { + switch (change.status) { + case Status.INDEX_ADDED: + bucket.set(change.uri.toString(), { + badge: '↓A', + color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), + tooltip: l10n.t('Incoming Changes (added)'), + }); + break; + case Status.DELETED: + bucket.set(change.uri.toString(), { + badge: '↓D', + color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), + tooltip: l10n.t('Incoming Changes (deleted)'), + }); + break; + case Status.INDEX_RENAMED: + bucket.set(change.originalUri.toString(), { + badge: '↓R', + color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), + tooltip: l10n.t('Incoming Changes (renamed)'), + }); + break; + case Status.MODIFIED: + bucket.set(change.uri.toString(), { + badge: '↓M', + color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), + tooltip: l10n.t('Incoming Changes (modified)'), + }); + break; + default: { + bucket.set(change.uri.toString(), { + badge: '↓~', + color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), + tooltip: l10n.t('Incoming Changes'), + }); + break; + } + } + } + } + + private async getIncomingChanges(): Promise { + try { + const historyProvider = this.repository.historyProvider; + const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; + + if (!currentHistoryItemGroup?.base) { + return []; + } + + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); + if (!ancestor) { + return []; + } + + const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); + return changes; + } catch (err) { + return []; + } + } + + provideFileDecoration(uri: Uri): FileDecoration | undefined { + return this.decorations.get(uri.toString()); + } + + dispose(): void { + dispose(this.disposables); + } +} export class GitDecorations { @@ -191,8 +285,12 @@ export class GitDecorations { } private onDidOpenRepository(repository: Repository): void { - const provider = new GitDecorationProvider(repository); - this.providers.set(repository, provider); + const providers = combinedDisposable([ + new GitDecorationProvider(repository), + new GitIncomingChangesFileDecorationProvider(repository) + ]); + + this.providers.set(repository, providers); } private onDidCloseRepository(repository: Repository): void { diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index dbb7b8d8a36e6..80fbbe52799f5 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -6,7 +6,7 @@ import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, filterEvent } from './util'; +import { IDisposable, dispose, filterEvent } from './util'; import { toGitUri } from './uri'; import { Branch, RefType, UpstreamRef } from './api/git'; import { emojify, ensureEmojis } from './emoji'; @@ -17,16 +17,20 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; + private readonly _onDidChangeCurrentHistoryItemGroupBase = new EventEmitter(); + readonly onDidChangeCurrentHistoryItemGroupBase: Event = this._onDidChangeCurrentHistoryItemGroupBase.event; + private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; private _HEAD: Branch | undefined; private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; - get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); + + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(value)); } private historyItemDecorations = new Map(); @@ -55,26 +59,35 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return; } - this._HEAD = this.repository.HEAD; - // Check if HEAD does not support incoming/outgoing (detached commit, tag) - if (!this._HEAD?.name || !this._HEAD?.commit || this._HEAD.type === RefType.Tag) { - this.currentHistoryItemGroup = undefined; + if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); + + this.currentHistoryItemGroup = undefined; + this._HEAD = this.repository.HEAD; return; } this.currentHistoryItemGroup = { - id: `refs/heads/${this._HEAD.name ?? ''}`, - label: this._HEAD.name ?? '', - base: this._HEAD.upstream ? + id: `refs/heads/${this.repository.HEAD.name ?? ''}`, + label: this.repository.HEAD.name ?? '', + base: this.repository.HEAD.upstream ? { - id: `refs/remotes/${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, - label: `${this._HEAD.upstream.remote}/${this._HEAD.upstream.name}`, + id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, + label: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`, } : undefined }; - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(this.currentHistoryItemGroup)); + // Check if Upstream has changed + if (force || + this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || + this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote || + this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); + this._onDidChangeCurrentHistoryItemGroupBase.fire(); + } + + this._HEAD = this.repository.HEAD; } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { @@ -216,6 +229,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } dispose(): void { - this.disposables.forEach(d => d.dispose()); + dispose(this.disposables); } } From a36fce7bd78ec12b55b96cc97b4f0d6d46b19dd8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Feb 2024 18:04:30 +0100 Subject: [PATCH 0146/1863] voice - use `startsWith` --- .../workbench/contrib/chat/common/voiceChat.ts | 17 +++++++++++------ .../contrib/chat/test/common/voiceChat.test.ts | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index 8d277ba99319b..ff8679fa3ea56 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { rtrim, startsWithIgnoreCase } from 'vs/base/common/strings'; +import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -63,11 +63,16 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly AGENT_PREFIX = chatAgentLeader; private static readonly COMMAND_PREFIX = chatSubcommandLeader; - private static readonly PHRASES = { + private static readonly PHRASES_LOWER = { [VoiceChatService.AGENT_PREFIX]: 'at', [VoiceChatService.COMMAND_PREFIX]: 'slash' }; + private static readonly PHRASES_UPPER = { + [VoiceChatService.AGENT_PREFIX]: 'At', + [VoiceChatService.COMMAND_PREFIX]: 'Slash' + }; + private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); private _phrases: Map | undefined = undefined; @@ -96,12 +101,12 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { - const agentPhrase = `${VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); + const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); phrases.set(agentPhrase, { agent: agent.id }); if (agent.lastSlashCommands) { for (const slashCommand of agent.lastSlashCommands) { - const slashCommandPhrase = `${VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); + const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase(); @@ -141,8 +146,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { case SpeechToTextStatus.Recognizing: case SpeechToTextStatus.Recognized: if (e.text) { - const startsWithAgent = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.AGENT_PREFIX]); - const startsWithSlashCommand = startsWithIgnoreCase(e.text, VoiceChatService.PHRASES[VoiceChatService.COMMAND_PREFIX]); + const startsWithAgent = e.text.startsWith(VoiceChatService.PHRASES_UPPER[VoiceChatService.AGENT_PREFIX]) || e.text.startsWith(VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]); + const startsWithSlashCommand = e.text.startsWith(VoiceChatService.PHRASES_UPPER[VoiceChatService.COMMAND_PREFIX]) || e.text.startsWith(VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]); if (startsWithAgent || startsWithSlashCommand) { const originalWords = e.text.split(' '); let transformedWords: string[] | undefined; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index b986779094a46..d40cdccffc87e 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -147,6 +147,11 @@ suite('VoiceChat', () => { assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'At workspace'); assert.strictEqual(event?.waitingForInput, options.usesAgents); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'at workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, options.usesAgents ? '@workspace' : 'at workspace'); + assert.strictEqual(event?.waitingForInput, options.usesAgents); + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace help' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); assert.strictEqual(event?.text, options.usesAgents ? '@workspace help' : 'At workspace help'); From 01f46bb5357baa8f3b9da690e1e34eb78e09a72b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 10 Feb 2024 18:12:04 +0100 Subject: [PATCH 0147/1863] Go Back and Go Forward don't work sometimes (fix #204359) --- .../workbench/services/history/browser/historyService.ts | 5 +++++ .../services/history/test/browser/historyService.test.ts | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 18062624a78ed..1c73d33421020 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -1692,6 +1692,7 @@ ${entryLabels.join('\n')} } remove(arg1: EditorInput | FileChangesEvent | FileOperationEvent | GroupIdentifier): void { + const previousStackSize = this.stack.length; // Remove all stack entries that match `arg1` this.stack = this.stack.filter(entry => { @@ -1705,6 +1706,10 @@ ${entryLabels.join('\n')} return !matches; }); + if (previousStackSize === this.stack.length) { + return; // nothing removed + } + // Given we just removed entries, we need to make sure // to remove entries that are now identical and next // to each other to prevent no-op navigations. 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 cb861fbc783cb..124a79c02035d 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -427,6 +427,7 @@ suite('HistoryService', function () { const resource: URI = toResource.call(this, '/path/index.txt'); const otherResource: URI = toResource.call(this, '/path/index.html'); + const unrelatedResource: URI = toResource.call(this, '/path/unrelated.html'); const pane = await editorService.openEditor({ resource, options: { pinned: true } }); stack.notifyNavigation(pane); @@ -444,7 +445,14 @@ suite('HistoryService', function () { await editorService.openEditor({ resource: otherResource, options: { pinned: true } }); stack.notifyNavigation(pane); + // Remove unrelated resource does not cause any harm (via internal event) + await stack.goBack(); + assert.strictEqual(stack.canGoForward(), true); + stack.remove(new FileOperationEvent(unrelatedResource, FileOperation.DELETE)); + assert.strictEqual(stack.canGoForward(), true); + // Remove (via internal event) + await stack.goForward(); assert.strictEqual(stack.canGoBack(), true); stack.remove(new FileOperationEvent(resource, FileOperation.DELETE)); assert.strictEqual(stack.canGoBack(), false); From fea743c7cf1dfebdbf1fd095ac8a17a0a465cff5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 11 Feb 2024 21:12:01 -0800 Subject: [PATCH 0148/1863] Handle chat widget resize properly and handle follow up index. --- .../controller/chat/notebookChatController.ts | 49 ++++++++++++------- .../browser/viewParts/notebookViewZones.ts | 2 + 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index a336ad2c6ea8b..307c202e33c8c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -51,44 +51,40 @@ import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const WIDGET_MARGIN_BOTTOM = 16; class NotebookChatWidget extends Disposable implements INotebookViewZone { - private _afterModelPosition: number; - set afterModelPosition(afterModelPosition: number) { - this._afterModelPosition = afterModelPosition; + this.notebookViewZone.afterModelPosition = afterModelPosition; } get afterModelPosition(): number { - return this._afterModelPosition; + return this.notebookViewZone.afterModelPosition; } - private _heightInPx: number; - set heightInPx(heightInPx: number) { - this._heightInPx = heightInPx; + this.notebookViewZone.heightInPx = heightInPx; } get heightInPx(): number { - return this._heightInPx; + return this.notebookViewZone.heightInPx; } private _editingCell: CellViewModel | null = null; + get editingCell() { + return this._editingCell; + } + constructor( private readonly _notebookEditor: INotebookEditor, readonly id: string, + readonly notebookViewZone: INotebookViewZone, readonly domNode: HTMLElement, readonly widgetContainer: HTMLElement, readonly inlineChatWidget: InlineChatWidget, readonly parentEditor: CodeEditorWidget, - afterModelPosition: number, - heightInPx: number, private readonly _languageService: ILanguageService, ) { super(); - this._afterModelPosition = afterModelPosition; - this._heightInPx = heightInPx; - this._register(inlineChatWidget.onDidChangeHeight(() => { this.heightInPx = inlineChatWidget.getHeight() + WIDGET_MARGIN_BOTTOM; this._notebookEditor.changeViewZones(accessor => { @@ -114,7 +110,7 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { return undefined; } - this._editingCell = insertCell(this._languageService, this._notebookEditor, this._afterModelPosition, CellKind.Code, 'above'); + this._editingCell = insertCell(this._languageService, this._notebookEditor, this.afterModelPosition, CellKind.Code, 'above'); if (!this._editingCell) { return undefined; @@ -223,7 +219,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito { isSimpleWidget: true } ); - const inputBoxPath = `/notebook-chat-input0-${NotebookChatController.counter++}`; + const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); @@ -243,21 +239,22 @@ export class NotebookChatController extends Disposable implements INotebookEdito widgetContainer.appendChild(inlineChatWidget.domNode); this._notebookEditor.changeViewZones(accessor => { - const id = accessor.addZone({ + const notebookViewZone = { afterModelPosition: index, heightInPx: 80 + WIDGET_MARGIN_BOTTOM, domNode: viewZoneContainer - }); + }; + + const id = accessor.addZone(notebookViewZone); this._widget = new NotebookChatWidget( this._notebookEditor, id, + notebookViewZone, viewZoneContainer, widgetContainer, inlineChatWidget, fakeParentEditor, - index, - 80 + WIDGET_MARGIN_BOTTOM, this._languageService ); @@ -304,6 +301,20 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } + const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined; + if (editingCellIndex !== undefined) { + this._notebookEditor.setSelections([{ + start: editingCellIndex, + end: editingCellIndex + 1 + }]); + } else { + // Update selection to the widget index + this._notebookEditor.setSelections([{ + start: this._widget.afterModelPosition, + end: this._widget.afterModelPosition + }]); + } + this._ctxHasActiveRequest.set(true); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._widget?.inlineChatWidget.updateProgress(true); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts index a56dd5381559b..bc7384c7654ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -149,6 +149,8 @@ export class NotebookViewZones extends Disposable { return; } + this._updateWhitespace(this._zones[id]); + const isInHiddenArea = this._isInHiddenRanges(zoneWidget.zone); if (isInHiddenArea) { From deadef1f3c0c3117903304c42dae9ce165f98713 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 11 Feb 2024 21:15:00 -0800 Subject: [PATCH 0149/1863] Fix accept execption blocking dismiss --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 307c202e33c8c..a2fde377709a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -498,9 +498,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito try { await this._strategy.apply(editor); + this._inlineChatSessionService.releaseSession(this._activeSession); } catch (_err) { } - this._inlineChatSessionService.releaseSession(this._activeSession); this.dismiss(); } From 983a3bc0aecfc0772dd7b37919d7da36c89a6f2d Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 12 Feb 2024 14:42:48 +0900 Subject: [PATCH 0150/1863] chore: bump electron@27.3.2 (#204960) * chore: bump electron@27.3.2 * chore: bump distro --- .yarnrc | 4 +- build/checksums/electron.txt | 150 +++++++++++++++++------------------ cgmanifest.json | 4 +- package.json | 4 +- yarn.lock | 8 +- 5 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.yarnrc b/.yarnrc index e7aba7e6b7634..19c5cb1eb8f05 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "27.3.1" -ms_build_id "26731440" +target "27.3.2" +ms_build_id "26836302" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 2eaf08f2c005b..a774dffc830a4 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -27fcfc6982b3b1b56cbb611c5060bdbee35fe64aab6c8a24e19ac133d1bbaf05 *chromedriver-v27.3.1-darwin-arm64.zip -56ddf9df850b3612e8aa61204e6334400a89502bb8acabcc9873218317831d0d *chromedriver-v27.3.1-darwin-x64.zip -c070bc178589426112c4ff740712ad3ce5da672f717a93ef74c3d53f23173e08 *chromedriver-v27.3.1-linux-arm64.zip -84f072cca93660bc5eb2f261d71c6db207e7a979ecede92c0f0c5d64cbdfc907 *chromedriver-v27.3.1-linux-armv7l.zip -a722b30273d94d370434604949c4bccb38f3c725e14429d9f6dc9f33455414bf *chromedriver-v27.3.1-linux-x64.zip -8ca0fe4e3daeef42002213c2bf957539c77010e16a5772a0a79e167cf803bb85 *chromedriver-v27.3.1-mas-arm64.zip -52bba4265e0e18f6958de225b9726b262efafebdfee846749f4d83c400046a74 *chromedriver-v27.3.1-mas-x64.zip -b1b9dfd2d5c962b87827b0f4f72ab955cdee14358e9914476b30de603b8834ce *chromedriver-v27.3.1-win32-arm64.zip -a74ae6f90b8d0ae0e29a53a254d25a9ca09eaadeb0bc546fea0aff838345a494 *chromedriver-v27.3.1-win32-ia32.zip -bf37f6947e2e0dc19a99320a39aa23cf644dcf075d7d9ba2394364681aaafb60 *chromedriver-v27.3.1-win32-x64.zip -b25fb8ae02721a89614aeb4db621a797fca385340c8b6be64724785a2e734d23 *electron-api.json -e8394d2c23c878b4bd774f85a1f468547f4c9e4669e14b19850f685ca7ab3c76 *electron-v27.3.1-darwin-arm64-dsym-snapshot.zip -f346aeb98ef1c059b6fc035f837ba997e98d2a2b9e06076ec882df877ae1d6be *electron-v27.3.1-darwin-arm64-dsym.zip -a66a6656acc7217a3f8627a384d9ca5ba3d3cb1b7e144e25bb82344eb211612b *electron-v27.3.1-darwin-arm64-symbols.zip -0c8b1c3bf1fcb52be3c0517603fe69329050a2efb1c61ab5e5988a54aa693ebd *electron-v27.3.1-darwin-arm64.zip -92086b977a81d5e4787bdcff9e4b684807edbf71093cdc54c1d2169b96c0165d *electron-v27.3.1-darwin-x64-dsym-snapshot.zip -57c0d9dd591d2abfa56865323745e411351f639049eefa4506116cbe421baca5 *electron-v27.3.1-darwin-x64-dsym.zip -069c76522f1c4316065932a90067f02b7f6e8f144133c2ba09da8d97a06c4c0f *electron-v27.3.1-darwin-x64-symbols.zip -e2360124f0fdb3ff5e6a644d0e139a9546947ee2f69e51f3d310c3f2cbcd19ee *electron-v27.3.1-darwin-x64.zip -e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-arm64-debug.zip -3304fcfbf8aa45f3a174d404174c34123b8f777259eae7fe26a9d82104068a89 *electron-v27.3.1-linux-arm64-symbols.zip -9337cfe3f9256fecc806554c9fac3be780fd20c868efe621aaca2c1455c5ff64 *electron-v27.3.1-linux-arm64.zip -e6fdce1f521511b13ec4425d8a7e51077e83faa4bbfa18026598037c6688965c *electron-v27.3.1-linux-armv7l-debug.zip -7bb138d548e621b62ece75f43b26c5e287127b720a2fcf1b24fde75178000848 *electron-v27.3.1-linux-armv7l-symbols.zip -4956df2e23ae6bdebda8a1fbbee40828ca1170ce61b8d4504f1e30ed1102052a *electron-v27.3.1-linux-armv7l.zip -b8e7cd591db6ffad32f7dac90e05404a675d4f2a5e1e375dfce7b4a5c3b0064b *electron-v27.3.1-linux-x64-debug.zip -5c692dce572cf1b8ad57a0633280ee61096256b7291701a1c3e357f48479fb7b *electron-v27.3.1-linux-x64-symbols.zip -9daf5c5dc2050b9f37a5ec6d91d586ac156824bfe9a07ca53872c1b293280ca1 *electron-v27.3.1-linux-x64.zip -959529b81b9517df24baed089fb92fd1730b4eefaddcd37c864da6b05f63ecbe *electron-v27.3.1-mas-arm64-dsym-snapshot.zip -603c4955dddd4f8211c6fab3e633242255c1bf408326bf66567bfa00bed21493 *electron-v27.3.1-mas-arm64-dsym.zip -8181e85363bcc89863c0de76b29d47b68beb4f53d79583875a74178f5e12ef1d *electron-v27.3.1-mas-arm64-symbols.zip -c577088087c137e60884d857335a6720547c14eac894884d9daa07f1662a43a5 *electron-v27.3.1-mas-arm64.zip -6ce1f58f7bfa0b1aafd1aea2bfbad83df3a86a7b922b5d57f00689c7dd573f42 *electron-v27.3.1-mas-x64-dsym-snapshot.zip -1efe461ecca71b7efd96a34187b4810969c2d2bece7d02db2ad2821acd44e130 *electron-v27.3.1-mas-x64-dsym.zip -eabfbd4ee150f60b7cf10c126343cc078a3df2b8e63938f5795766fa8d837c93 *electron-v27.3.1-mas-x64-symbols.zip -2e0f6a468d388ca47eba3ae8cd79f5f422d3a36813b46d09fc7a0ae93bcbb68c *electron-v27.3.1-mas-x64.zip -c086cee2cfe73567d35e3b373c39f562e9169dc1f234caa6d006a878ff43b2e4 *electron-v27.3.1-win32-arm64-pdb.zip -c064dd5aa63b1506cca2cc6cb2fda6478839c194f62b3b90782b3dc8246bd55c *electron-v27.3.1-win32-arm64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-arm64-toolchain-profile.zip -09caae1d93087cebcefec8bfc01496213e8db95d4563151d46aec3f05f31ec30 *electron-v27.3.1-win32-arm64.zip -c1dbf42222a5bfd86850fec114436e24960275547cf529e4f5ff2729ce680801 *electron-v27.3.1-win32-ia32-pdb.zip -8d3db6570266cc571823e7b2498e694e428134ecf2a11211dbd8f1b1d9ddfe02 *electron-v27.3.1-win32-ia32-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-ia32-toolchain-profile.zip -686daa2e3cd9751d411b781af88ff84b91b23ddcf4879bfa5f4417e2e45ca4eb *electron-v27.3.1-win32-ia32.zip -7c1ec060a7ca71c3018b18c66607aad3f648257bdc055f4672341abcf275f1d8 *electron-v27.3.1-win32-x64-pdb.zip -7ab64686ed233b2aef2443d72997a36a2eb67cbdc1321a419111d573292956bc *electron-v27.3.1-win32-x64-symbols.zip -c9f31ae6408aa6936b5d683eda601773789185890375cd097e61e924d4fed77a *electron-v27.3.1-win32-x64-toolchain-profile.zip -20d54521540d1d9fa441d1e977555dbec4958bcd76de57f23b4534077361a865 *electron-v27.3.1-win32-x64.zip -482a6452d0d2ccda8afbfc19326fe0d59118491c216938a192bfa1ad7d168493 *electron.d.ts -ac54ec270dacdd7ca2f806b3c75047875535e1722e251ec81b4f3ab9c574470b *ffmpeg-v27.3.1-darwin-arm64.zip -091699a1fe0506f54b2b990c3e85058dae0fffffb818d22b41ffc2496e155fa4 *ffmpeg-v27.3.1-darwin-x64.zip -be517ba93c5b5f36d2e55af148213f4c2fc43177563825601bee6f88dd4f7b05 *ffmpeg-v27.3.1-linux-arm64.zip -926d0da25ffcea3d05a6cbcae15e5d7729d93bc43394ae4439747669d2210e1d *ffmpeg-v27.3.1-linux-armv7l.zip -6f9c0ef52af14828ad547a80b17f8c63cac51a18b8d5769a2f33e4fa6cccfc7e *ffmpeg-v27.3.1-linux-x64.zip -e26c3c7097642012acf1178367c6815cf90ac588864e99206d89230409f67c01 *ffmpeg-v27.3.1-mas-arm64.zip -8dc1775e034c1a2c25a150690c384d3479d26f020a0ed533e1691a01a62ab0a5 *ffmpeg-v27.3.1-mas-x64.zip -94832b30166038225703810fec94b2f854fab7ca3fc7f5531219d2de5b9fa4e2 *ffmpeg-v27.3.1-win32-arm64.zip -7e5d9c0b4db4e1370219a74c4484f9d15d18c0c54501003280eb61f8dc3b84b2 *ffmpeg-v27.3.1-win32-ia32.zip -f45d1f9cf5e5e7bff93406d70b2bee58ae5b3836f1950fb47abb6520d0547aac *ffmpeg-v27.3.1-win32-x64.zip -ea4a6308be24bfc818526f1830f084961b5bd9277bbac16e5753fa4d60aad01e *hunspell_dictionaries.zip -9c05221181a2ae7d6f437ec356878ab44b71a958995f4f104667fef20a8076ef *libcxx-objects-v27.3.1-linux-arm64.zip -bef8d7930d31993196f7b0916eece6fac1d84fe9ed2b0a63fb484778949c1ee3 *libcxx-objects-v27.3.1-linux-armv7l.zip -17b1e3d3d9e5abdcedc3c22ce879becdff316c2e2a89369c6f2657e9051da6a6 *libcxx-objects-v27.3.1-linux-x64.zip -46313e591349d7a3a8390d96560d7799afad1f3774d588488a6ee80756172f7d *libcxx_headers.zip -44e6410776454067a546bf5d2525dd33baacd919481dc43708f327009f8b52bd *libcxxabi_headers.zip -870072bdd1956b404265ecfebcbe1a4fbaa2219fd61dd881c817f4f4f8130684 *mksnapshot-v27.3.1-darwin-arm64.zip -d76067c1f28fa4baebed723cc9f6374bd31d4baf0d6e7e19c36bfff6d214dd91 *mksnapshot-v27.3.1-darwin-x64.zip -e403e621c2af0a468301297c11790b370a77a0ba6f7f3fa4af731f005b6ffb96 *mksnapshot-v27.3.1-linux-arm64-x64.zip -6f7ef9ce138ff5c6d4c7df449ac717613e3001756b765f0e4e7662af1387c754 *mksnapshot-v27.3.1-linux-armv7l-x64.zip -a794b49b20677cd68b8616229180687adec3f46c425cc4c805c4cdb9c4b6ec72 *mksnapshot-v27.3.1-linux-x64.zip -6a61292fd8e3fb86c36c364ea32be2e78b8947e9abf22a8a7749bad40e75a5f5 *mksnapshot-v27.3.1-mas-arm64.zip -d3b1ae83b6244a4ce83e6eab16f8cd121c187ee21a5e37cac4253cfd4e53f6b4 *mksnapshot-v27.3.1-mas-x64.zip -6916414f51fa30cce966e49ea252a73facf24ebac86f068daa2f37930c48d58b *mksnapshot-v27.3.1-win32-arm64-x64.zip -ef37d3c0d9ef36038adef1b81ffbf302c3b819456ffea705c05c3def09710723 *mksnapshot-v27.3.1-win32-ia32.zip -95a222425bb72a8b1a42f37161eb90ff6869268f539387b47b54904df27f2f68 *mksnapshot-v27.3.1-win32-x64.zip +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 diff --git a/cgmanifest.json b/cgmanifest.json index c546a3e27e5d0..2673931fdb62c 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "f6af8959c36eca437f38076c46ab13e910cbfbdb" + "commitHash": "077c4addd5faa3ad1d1c9e598284368394a97fdd" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "27.3.1" + "version": "27.3.2" }, { "component": { diff --git a/package.json b/package.json index 6dc97a406e515..d27c2f3b8fce2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "848d09154925ef27343af358097ed5435450160e", + "distro": "d944d3f8f3d96c19da85e25c45e65317f8d795ac", "author": { "name": "Microsoft Corporation" }, @@ -149,7 +149,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "27.3.1", + "electron": "27.3.2", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/yarn.lock b/yarn.lock index 49eb5f91f6e06..fa86aaa289306 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.1: - version "27.3.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-27.3.1.tgz#b64ae70410b657c911d8c7665605c02695722fd3" - integrity sha512-hDAeaTJvXHu3vCmR6l6/muPGaKRlEp6v471yyqOju9xhLKUaGOAdMN46F0V+SCABZq2nWr9DTwqHsJ0b4hUZLQ== +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== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" From c7335f45ec29c41eaed5e98da5163040815c0fdb Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 09:31:33 +0100 Subject: [PATCH 0151/1863] fixing failing tests --- .../browser/inlineCompletionsModel.ts | 49 ++----------------- .../browser/inlineCompletionsModel.test.ts | 2 - 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index e4d474d8238cb..87fa708018010 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -479,64 +479,23 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); - console.log('positions : ', JSON.stringify(positions)); - // replaced text is not calculated correctly - // only works if primary edit line number - // Need to take all the that is replaced in the primary edit range - const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); - console.log('replacedTextAfterPrimaryCursor : ', JSON.stringify(replacedTextAfterPrimaryCursor)); - console.log('primaryEdit : ', JSON.stringify(primaryEdit)); - - // There is an error below too, the secondary edit text is the text after the cursor to the right of it, that needs to be added - // in the test case we would want to add ') {\n\treturn 0;\n}' because we already have fib( written. - // Before it worked because we would have the cursor at the end of function fib(, now the cursor is on the line below it - // Or at the very least, we should insert 'return 0;\n}' because this is to the right of the cursor at the primary cursor position - // So need to find the primary position within the edit, and find all the text to the right of it. The primary position will not necessarily be on the first - // line of the edit text. - // We suppose that the primaryEdit.range always touches the primaryPosition in some manner - - // could find the offset of primary position, the offset of the primary edit start and find thus the secondary edit text - // const _offsetPrimaryPosition = textModel.getOffsetAt(primaryPosition); - // const _offsetPrimaryEditStart = textModel.getOffsetAt(primaryEdit.range.getStartPosition()); - // console.log('_offsetPrimaryPosition : ', _offsetPrimaryPosition); - // console.log('_offsetPrimaryEditStart : ', _offsetPrimaryEditStart); - - // Find offset in a different way - // Split the lines of the text, place it in the context of the whole text - // Find the position in the text where the initial position would be, exactly as is, find the offset, and take the substring - - const newCol = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const newLine = newCol === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column; - + const newLine = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const newCol = newLine === 0 ? primaryPosition.column - primaryEdit.range.startColumn + 1 : primaryPosition.column; let text = ''; const _splitLines = splitLines(primaryEdit.text); for (let i = newLine; i < _splitLines.length; i++) { if (i === newLine) { - text += _splitLines[i].substring(newCol) + '\n'; + text += _splitLines[i].substring(newCol - 1) + (i === _splitLines.length - 1 ? '' : '\n'); } else { - text += _splitLines[i] + '\n'; + text += _splitLines[i] + (i === _splitLines.length - 1 ? '' : '\n'); } } - console.log('text : ', text); - const secondaryEditText = text; - // primaryEdit.text.substring(primaryPosition.column - primaryEdit.range.startColumn); - // console.log('secondaryEditText : ', JSON.stringify(secondaryEditText)); return secondaryPositions.map(pos => { - console.log('pos : ', JSON.stringify(pos)); - // Maybe taking the substring on the line content specifically is not enough, so we need to actually take it until the range end, because that is the text we would replace - // the range end is not necessarily on the end of that line either - // const textAfterSecondaryCursor = textModel - // .getLineContent(pos.lineNumber) - // .substring(pos.column - 1); - const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); - console.log('textAfterSecondaryCursor : ', textAfterSecondaryCursor); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); - console.log('l : ', l); const range = Range.fromPositions(pos, pos.delta(0, l)); - console.log('range : ', JSON.stringify(range)); return new SingleTextEdit(range, secondaryEditText); }); } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts index c6bc78de063cb..67c56bb3945d5 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsModel.test.ts @@ -57,7 +57,6 @@ suite('inlineCompletionModel', () => { '}' ].join('\n') )]); - console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); @@ -88,7 +87,6 @@ suite('inlineCompletionModel', () => { '}' ].join('\n') )]); - console.log('secondaryEdits : ', JSON.stringify(secondaryEdits)); textModel.dispose(); }); }); From 420099cb092f0994ecb1e7af4c679dc0ffe57fa3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 10:55:48 +0100 Subject: [PATCH 0152/1863] code :lipstick: (#204967) --- .../workbench/browser/parts/compositeBarActions.ts | 10 +++++----- src/vs/workbench/browser/parts/globalCompositeBar.ts | 4 ++-- src/vs/workbench/browser/parts/panel/panelActions.ts | 2 +- .../browser/parts/titlebar/menubarControl.ts | 8 ++++---- .../browser/parts/views/viewPaneContainer.ts | 2 +- .../workbench/common/editor/sideBySideEditorInput.ts | 4 ++-- .../contrib/timeline/browser/timelinePane.ts | 12 ++++++------ .../parts/titlebar/menubarControl.ts | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index e1945a1cbc2a7..fb06c28c67d35 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -144,7 +144,7 @@ export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptio readonly compact?: boolean; } -export class CompoisteBarActionViewItem extends BaseActionViewItem { +export class CompositeBarActionViewItem extends BaseActionViewItem { private static hoverLeaveTime = 0; @@ -394,7 +394,7 @@ export class CompoisteBarActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { if (!this.showHoverScheduler.isScheduled()) { - if (Date.now() - CompoisteBarActionViewItem.hoverLeaveTime < 200) { + if (Date.now() - CompositeBarActionViewItem.hoverLeaveTime < 200) { this.showHover(true); } else { this.showHoverScheduler.schedule(this.configurationService.getValue('workbench.hover.delay')); @@ -404,7 +404,7 @@ export class CompoisteBarActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, e => { if (e.target === this.container) { - CompoisteBarActionViewItem.hoverLeaveTime = Date.now(); + CompositeBarActionViewItem.hoverLeaveTime = Date.now(); this.hoverService.hideHover(); this.showHoverScheduler.cancel(); } @@ -467,7 +467,7 @@ export class CompositeOverflowActivityAction extends CompositeBarAction { } } -export class CompositeOverflowActivityActionViewItem extends CompoisteBarActionViewItem { +export class CompositeOverflowActivityActionViewItem extends CompositeBarActionViewItem { constructor( action: CompositeBarAction, @@ -529,7 +529,7 @@ class ManageExtensionAction extends Action { } } -export class CompositeActionViewItem extends CompoisteBarActionViewItem { +export class CompositeActionViewItem extends CompositeBarActionViewItem { private static manageExtensionAction: ManageExtensionAction; diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 50a63bb9e0021..263ef8d961614 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -12,7 +12,7 @@ import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { CompoisteBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; @@ -153,7 +153,7 @@ export class GlobalCompositeBar extends Disposable { } } -abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionViewItem { +abstract class AbstractGlobalActivityActionViewItem extends CompositeBarActionViewItem { constructor( private readonly menuId: MenuId, diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 23f5ce98a1b70..416cc1d49217e 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -199,7 +199,7 @@ AlignPanelActionConfigs.forEach(alignPanelAction => { constructor() { super({ id, - title: title, + title, category: Categories.View, toggled: when.negate(), f1: true diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index eeec3dbcc5e62..a3d09742994a0 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -665,7 +665,7 @@ export class CustomMenubarControl extends MenubarControl { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(this.toActionsArray(menu), actions, title); - this.menubar?.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } })); @@ -675,7 +675,7 @@ export class CustomMenubarControl extends MenubarControl { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(this.toActionsArray(menu), actions, title); - this.menubar?.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar?.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } })); } @@ -688,9 +688,9 @@ export class CustomMenubarControl extends MenubarControl { if (this.menubar) { if (!firstTime) { - this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar.updateMenu({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } else { - this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); + this.menubar.push({ actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3021fe51eee7a..1c7201e246bf8 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -658,7 +658,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void { const wasMerged = this.isViewMergedWithContainer(); - for (const { pane: pane, size, index, disposable } of panes) { + for (const { pane, size, index, disposable } of panes) { this.addPane(pane, size, disposable, index); } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 88585cb807ca1..7228b47ae7139 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -362,8 +362,8 @@ export abstract class AbstractSideBySideEditorInputSerializer implements IEditor const serializedEditorInput: ISerializedSideBySideEditorInput = { name: input.getPreferredName(), description: input.getPreferredDescription(), - primarySerialized: primarySerialized, - secondarySerialized: secondarySerialized, + primarySerialized, + secondarySerialized, primaryTypeId: input.primary.typeId, secondaryTypeId: input.secondary.typeId }; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index d20b31e06410d..d919f15af47d5 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -747,7 +747,7 @@ export class TimelinePane extends ViewPane { } const iterator = timeline.items[Symbol.iterator](); - sources.push({ timeline: timeline, iterator: iterator, nextItem: iterator.next() }); + sources.push({ timeline, iterator, nextItem: iterator.next() }); } this._visibleItemCount = hasAnyItems ? 1 : 0; @@ -1046,7 +1046,7 @@ export class TimelinePane extends ViewPane { this.tree.domFocus(); } }, - getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item: item }), + getActionsContext: (): TimelineActionContext => ({ uri: this.uri, item }), actionRunner: new TimelineActionRunner() }); } @@ -1068,13 +1068,13 @@ class TimelineElementTemplate implements IDisposable { container.classList.add('custom-view-tree-node-item'); this.icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - this.iconLabel = new IconLabel(container, { supportHighlights: true, supportIcons: true, hoverDelegate: hoverDelegate }); + this.iconLabel = new IconLabel(container, { supportHighlights: true, supportIcons: true, hoverDelegate }); const timestampContainer = DOM.append(this.iconLabel.element, DOM.$('.timeline-timestamp-container')); this.timestamp = DOM.append(timestampContainer, DOM.$('span.timeline-timestamp')); const actionsContainer = DOM.append(this.iconLabel.element, DOM.$('.actions')); - this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: actionViewItemProvider }); + this.actionBar = new ActionBar(actionsContainer, { actionViewItemProvider }); } dispose() { @@ -1109,7 +1109,7 @@ class TimelineActionRunner extends ActionRunner { $mid: MarshalledId.TimelineActionContext, handle: item.handle, source: item.source, - uri: uri + uri }, uri, item.source, @@ -1212,7 +1212,7 @@ class TimelineTreeRenderer implements ITreeRenderer Date: Mon, 12 Feb 2024 11:34:21 +0100 Subject: [PATCH 0153/1863] adding function splitLinesIncludeSeparators --- src/vs/base/common/strings.ts | 8 ++++++ .../browser/inlineCompletionsModel.ts | 28 +++++++++---------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 230e6eb7bdd49..a68f48e76f910 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -254,6 +254,14 @@ export function splitLines(str: string): string[] { return str.split(/\r\n|\r|\n/); } +export function splitLinesIncludeSeparators(str: string): { lines: string[]; separators: string[] } { + const lines: string[] = []; + const separators: string[] = []; + const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/); + splitLinesAndSeparators.forEach((el, idx) => (idx % 2 === 0 ? lines : separators).push(el)); + return { lines, separators }; +} + /** * Returns first index of the string that is not whitespace. * If string is empty or contains only whitespaces, returns -1 diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 87fa708018010..15fd1e0e85662 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -7,7 +7,7 @@ import { mapFindFirst } from 'vs/base/common/arraysFind'; import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; -import { commonPrefixLength, splitLines } from 'vs/base/common/strings'; +import { commonPrefixLength, splitLinesIncludeSeparators } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -479,21 +479,21 @@ function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Positio export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); - const replacedTextAfterPrimaryCursor = textModel.getValueInRange(Range.fromPositions(primaryPosition, primaryEdit.range.getEndPosition())); - const newLine = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const newCol = newLine === 0 ? primaryPosition.column - primaryEdit.range.startColumn + 1 : primaryPosition.column; - let text = ''; - const _splitLines = splitLines(primaryEdit.text); - for (let i = newLine; i < _splitLines.length; i++) { - if (i === newLine) { - text += _splitLines[i].substring(newCol - 1) + (i === _splitLines.length - 1 ? '' : '\n'); - } else { - text += _splitLines[i] + (i === _splitLines.length - 1 ? '' : '\n'); - } + const primaryEditEndPosition = primaryEdit.range.getEndPosition(); + const replacedTextAfterPrimaryCursor = textModel.getValueInRange( + Range.fromPositions(primaryPosition, primaryEditEndPosition) + ); + const lineNumberWithinEditText = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; + const columnWithinEditText = lineNumberWithinEditText === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column - 1; + let secondaryEditText = ''; + const { lines, separators } = splitLinesIncludeSeparators(primaryEdit.text); + for (let i = lineNumberWithinEditText; i < lines.length; i++) { + secondaryEditText += lines[i].substring(i === lineNumberWithinEditText ? columnWithinEditText : 0) + (separators[i] ?? ''); } - const secondaryEditText = text; return secondaryPositions.map(pos => { - const textAfterSecondaryCursor = textModel.getValueInRange(Range.fromPositions(pos, primaryEdit.range.getEndPosition())); + const textAfterSecondaryCursor = textModel.getValueInRange( + Range.fromPositions(pos, primaryEditEndPosition) + ); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); return new SingleTextEdit(range, secondaryEditText); From 3f52e5efd6eee98a355a221044f92b9620d24238 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 12:31:57 +0100 Subject: [PATCH 0154/1863] Multi diff editor: implement `focus` and `hasFocus` (#204076) (#204970) --- .../multiDiffEditorWidget/multiDiffEditorWidget.ts | 3 ++- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 10 ++++++++++ .../browser/scmMultiDiffSourceResolver.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 938edfad4b33d..ef78607a7d592 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -58,7 +59,7 @@ export class MultiDiffEditorWidget extends Disposable { private readonly _activeControl = derived(this, (reader) => this._widgetImpl.read(reader).activeControl.read(reader)); - public getActiveControl(): any | undefined { + public getActiveControl(): DiffEditorWidget | undefined { return this._activeControl.get(); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 263668667598c..a879f7b85c489 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -96,6 +96,16 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Mon, 12 Feb 2024 12:33:15 +0100 Subject: [PATCH 0155/1863] api - refine `ChatAgentRequest` to have the prompt as-is and include metadata about variables (#204980) --- .../api/common/extHostTypeConverters.ts | 13 +++++++++ .../contrib/chat/common/chatAgents.ts | 4 ++- .../contrib/chat/common/chatModel.ts | 7 ++++- .../contrib/chat/common/chatServiceImpl.ts | 28 +++++++++++++++++-- .../vscode.proposed.chatAgents2.d.ts | 23 +++++++++++++-- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index e1e05919528df..0b639e933b5e4 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -52,6 +52,7 @@ import type * as vscode from 'vscode'; import * as types from './extHostTypes'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { basename } from 'vs/base/common/resources'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; export namespace Command { @@ -2609,9 +2610,21 @@ export namespace ChatAgentRequest { export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { return { prompt: request.message, + prompt2: request.variables2.message, variables: ChatVariable.objectTo(request.variables), command: request.command, agentId: request.agentId, + variables2: request.variables2.variables.map(ChatAgentResolvedVariable.to) + }; + } +} + +export namespace ChatAgentResolvedVariable { + export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatAgentResolvedVariable { + return { + name: request.name, + range: [request.range.start, request.range.endExclusive], + values: request.values.map(ChatVariable.to) }; } } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 2da3567a20e68..e5d20e0db45e8 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -12,7 +12,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -79,6 +79,7 @@ export interface IChatAgentMetadata { supportIssueReporting?: boolean; } + export interface IChatAgentRequest { sessionId: string; requestId: string; @@ -86,6 +87,7 @@ export interface IChatAgentRequest { command?: string; message: string; variables: Record; + variables2: IChatRequestVariableData2; } export interface IChatAgentResult { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 68b5b843ee367..81bcad7a26c22 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -12,7 +12,7 @@ import { revive } from 'vs/base/common/marshalling'; import { basename } from 'vs/base/common/resources'; import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -28,6 +28,11 @@ export interface IChatRequestVariableData { variables: Record; } +export interface IChatRequestVariableData2 { + message: string; + variables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[]; +} + export interface IChatRequestModel { readonly id: string; readonly username: string; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 4e1222c1a0cd4..8c363983bb196 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -22,8 +22,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, 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 } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -545,7 +545,8 @@ export class ChatService extends Disposable implements IChatService { agentId: request.response.agent?.id ?? '', message: request.variableData.message, variables: request.variableData.variables, - command: request.response.slashCommand?.name + command: request.response.slashCommand?.name, + variables2: asVariablesData2(request.message, request.variableData) }; history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } @@ -562,6 +563,7 @@ export class ChatService extends Disposable implements IChatService { message: variableData.message, variables: variableData.variables, command: agentSlashCommandPart?.command.name, + variables2: asVariablesData2(parsedRequest, variableData) }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); @@ -763,3 +765,23 @@ export class ChatService extends Disposable implements IChatService { this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } } + +function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChatRequestVariableData): IChatRequestVariableData2 { + + const res: IChatRequestVariableData2 = { + message: getPromptText(parsedRequest.parts), + variables: [] + }; + + for (const part of parsedRequest.parts) { + if (part instanceof ChatRequestVariablePart) { + const values = variableData.variables[part.variableName]; + res.variables.push({ name: part.variableName, range: part.range, values }); + } + } + + // "reverse", high index first so that replacement is simple + res.variables.sort((a, b) => b.range.start - a.range.start); + + return res; +} diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 5c12b9d4fd5f1..403265ad3f29e 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -276,6 +276,12 @@ declare module 'vscode' { dispose(): void; } + export interface ChatAgentResolvedVariable { + name: string; + range: [start: number, end: number]; + values: ChatVariableValue[]; + } + export interface ChatAgentRequest { /** @@ -286,6 +292,16 @@ declare module 'vscode' { */ prompt: string; + /** + * The prompt as entered by the user. + * + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * + * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * are not part of the prompt. + */ + prompt2: string; + /** * The ID of the chat agent to which this request was directed. */ @@ -296,10 +312,13 @@ declare module 'vscode' { */ command?: string; + /** @deprecated */ variables: Record; - // TODO@API argumented prompt, reverse order! - // variables2: { start:number, length:number, values: ChatVariableValue[]}[] + /** + * + */ + variables2: ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { From e37a23a9e4b6dddbb289c43e18134b674203f6d0 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 12:56:23 +0100 Subject: [PATCH 0156/1863] defining now the subtract positions method and using it --- .../browser/inlineCompletionsModel.ts | 33 ++++++++++--------- .../inlineCompletions/browser/utils.ts | 4 +++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 15fd1e0e85662..0773f8224eb7c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -22,7 +22,7 @@ import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostT import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource'; import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/singleTextEdit'; import { SuggestItemInfo } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; -import { Permutation, addPositions, getNewRanges, lengthOfText } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +import { Permutation, addPositions, getNewRanges, lengthOfText, subtractPositions } from 'vs/editor/contrib/inlineCompletions/browser/utils'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -469,13 +469,6 @@ export class InlineCompletionsModel extends Disposable { } } -function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { - const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); - const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); - const newRanges = sortPerm.inverse().apply(sortedNewRanges); - return newRanges.map(range => range.getEndPosition()); -} - export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); @@ -483,13 +476,8 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos const replacedTextAfterPrimaryCursor = textModel.getValueInRange( Range.fromPositions(primaryPosition, primaryEditEndPosition) ); - const lineNumberWithinEditText = primaryPosition.lineNumber - primaryEdit.range.startLineNumber; - const columnWithinEditText = lineNumberWithinEditText === 0 ? primaryPosition.column - primaryEdit.range.startColumn : primaryPosition.column - 1; - let secondaryEditText = ''; - const { lines, separators } = splitLinesIncludeSeparators(primaryEdit.text); - for (let i = lineNumberWithinEditText; i < lines.length; i++) { - secondaryEditText += lines[i].substring(i === lineNumberWithinEditText ? columnWithinEditText : 0) + (separators[i] ?? ''); - } + const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); + const secondaryEditText = getTextFromPosition(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const textAfterSecondaryCursor = textModel.getValueInRange( Range.fromPositions(pos, primaryEditEndPosition) @@ -500,3 +488,18 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos }); } +function getTextFromPosition(text: string, pos: Position): string { + let subtext = ''; + const { lines, separators } = splitLinesIncludeSeparators(text); + for (let i = pos.lineNumber - 1; i < lines.length; i++) { + subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0) + (separators[i] ?? ''); + } + return subtext; +} + +function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] { + const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range)); + const sortedNewRanges = getNewRanges(sortPerm.apply(edits)); + const newRanges = sortPerm.inverse().apply(sortedNewRanges); + return newRanges.map(range => range.getEndPosition()); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts index 805de9a781a07..4a9e78238b60b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/utils.ts @@ -96,6 +96,10 @@ export function addPositions(pos1: Position, pos2: Position): Position { return new Position(pos1.lineNumber + pos2.lineNumber - 1, pos2.lineNumber === 1 ? pos1.column + pos2.column - 1 : pos2.column); } +export function subtractPositions(pos1: Position, pos2: Position): Position { + return new Position(pos1.lineNumber - pos2.lineNumber + 1, pos1.lineNumber - pos2.lineNumber === 0 ? pos1.column - pos2.column + 1 : pos1.column); +} + export function lengthOfText(text: string): Position { let line = 1; let column = 1; From 55b3ca6757904e56147ed8acbdd1a162fe310117 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 13:09:59 +0100 Subject: [PATCH 0157/1863] adding check that the marker controller is defined before ppending an action --- .../hover/browser/markerHoverParticipant.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts index d56720dcebd68..ffdc5ccf50fb0 100644 --- a/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts @@ -170,15 +170,18 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant { - context.hide(); - MarkerController.get(this._editor)?.showAtMarker(markerHover.marker); - this._editor.focus(); - } - }); + const markerController = MarkerController.get(this._editor); + if (markerController) { + context.statusBar.addAction({ + label: nls.localize('view problem', "View Problem"), + commandId: NextMarkerAction.ID, + run: () => { + context.hide(); + markerController.showAtMarker(markerHover.marker); + this._editor.focus(); + } + }); + } } if (!this._editor.getOption(EditorOption.readOnly)) { From 488005693067ba8d6ac73d32bac3892fe4a445c9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 14:53:36 +0100 Subject: [PATCH 0158/1863] aux window - reduce $window usage (#204991) --- src/vs/platform/sign/browser/signService.ts | 4 +- .../services/notebookKernelServiceImpl.ts | 5 +- .../browser/view/renderers/webviewPreloads.ts | 118 +++++++++--------- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index dadf7a0f418e4..a9d699bf3b297 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { WindowIntervalTimer } from 'vs/base/browser/dom'; -import { $window } from 'vs/base/browser/window'; +import { mainWindow } from 'vs/base/browser/window'; import { memoize } from 'vs/base/common/decorators'; import { FileAccess } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -70,7 +70,7 @@ export class SignService extends AbstractSignService implements ISignService { if (typeof vsda_web !== 'undefined') { resolve(); } - }, 50, $window); + }, 50, mainWindow); }).finally(() => checkInterval.dispose()), ]); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts index d86b1b7f928e6..572a833e173d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts @@ -16,8 +16,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction } from 'vs/base/common/actions'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { Schemas } from 'vs/base/common/network'; -import { $window } from 'vs/base/browser/window'; -import { runWhenWindowIdle } from 'vs/base/browser/dom'; +import { getActiveWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; class KernelInfo { @@ -171,7 +170,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private _persistMementos(): void { this._persistSoonHandle?.dispose(); - this._persistSoonHandle = runWhenWindowIdle($window, () => { + this._persistSoonHandle = runWhenWindowIdle(getActiveWindow(), () => { this._storageService.store(NotebookKernelService._storageNotebookBinding, JSON.stringify(this._notebookBindings), StorageScope.WORKSPACE, StorageTarget.MACHINE); }, 100); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index ebbf9d2703209..c08eb70c296af 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as DOM from 'vs/base/browser/window'; import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; @@ -89,8 +88,13 @@ declare function cancelIdleCallback(handle: number): void; declare function __import(path: string): Promise; async function webviewPreloads(ctx: PreloadContext) { - // eslint-disable-next-line no-restricted-globals - const $window = window as typeof DOM.$window; + + /* eslint-disable no-restricted-globals */ + + // The use of global `window` should be fine in this context, even + // with aux windows. This code is running from within an `iframe` + // where there is only one `window` object anyway. + const userAgent = navigator.userAgent; const isChrome = (userAgent.indexOf('Chrome') >= 0); const textEncoder = new TextEncoder(); @@ -159,7 +163,7 @@ async function webviewPreloads(ctx: PreloadContext) { // check if an input element is focused within the output element const checkOutputInputFocus = () => { - const activeElement = $window.document.activeElement; + const activeElement = window.document.activeElement; if (!activeElement) { return; } @@ -271,8 +275,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - $window.document.body.addEventListener('click', handleInnerClick); - $window.document.body.addEventListener('focusin', checkOutputInputFocus); + window.document.body.addEventListener('click', handleInnerClick); + window.document.body.addEventListener('focusin', checkOutputInputFocus); interface RendererContext extends rendererApi.RendererContext { readonly onDidChangeSettings: Event; @@ -373,7 +377,7 @@ async function webviewPreloads(ctx: PreloadContext) { constructor() { this._observer = new ResizeObserver(entries => { for (const entry of entries) { - if (!$window.document.body.contains(entry.target)) { + if (!window.document.body.contains(entry.target)) { continue; } @@ -405,7 +409,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (shouldUpdatePadding) { // Do not update dimension in resize observer - $window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { if (newHeight !== 0) { entry.target.style.padding = `${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodeLeftPadding}px`; } else { @@ -473,7 +477,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // if the node is not scrollable, we can continue. We don't check the computed style always as it's expensive - if ($window.getComputedStyle(node).overflowY === 'hidden' || $window.getComputedStyle(node).overflowY === 'visible') { + if (window.getComputedStyle(node).overflowY === 'hidden' || window.getComputedStyle(node).overflowY === 'visible') { continue; } @@ -495,9 +499,9 @@ async function webviewPreloads(ctx: PreloadContext) { deltaY: event.deltaY, deltaZ: event.deltaZ, // Refs https://github.com/microsoft/vscode/issues/146403#issuecomment-1854538928 - wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / $window.devicePixelRatio) : event.wheelDelta, - wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / $window.devicePixelRatio) : event.wheelDeltaX, - wheelDeltaY: event.wheelDeltaY && isChrome ? (event.wheelDeltaY / $window.devicePixelRatio) : event.wheelDeltaY, + wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / window.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / window.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: event.wheelDeltaY && isChrome ? (event.wheelDeltaY / window.devicePixelRatio) : event.wheelDeltaY, detail: event.detail, shiftKey: event.shiftKey, type: event.type @@ -506,10 +510,10 @@ async function webviewPreloads(ctx: PreloadContext) { }; function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) { - const cellOutputContainer = $window.document.getElementById(cellOrOutputId) ?? - (alternateId ? $window.document.getElementById(alternateId) : undefined); + const cellOutputContainer = window.document.getElementById(cellOrOutputId) ?? + (alternateId ? window.document.getElementById(alternateId) : undefined); if (cellOutputContainer) { - if (cellOutputContainer.contains($window.document.activeElement)) { + if (cellOutputContainer.contains(window.document.activeElement)) { return; } @@ -688,7 +692,7 @@ async function webviewPreloads(ctx: PreloadContext) { } function selectRange(_range: ICommonRange) { - const sel = $window.getSelection(); + const sel = window.getSelection(); if (sel) { try { sel.removeAllRanges(); @@ -721,8 +725,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; } else { - $window.document.execCommand('hiliteColor', false, matchColor); - const cloneRange = $window.getSelection()!.getRangeAt(0).cloneRange(); + window.document.execCommand('hiliteColor', false, matchColor); + const cloneRange = window.getSelection()!.getRangeAt(0).cloneRange(); const _range = { collapsed: cloneRange.collapsed, commonAncestorContainer: cloneRange.commonAncestorContainer, @@ -737,9 +741,9 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - $window.document.execCommand('removeFormat', false, undefined); + window.document.execCommand('removeFormat', false, undefined); document.designMode = 'Off'; - $window.getSelection()?.removeAllRanges(); + window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -748,10 +752,10 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - $window.document.execCommand('removeFormat', false, undefined); - $window.document.execCommand('hiliteColor', false, color); + window.document.execCommand('removeFormat', false, undefined); + window.document.execCommand('hiliteColor', false, color); document.designMode = 'Off'; - $window.getSelection()?.removeAllRanges(); + window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -922,12 +926,12 @@ async function webviewPreloads(ctx: PreloadContext) { const onDidReceiveKernelMessage = createEmitter(); - const ttPolicy = $window.trustedTypes?.createPolicy('notebookRenderer', { + const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { createHTML: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. createScript: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. }); - $window.addEventListener('wheel', handleWheel); + window.addEventListener('wheel', handleWheel); interface IFindMatch { type: 'preview' | 'output'; @@ -961,8 +965,8 @@ async function webviewPreloads(ctx: PreloadContext) { currentMatchIndex: number; } - const matchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).color; - const currentMatchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).backgroundColor; + const matchColor = window.getComputedStyle(window.document.getElementById('_defaultColorPalatte')!).color; + const currentMatchColor = window.getComputedStyle(window.document.getElementById('_defaultColorPalatte')!).backgroundColor; class JSHighlighter implements IHighlighter { private _activeHighlightInfo: Map; @@ -1008,11 +1012,11 @@ async function webviewPreloads(ctx: PreloadContext) { const match = highlightInfo.matches[index]; highlightInfo.currentMatchIndex = index; - const sel = $window.getSelection(); + const sel = window.getSelection(); if (!!match && !!sel && match.highlightResult) { let offset = 0; try { - const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = window.document.getElementById(match.id)!.getBoundingClientRect().top; const tempRange = document.createRange(); tempRange.selectNode(match.highlightResult.range.startContainer); @@ -1028,7 +1032,7 @@ async function webviewPreloads(ctx: PreloadContext) { match.highlightResult?.update(currentMatchColor, match.isShadow ? undefined : 'current-find-match'); - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); postNotebookMessage('didFindHighlightCurrent', { offset }); @@ -1047,7 +1051,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose() { - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); this._activeHighlightInfo.forEach(highlightInfo => { highlightInfo.matches.forEach(match => { match.highlightResult?.dispose(); @@ -1122,7 +1126,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (match) { let offset = 0; try { - const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = window.document.getElementById(match.id)!.getBoundingClientRect().top; match.originalRange.startContainer.parentElement?.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' }); const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; @@ -1151,7 +1155,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose(): void { - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); this._currentMatchesHighlight.clear(); this._matchesHighlight.clear(); } @@ -1252,8 +1256,8 @@ async function webviewPreloads(ctx: PreloadContext) { const matches: IFindMatch[] = []; const range = document.createRange(); - range.selectNodeContents($window.document.getElementById('findStart')!); - const sel = $window.getSelection(); + range.selectNodeContents(window.document.getElementById('findStart')!); + const sel = window.getSelection(); sel?.removeAllRanges(); sel?.addRange(range); @@ -1263,7 +1267,7 @@ async function webviewPreloads(ctx: PreloadContext) { document.designMode = 'On'; while (find && matches.length < 500) { - find = ($window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, + find = (window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, /* backwards*/ false, /* wrapAround*/ false, /* wholeWord */ !!options.wholeWord, @@ -1271,7 +1275,7 @@ async function webviewPreloads(ctx: PreloadContext) { false); if (find) { - const selection = $window.getSelection(); + const selection = window.getSelection(); if (!selection) { console.log('no selection'); break; @@ -1360,7 +1364,7 @@ async function webviewPreloads(ctx: PreloadContext) { break; } - if (node.id === 'container' || node === $window.document.body) { + if (node.id === 'container' || node === window.document.body) { break; } } @@ -1376,7 +1380,7 @@ async function webviewPreloads(ctx: PreloadContext) { } _highlighter.addHighlights(matches, options.ownerID); - $window.document.getSelection()?.removeAllRanges(); + window.document.getSelection()?.removeAllRanges(); viewModel.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); @@ -1394,7 +1398,7 @@ async function webviewPreloads(ctx: PreloadContext) { }; const copyOutputImage = async (outputId: string, altOutputId: string, retries = 5) => { - if (!$window.document.hasFocus() && retries > 0) { + if (!window.document.hasFocus() && retries > 0) { // copyImage can be called from outside of the webview, which means this function may be running whilst the webview is gaining focus. // Since navigator.clipboard.write requires the document to be focused, we need to wait for focus. // We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it. @@ -1403,8 +1407,8 @@ async function webviewPreloads(ctx: PreloadContext) { } try { - const outputElement = $window.document.getElementById(outputId) - ?? $window.document.getElementById(altOutputId); + const outputElement = window.document.getElementById(outputId) + ?? window.document.getElementById(altOutputId); let image = outputElement?.querySelector('img'); @@ -1446,7 +1450,7 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - $window.addEventListener('message', async rawEvent => { + window.addEventListener('message', async rawEvent => { const event = rawEvent as ({ data: webviewMessages.ToWebviewMessage }); switch (event.data.type) { @@ -1520,7 +1524,7 @@ async function webviewPreloads(ctx: PreloadContext) { case 'clear': renderers.clearAll(); viewModel.clearAll(); - $window.document.getElementById('container')!.innerText = ''; + window.document.getElementById('container')!.innerText = ''; break; case 'clearOutput': { @@ -1572,10 +1576,10 @@ async function webviewPreloads(ctx: PreloadContext) { focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId, event.data.alternateId); break; case 'decorations': { - let outputContainer = $window.document.getElementById(event.data.cellId); + let outputContainer = window.document.getElementById(event.data.cellId); if (!outputContainer) { viewModel.ensureOutputCell(event.data.cellId, -100000, true); - outputContainer = $window.document.getElementById(event.data.cellId); + outputContainer = window.document.getElementById(event.data.cellId); } outputContainer?.classList.add(...event.data.addedClassNames); outputContainer?.classList.remove(...event.data.removedClassNames); @@ -1588,7 +1592,7 @@ async function webviewPreloads(ctx: PreloadContext) { renderers.getRenderer(event.data.rendererId)?.receiveMessage(event.data.message); break; case 'notebookStyles': { - const documentStyle = $window.document.documentElement.style; + const documentStyle = window.document.documentElement.style; for (let i = documentStyle.length - 1; i >= 0; i--) { const property = documentStyle[i]; @@ -1969,7 +1973,7 @@ async function webviewPreloads(ctx: PreloadContext) { public async render(item: ExtendedOutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise { const primaryRenderer = this.findRenderer(preferredRendererId, item); if (!primaryRenderer) { - const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); + const errorMessage = (window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); return; } @@ -2001,7 +2005,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // All renderers have failed and there is nothing left to fallback to - const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); + const errorMessage = (window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); } @@ -2318,7 +2322,7 @@ async function webviewPreloads(ctx: PreloadContext) { }] }); - const root = $window.document.getElementById('container')!; + const root = window.document.getElementById('container')!; const markupCell = document.createElement('div'); markupCell.className = 'markup'; markupCell.style.position = 'absolute'; @@ -2486,7 +2490,7 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly outputElements = new Map(); constructor(cellId: string) { - const container = $window.document.getElementById('container')!; + const container = window.document.getElementById('container')!; const upperWrapperElement = createFocusSink(cellId); container.appendChild(upperWrapperElement); @@ -2779,12 +2783,12 @@ async function webviewPreloads(ctx: PreloadContext) { private dragOverlay?: HTMLElement; constructor() { - $window.document.addEventListener('dragover', e => { + window.document.addEventListener('dragover', e => { // Allow dropping dragged markup cells e.preventDefault(); }); - $window.document.addEventListener('drop', e => { + window.document.addEventListener('drop', e => { e.preventDefault(); const drag = this.currentDrag; @@ -2823,7 +2827,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.dragOverlay.style.width = '100%'; this.dragOverlay.style.height = '100%'; this.dragOverlay.style.background = 'transparent'; - $window.document.body.appendChild(this.dragOverlay); + window.document.body.appendChild(this.dragOverlay); } (e.target as HTMLElement).style.zIndex = `${overlayZIndex + 1}`; (e.target as HTMLElement).classList.add('dragging'); @@ -2844,9 +2848,9 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, dragOffsetY: this.currentDrag.clientY, }); - $window.requestAnimationFrame(trySendDragUpdate); + window.requestAnimationFrame(trySendDragUpdate); }; - $window.requestAnimationFrame(trySendDragUpdate); + window.requestAnimationFrame(trySendDragUpdate); } updateDrag(e: DragEvent, cellId: string) { @@ -2865,7 +2869,7 @@ async function webviewPreloads(ctx: PreloadContext) { }); if (this.dragOverlay) { - $window.document.body.removeChild(this.dragOverlay); + window.document.body.removeChild(this.dragOverlay); this.dragOverlay = undefined; } From b31cef9e2c98143aad50d8ccbf7ec203cffa925c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 14:55:26 +0100 Subject: [PATCH 0159/1863] placing separators with the lines themselves --- src/vs/base/common/strings.ts | 11 ++++++----- .../browser/inlineCompletionsModel.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index a68f48e76f910..6ec11f0391900 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -254,12 +254,13 @@ export function splitLines(str: string): string[] { return str.split(/\r\n|\r|\n/); } -export function splitLinesIncludeSeparators(str: string): { lines: string[]; separators: string[] } { - const lines: string[] = []; - const separators: string[] = []; +export function splitLinesIncludeSeparators(str: string): string[] { + const linesWithSeparators: string[] = []; const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/); - splitLinesAndSeparators.forEach((el, idx) => (idx % 2 === 0 ? lines : separators).push(el)); - return { lines, separators }; + for (let i = 0; i < Math.ceil(splitLinesAndSeparators.length / 2); i++) { + linesWithSeparators.push(splitLinesAndSeparators[2 * i] + (splitLinesAndSeparators[2 * i + 1] ?? '')); + } + return linesWithSeparators; } /** diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0773f8224eb7c..e385a0d3c6595 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -490,9 +490,9 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos function getTextFromPosition(text: string, pos: Position): string { let subtext = ''; - const { lines, separators } = splitLinesIncludeSeparators(text); + const lines = splitLinesIncludeSeparators(text); for (let i = pos.lineNumber - 1; i < lines.length; i++) { - subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0) + (separators[i] ?? ''); + subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0); } return subtext; } From 4eb07edec2fde1a1de1886086a947e737ff3d8e4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:20:58 +0100 Subject: [PATCH 0160/1863] api - change history into turns (#204992) https://github.com/microsoft/vscode/issues/199908 --- .../workbench/api/common/extHost.api.impl.ts | 2 + .../api/common/extHostChatAgents2.ts | 24 ++++++- .../api/common/extHostTypeConverters.ts | 4 +- src/vs/workbench/api/common/extHostTypes.ts | 17 +++++ .../vscode.proposed.chatAgents2.d.ts | 72 ++++++++++++++----- 5 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a820c66779643..071e935c05128 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1663,6 +1663,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, + ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, + ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, }; }; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 558061ddbe86d..65a95d5374e58 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -198,9 +198,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { const convertedHistory = await this.prepareHistory(request, context); + const convertedHistory2 = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory }, + { history: convertedHistory, history2: convertedHistory2 }, stream.apiObject, token ); @@ -243,6 +244,27 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { }))); } + private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { + + const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; + + for (const h of context.history) { + const ehResult = typeConvert.ChatAgentResult.to(h.result); + const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + ehResult : + { ...ehResult, metadata: undefined }; + + // REQUEST turn + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.agentId, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to))); + + // RESPONSE turn + const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, h.request.agentId)); + } + + return res; + } + $releaseSession(sessionId: string): void { this._sessionDisposables.deleteAndDispose(sessionId); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 0b639e933b5e4..588ab94a8de1a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2479,7 +2479,7 @@ export namespace ChatResponsePart { } - export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart { + export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart | undefined { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); @@ -2488,7 +2488,7 @@ export namespace ChatResponsePart { case 'treeData': return ChatResponseFilesPart.from(part); case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } - return new types.ChatResponseTextPart(''); + return undefined; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9a392ffc2e32f..492cf921558a2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,6 +4257,23 @@ export class ChatResponseReferencePart { } +export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { + constructor( + readonly prompt: string, + readonly agentId: string, + readonly command: string | undefined, + readonly variables: vscode.ChatAgentResolvedVariable[], + ) { } +} + +export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { + constructor( + readonly response: ReadonlyArray, + readonly result: vscode.ChatAgentResult2, + readonly agentId: string, + ) { } +} + //#endregion //#region ai diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 403265ad3f29e..9f0f46769f159 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -12,13 +12,12 @@ declare module 'vscode' { /** * The request that was sent to the chat agent. */ - // TODO@API make this optional? Allow for response without request? request: ChatAgentRequest; /** * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. */ - response: (ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart)[]; + response: ReadonlyArray; /** * The result that was received from the chat agent. @@ -26,31 +25,69 @@ declare module 'vscode' { result: ChatAgentResult2; } - // TODO@API class - // export interface ChatAgentResponse { - // /** - // * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. - // */ - // response: (ChatAgentContentProgress | ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart)[]; + // TODO@API name: Turn? + export class ChatAgentRequestTurn { - // agentId: string + /** + * The prompt as entered by the user. + * + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * + * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * are not part of the prompt. + */ + readonly prompt: string; + + /** + * The ID of the chat agent to which this request was directed. + */ + readonly agentId: string; + + /** + * The name of the {@link ChatAgentCommand command} that was selected for this request. + */ + readonly command: string | undefined; + + /** + * + */ + // TODO@API is this needed? + readonly variables: ChatAgentResolvedVariable[]; + + private constructor(prompt: string, agentId: string, command: string | undefined, variables: ChatAgentResolvedVariable[],); + } - // /** - // * The result that was received from the chat agent. - // */ - // result: ChatAgentResult2; - // } + // TODO@API name: Turn? + export class ChatAgentResponseTurn { + + /** + * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + */ + readonly response: ReadonlyArray; + + /** + * The result that was received from the chat agent. + */ + readonly result: ChatAgentResult2; + + readonly agentId: string; + + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: string); + } export interface ChatAgentContext { /** - * All of the chat messages so far in the current chat session. + * @deprecated */ history: ChatAgentHistoryEntry[]; // location: - // TODO@API have "turns" - // history2: (ChatAgentRequest | ChatAgentResponse)[]; + /** + * All of the chat messages so far in the current chat session. + */ + // TODO@API name: histroy + readonly history2: ReadonlyArray; } /** @@ -479,6 +516,7 @@ declare module 'vscode' { * @param description A description of the variable for the chat input suggest widget. * @param resolver Will be called to provide the chat variable's value when it is used. */ + // TODO@API NAME: registerChatVariable, registerChatVariableResolver export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } From b2d46084e9900bc9c4818e5824288ebdc4bc4a23 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:32:14 +0100 Subject: [PATCH 0161/1863] api - remove `vscode.ChatMessage#name` (#204993) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostTypeConverters.ts | 6 +----- src/vs/workbench/contrib/chat/common/chatProvider.ts | 1 - src/vscode-dts/vscode.proposed.chat.d.ts | 3 --- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 588ab94a8de1a..fda37eef309f3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2228,17 +2228,13 @@ export namespace ChatInlineFollowup { export namespace ChatMessage { export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { - const res = new types.ChatMessage(ChatMessageRole.to(message.role), message.content); - res.name = message.name; - return res; + return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); } - export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { return { role: ChatMessageRole.from(message.role), content: message.content, - name: message.name }; } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index 38be72eb51e49..c4655e3566564 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -19,7 +19,6 @@ export const enum ChatMessageRole { export interface IChatMessage { readonly role: ChatMessageRole; readonly content: string; - readonly name?: string; } export interface IChatResponseFragment { diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts index 1e27e2e5ab9a1..5dc2704b1767d 100644 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ b/src/vscode-dts/vscode.proposed.chat.d.ts @@ -18,9 +18,6 @@ declare module 'vscode' { role: ChatMessageRole; content: string; - // TODO@API is this a leftover from Role.Function? Should message just support a catch-all signature? - name?: string; - constructor(role: ChatMessageRole, content: string); } From 5cfd0d5a41d47fb72a3625800407c9b24673e94e Mon Sep 17 00:00:00 2001 From: Xavier Decoster Date: Mon, 12 Feb 2024 15:46:32 +0100 Subject: [PATCH 0162/1863] Update comment in extensionGalleryService.ts See GH issue #205003 --- .../extensionManagement/common/extensionGalleryService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6afa575b1a0fd..07195962b1c22 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -167,9 +167,8 @@ enum Flags { IncludeLatestVersionOnly = 0x200, /** - * This flag switches the asset uri to use GetAssetByName instead of CDN - * When this is used, values of base asset uri and base asset uri fallback are switched - * When this is used, source of asset files are pointed to Gallery service always even if CDN is available + * The Unpublished extension flag indicates that the extension can't be installed/downloaded. + * Users who have installed such an extension can continue to use the extension. */ Unpublished = 0x1000, From 23704c7fa53d5fd08d163de682a1423db89fc0a1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 15:51:28 +0100 Subject: [PATCH 0163/1863] using instead scrollTo method --- .../multiDiffEditorWidget.ts | 14 ++------ .../multiDiffEditorWidgetImpl.ts | 21 ++++-------- .../bulkEdit/browser/preview/bulkEditPane.ts | 33 ++++--------------- .../browser/multiDiffEditor.ts | 14 ++------ 4 files changed, 19 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 349c26b5ee389..9a19e5e5669ea 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,16 +44,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public setScrollState(scrollState: { top?: number; left?: number }): void { - this._widgetImpl.get().setScrollState(scrollState); - } - - public getTopOfElement(index: number): number { - return this._widgetImpl.get().getTopOfElement(index); - } - - public viewItems(): readonly VirtualizedViewItem[] { - return this._widgetImpl.get().viewItems(); + public scrollTo(uri: URI): void { + this._widgetImpl.get().scrollTo(uri); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 54818d55c3597..5619b08b2cd5f 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -186,19 +186,14 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public getTopOfElement(index: number): number { - console.log('index : ', index); + public scrollTo(originalUri: URI): void { const viewItems = this._viewItems.get(); - console.log('viewItems : ', viewItems); - let top = 0; - for (let i = 0; i < index - 1; i++) { - top += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + const index = viewItems?.findIndex(item => item.viewModel.originalUri?.toString() === originalUri.toString()); + let scrollTop = 0; + for (let i = 0; i < index; i++) { + scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } - return top; - } - - public viewItems(): readonly VirtualizedViewItem[] { - return this._viewItems.get(); + this._scrollableElement.setScrollPosition({ scrollTop }); } public getViewState(): IMultiDiffEditorViewState { @@ -292,10 +287,9 @@ export interface IMultiDiffEditorViewState { interface IMultiDiffDocState { collapsed: boolean; selections?: ISelection[]; - uri?: URI; } -export class VirtualizedViewItem extends Disposable { +class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); public readonly contentHeight = derived(this, reader => @@ -352,7 +346,6 @@ export class VirtualizedViewItem extends Disposable { return { collapsed: this.viewModel.collapsed.get(), selections: this.viewModel.lastTemplateData.get().selections, - uri: this.viewModel.originalUri }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 7a8faba7ea4b4..b072acb88e073 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -318,10 +318,6 @@ export class BulkEditPane extends ViewPane { } } - // Issues with current implementation - // 3. Currently I am accessing the parent of the file element and showing all of the files, but we want to jump to the correct location when clicking on the multi diff editor - // Maybe we should somehow return the file operations directly instead of iterating upwards, the way that we currently do - // 4. Should jump instead to the part of the multi diff editor of interest, so no need to show it again, and can just scroll private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { const options: Mutable = { ...e.editorOptions }; @@ -352,16 +348,7 @@ export class BulkEditPane extends ViewPane { if (this._fileOperations === fileOperations) { // Multi diff editor already open - const viewItems = this._multiDiffEditor?.viewItems(); - if (viewItems) { - const item = viewItems?.find(item => item.viewModel.originalUri?.toString() === fileElement.edit.uri.toString()); - if (item) { - const index = viewItems.indexOf(item); - const top = this._multiDiffEditor?.getTopOfElement(index); - console.log('top : ', top); - this._multiDiffEditor?.setScrollState({ top }); - } - } + this._multiDiffEditor?.scrollTo(fileElement.edit.uri); return; } this._fileOperations = fileOperations; @@ -381,9 +368,6 @@ export class BulkEditPane extends ViewPane { originalUri: leftResource, modifiedUri: rightResource }); - - console.log('leftResource : ', JSON.stringify(leftResource)); - console.log('rightResource : ', JSON.stringify(rightResource)); } let typeLabel: string | undefined; @@ -403,21 +387,16 @@ export class BulkEditPane extends ViewPane { const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); const description = 'Refactor Preview'; - console.log('options : ', JSON.stringify(options)); - console.log('fileOperations : ', fileOperations); - console.log('fileElement.edit ; ', fileElement.edit); - console.log('multiDiffSource : ', multiDiffSource); - console.log('resources : ', resources); - console.log('label : ', label); - console.log('description : ', description); - - this._multiDiffEditor = await this._editorService.openEditor({ + console.log('before open editor'); + this._multiDiffEditor = this._disposables.add(await this._editorService.openEditor({ multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), label: label, description: description, options: options, - }) as MultiDiffEditor; + }) as MultiDiffEditor); + + } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 9361ee7e0efbf..21b1d64a6fac1 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, VirtualizedViewItem } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,16 +72,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From 7932adc68cff4093d79068890beda2aec0f33501 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 12 Feb 2024 15:56:02 +0100 Subject: [PATCH 0164/1863] api - remove old ChatAgentFileTree and ChatAgentFileTreeData (#205002) --- .../workbench/api/common/extHostTypeConverters.ts | 4 ---- .../vscode.proposed.chatAgents2Additions.d.ts | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index fda37eef309f3..f41b939a927aa 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2536,8 +2536,6 @@ export namespace ChatResponseProgress { } else if ('agentName' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' }; - } else if ('treeData' in progress) { - return { treeData: progress.treeData, kind: 'treeData' }; } else if ('message' in progress) { return { content: MarkdownString.from(progress.message), kind: 'progressMessage' }; } else { @@ -2588,8 +2586,6 @@ export namespace ChatResponseProgress { Location.to(progress.inlineReference), title: progress.name }; - case 'treeData': - return { treeData: revive(progress.treeData) }; case 'command': // If the command isn't in the converter, then this session may have been restored, and the command args don't exist anymore return { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 22bf5f7dbd89a..712d31b296b59 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -45,7 +45,6 @@ declare module 'vscode' { */ export type ChatAgentContentProgress = | ChatAgentContent - | ChatAgentFileTree | ChatAgentInlineContentReference | ChatAgentCommandButton; @@ -108,20 +107,6 @@ declare module 'vscode' { content: string; } - /** @deprecated */ - export interface ChatAgentFileTree { - treeData: ChatAgentFileTreeData; - } - - /** @deprecated */ - export interface ChatAgentFileTreeData { - label: string; - uri: Uri; - type?: FileType; - children?: ChatAgentFileTreeData[]; - } - - export interface ChatAgentDocumentContext { uri: Uri; version: number; From b3fc17d24a46428d665a03e34a0d95294dc27ddc Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 12 Feb 2024 15:34:40 +0100 Subject: [PATCH 0165/1863] update vscode-distro hash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d27c2f3b8fce2..488e3cd2c58ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "d944d3f8f3d96c19da85e25c45e65317f8d795ac", + "distro": "cee7e51a2bc638aa4fe4bc62668aec6d46ac94dc", "author": { "name": "Microsoft Corporation" }, From 9cc555d28b959627b0160cfaac09264bbcf9ea77 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 16:02:20 +0100 Subject: [PATCH 0166/1863] renaming method --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index e385a0d3c6595..0b8bc138bf31e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -477,7 +477,7 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos Range.fromPositions(primaryPosition, primaryEditEndPosition) ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); - const secondaryEditText = getTextFromPosition(primaryEdit.text, positionWithinTextEdit); + const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const textAfterSecondaryCursor = textModel.getValueInRange( Range.fromPositions(pos, primaryEditEndPosition) @@ -488,7 +488,7 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos }); } -function getTextFromPosition(text: string, pos: Position): string { +function substringPos(text: string, pos: Position): string { let subtext = ''; const lines = splitLinesIncludeSeparators(text); for (let i = pos.lineNumber - 1; i < lines.length; i++) { From 258e22ea12e795cc101df2c006b452a83b0b1d0b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 12 Feb 2024 09:59:47 -0600 Subject: [PATCH 0167/1863] audio cue setting migration (#203827) --- .../audioCues/browser/audioCueService.ts | 73 ++- .../browser/accessibilityConfiguration.ts | 567 +++++++++++++++--- .../browser/audioCues.contribution.ts | 61 +- .../contrib/audioCues/browser/commands.ts | 35 +- .../preferences/browser/settingsLayout.ts | 5 + 5 files changed, 588 insertions(+), 153 deletions(-) diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 0e35e7f9e3ca6..7c7dfbe056719 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -178,9 +178,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) + e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.settingsKey) + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') ); return derived(reader => { /** @description audio cue enabled */ @@ -209,9 +209,9 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) + e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.alertSettingsKey) : false + () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false ); return derived(reader => { /** @description alert enabled */ @@ -368,11 +368,12 @@ export class AudioCue { randomOneOf: Sound[]; }; settingsKey: string; + signalSettingsKey: string; alertSettingsKey?: AccessibilityAlertSettingId; alertMessage?: string; }): AudioCue { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.alertSettingsKey, options.alertMessage); + const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); AudioCue._audioCues.add(audioCue); return audioCue; } @@ -386,33 +387,38 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.lineHasError', alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('audioCues.lineHasError.alertMessage', 'Error') + alertMessage: localize('accessibility.signals.lineHasError', 'Error'), + signalSettingsKey: 'accessibility.signals.lineHasError' }); public static readonly warning = AudioCue.register({ name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, settingsKey: 'audioCues.lineHasWarning', alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('audioCues.lineHasWarning.alertMessage', 'Warning') + alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + signalSettingsKey: 'accessibility.signals.lineHasWarning' }); public static readonly foldedArea = AudioCue.register({ name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, settingsKey: 'audioCues.lineHasFoldedArea', alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('audioCues.lineHasFoldedArea.alertMessage', 'Folded') + alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' }); public static readonly break = AudioCue.register({ name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, settingsKey: 'audioCues.lineHasBreakpoint', alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('audioCues.lineHasBreakpoint.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' }); public static readonly inlineSuggestion = AudioCue.register({ name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, settingsKey: 'audioCues.lineHasInlineSuggestion', + signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AudioCue.register({ @@ -420,7 +426,8 @@ export class AudioCue { sound: Sound.quickFixes, settingsKey: 'audioCues.terminalQuickFix', alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('audioCues.terminalQuickFix.alertMessage', 'Quick Fix') + alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + signalSettingsKey: 'accessibility.signals.terminalQuickFix' }); public static readonly onDebugBreak = AudioCue.register({ @@ -428,7 +435,8 @@ export class AudioCue { sound: Sound.break, settingsKey: 'audioCues.onDebugBreak', alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('audioCues.onDebugBreak.alertMessage', 'Breakpoint') + alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + signalSettingsKey: 'accessibility.signals.onDebugBreak' }); public static readonly noInlayHints = AudioCue.register({ @@ -436,7 +444,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.noInlayHints', alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('audioCues.noInlayHints.alertMessage', 'No Inlay Hints') + alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + signalSettingsKey: 'accessibility.signals.noInlayHints' }); public static readonly taskCompleted = AudioCue.register({ @@ -444,7 +453,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.taskCompleted', alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('audioCues.taskCompleted.alertMessage', 'Task Completed') + alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + signalSettingsKey: 'accessibility.signals.taskCompleted' }); public static readonly taskFailed = AudioCue.register({ @@ -452,7 +462,8 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.taskFailed', alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('audioCues.taskFailed.alertMessage', 'Task Failed') + alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + signalSettingsKey: 'accessibility.signals.taskFailed' }); public static readonly terminalCommandFailed = AudioCue.register({ @@ -460,7 +471,8 @@ export class AudioCue { sound: Sound.error, settingsKey: 'audioCues.terminalCommandFailed', alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('audioCues.terminalCommandFailed.alertMessage', 'Command Failed') + alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + signalSettingsKey: 'accessibility.signals.terminalCommandFailed' }); public static readonly terminalBell = AudioCue.register({ @@ -468,7 +480,8 @@ export class AudioCue { sound: Sound.terminalBell, settingsKey: 'audioCues.terminalBell', alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('audioCues.terminalBell.alertMessage', 'Terminal Bell') + alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + signalSettingsKey: 'accessibility.signals.terminalBell' }); public static readonly notebookCellCompleted = AudioCue.register({ @@ -476,7 +489,8 @@ export class AudioCue { sound: Sound.taskCompleted, settingsKey: 'audioCues.notebookCellCompleted', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('audioCues.notebookCellCompleted.alertMessage', 'Notebook Cell Completed') + alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + signalSettingsKey: 'accessibility.signals.notebookCellCompleted' }); public static readonly notebookCellFailed = AudioCue.register({ @@ -484,25 +498,29 @@ export class AudioCue { sound: Sound.taskFailed, settingsKey: 'audioCues.notebookCellFailed', alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('audioCues.notebookCellFailed.alertMessage', 'Notebook Cell Failed') + alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + signalSettingsKey: 'accessibility.signals.notebookCellFailed' }); public static readonly diffLineInserted = AudioCue.register({ name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, settingsKey: 'audioCues.diffLineInserted', + signalSettingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AudioCue.register({ name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, settingsKey: 'audioCues.diffLineDeleted', + signalSettingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AudioCue.register({ name: localize('audioCues.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, settingsKey: 'audioCues.diffLineModified', + signalSettingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AudioCue.register({ @@ -510,7 +528,8 @@ export class AudioCue { sound: Sound.chatRequestSent, settingsKey: 'audioCues.chatRequestSent', alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('audioCues.chatRequestSent.alertMessage', 'Chat Request Sent') + alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + signalSettingsKey: 'accessibility.signals.chatRequestSent' }); public static readonly chatResponseReceived = AudioCue.register({ @@ -524,6 +543,7 @@ export class AudioCue { Sound.chatResponseReceived4 ] }, + signalSettingsKey: 'accessibility.signals.chatResponseReceived' }); public static readonly chatResponsePending = AudioCue.register({ @@ -531,7 +551,8 @@ export class AudioCue { sound: Sound.chatResponsePending, settingsKey: 'audioCues.chatResponsePending', alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('audioCues.chatResponsePending.alertMessage', 'Chat Response Pending') + alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + signalSettingsKey: 'accessibility.signals.chatResponsePending' }); public static readonly clear = AudioCue.register({ @@ -539,7 +560,8 @@ export class AudioCue { sound: Sound.clear, settingsKey: 'audioCues.clear', alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('audioCues.clear.alertMessage', 'Clear') + alertMessage: localize('accessibility.signals.clear', 'Clear'), + signalSettingsKey: 'accessibility.signals.clear' }); public static readonly save = AudioCue.register({ @@ -547,7 +569,8 @@ export class AudioCue { sound: Sound.save, settingsKey: 'audioCues.save', alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('audioCues.save.alertMessage', 'Save') + alertMessage: localize('accessibility.signals.save', 'Save'), + signalSettingsKey: 'accessibility.signals.save' }); public static readonly format = AudioCue.register({ @@ -555,14 +578,16 @@ export class AudioCue { sound: Sound.format, settingsKey: 'audioCues.format', alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('audioCues.format.alertMessage', 'Format') + alertMessage: localize('accessibility.signals.format', 'Format'), + signalSettingsKey: 'accessibility.signals.format' }); private constructor( public readonly sound: SoundSource, public readonly name: string, public readonly settingsKey: string, + public readonly signalSettingsKey: string, public readonly alertSettingsKey?: string, - public readonly alertMessage?: string + public readonly alertMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 4e5b465ff71c8..3f601b3b408e2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { AccessibilityAlertSettingId } from 'vs/platform/audioCues/browser/audioCueService'; +import { workbenchConfigurationNodeBase, Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; +import { AccessibilityAlertSettingId, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; import { ISpeechService } 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'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -70,11 +71,18 @@ export const enum AccessibleViewProviderId { Comments = 'comments' } -const baseProperty: object = { +const baseVerbosityProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, tags: ['accessibility'] }; +const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const baseAlertProperty: IConfigurationPropertySchema = { + type: 'boolean', + default: true, + tags: ['accessibility'], + markdownDeprecationMessage +}; export const accessibilityConfigurationNodeBase = Object.freeze({ id: 'accessibility', @@ -82,183 +90,530 @@ export const accessibilityConfigurationNodeBase = Object.freeze(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.debouncePositionChanges', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.debouncePositionChanges', { value }], + ['audioCues.debouncePositionChangess', { value: undefined }] + ]; + } + }]); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + key: item.settingsKey, + migrateFn: (audioCue, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + const alertSettingsKey = item.alertSettingsKey; + let alert: string | undefined; + if (alertSettingsKey) { + alert = accessor(alertSettingsKey) ?? undefined; + if (typeof alert !== 'string') { + alert = alert ? 'auto' : 'off'; + } + } + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); + return configurationKeyValuePairs; + } + }))); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 1fced33582713..9059db0b31fb8 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -20,7 +20,7 @@ registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); -const audioCueFeatureBase: IConfigurationPropertySchema = { +export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', @@ -29,7 +29,12 @@ const audioCueFeatureBase: IConfigurationPropertySchema = { localize('audioCues.enabled.on', "Enable audio cue."), localize('audioCues.enabled.off', "Disable audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], +}; +const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); +const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...audioCueFeatureBase, + markdownDeprecationMessage }; Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ @@ -50,96 +55,97 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', 'default': false, - tags: ['accessibility'] + tags: ['accessibility'], + 'markdownDeprecationMessage': localize('audioCues.debouncePositionChangesDeprecated', 'This setting is deprecated, instead use the `signals.debouncePositionChanges` setting.') }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueFeatureBase + ...audioCueDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueFeatureBase, + ...audioCueDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { @@ -152,7 +158,8 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.save.always', "Plays the audio cue whenever a file is saved, including auto save."), localize('audioCues.save.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, 'audioCues.format': { 'markdownDescription': localize('audioCues.format', "Plays a sound when a file or notebook is formatted. Also see {0}", '`#accessibility.alert.format#`'), @@ -164,10 +171,12 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('audioCues.format.always', "Plays the audio cue whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), localize('audioCues.format.never', "Never plays the audio cue.") ], - tags: ['accessibility'] + tags: ['accessibility'], + markdownDeprecationMessage }, }, }); registerAction2(ShowAudioCueHelp); registerAction2(ShowAccessibilityAlertHelp); + diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/audioCues/browser/commands.ts index dbaac656df64f..624c67985db52 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/audioCues/browser/commands.ts @@ -31,7 +31,7 @@ export class ShowAudioCueHelp extends Action2 { const accessibilityService = accessor.get(IAccessibilityService); const userGestureCues = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.settingsKey)})` : cue.name, + label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, audioCue: cue, buttons: userGestureCues.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -47,14 +47,22 @@ export class ShowAudioCueHelp extends Action2 { const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + } else { + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + } } } for (const cue of disabledCues) { - if (userGestureCues.includes(cue)) { - configurationService.updateValue(cue.settingsKey, 'never'); + const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; + const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } else { - configurationService.updateValue(cue.settingsKey, 'off'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } qp.hide(); @@ -83,9 +91,10 @@ export class ShowAccessibilityAlertHelp extends Action2 { const audioCueService = accessor.get(IAudioCueService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); + const accessibilityService = accessor.get(IAccessibilityService); const userGestureAlerts = [AudioCue.save, AudioCue.format]; const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) && cue.alertSettingsKey ? `${cue.name} (${configurationService.getValue(cue.alertSettingsKey)})` : cue.name, + label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, audioCue: cue, buttons: userGestureAlerts.includes(cue) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), @@ -101,15 +110,17 @@ export class ShowAccessibilityAlertHelp extends Action2 { const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { if (!userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, true); + let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (alert) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + } } } for (const cue of disabledAlerts) { - if (userGestureAlerts.includes(cue)) { - configurationService.updateValue(cue.alertSettingsKey!, 'never'); - } else { - configurationService.updateValue(cue.alertSettingsKey!, false); - } + const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); + configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } qp.hide(); }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b4b225d7bc80a..54ac57ce22444 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -226,6 +226,11 @@ export const tocData: ITOCEntry = { label: localize('audioCues', 'Audio Cues'), settings: ['audioCues.*'] }, + { + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] + }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From c2df354c9fde197a2fade2bb342da7f427fd390d Mon Sep 17 00:00:00 2001 From: "ermin.zem" Date: Tue, 13 Feb 2024 00:01:52 +0800 Subject: [PATCH 0168/1863] chore: update vscode known variables (#204568) Co-authored-by: ermin.zem --- build/lib/stylelint/vscode-known-variables.json | 1 + 1 file changed, 1 insertion(+) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index f6d81111246c5..0411ea42d7566 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -693,6 +693,7 @@ "--vscode-terminalOverviewRuler-findMatchForeground", "--vscode-terminalStickyScroll-background", "--vscode-terminalStickyScrollHover-background", + "--vscode-testing-coverage-lineHeight", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", From 4f727cb9852caf9bb037344216a8e36d2cc0be66 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 12 Feb 2024 17:29:32 +0100 Subject: [PATCH 0169/1863] polishing the code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 102 ++++++++---------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index b072acb88e073..49cef9c8c8ae9 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -23,7 +23,6 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { basename } from 'vs/base/common/resources'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -39,6 +38,7 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; +import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { Data = 'data', @@ -70,7 +70,6 @@ export class BulkEditPane extends ViewPane { private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; private _multiDiffEditor?: MultiDiffEditor; - private _fileOperations?: BulkFileOperation[]; constructor( options: IViewletViewOptions, @@ -104,6 +103,7 @@ export class BulkEditPane extends ViewPane { override dispose(): void { this._tree.dispose(); this._disposables.dispose(); + this._multiDiffEditor = undefined; super.dispose(); } @@ -268,6 +268,7 @@ export class BulkEditPane extends ViewPane { this._dialogService.warn(message).finally(() => this._done(false)); } + // Going through here to discard discard() { this._done(false); } @@ -320,6 +321,11 @@ export class BulkEditPane extends ViewPane { private async _openElementInMultiDiffEditor(e: IOpenEvent): Promise { + const fileOperations = this._currentInput?.fileOperations; + if (!fileOperations) { + return; + } + const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; if (e.element instanceof TextEditElement) { @@ -335,68 +341,48 @@ export class BulkEditPane extends ViewPane { return; } - let bulkFileOperations: BulkFileOperations | undefined = undefined; - let currentParent = fileElement.parent; - while (true) { - if (currentParent instanceof BulkFileOperations) { - bulkFileOperations = currentParent; - break; - } - currentParent = currentParent.parent; - } - const fileOperations = bulkFileOperations.fileOperations; - - if (this._fileOperations === fileOperations) { - // Multi diff editor already open - this._multiDiffEditor?.scrollTo(fileElement.edit.uri); + if (this._multiDiffEditor) { + // Multi diff editor already visible + this._multiDiffEditor.scrollTo(fileElement.edit.uri); return; } - this._fileOperations = fileOperations; - const resources = []; + const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { - - let leftResource: URI | undefined = operation.textEdits[0].textEdit.resource; - let rightResource: URI | undefined = undefined; - try { - (await this._textModelService.createModelReference(leftResource)).dispose(); - rightResource = this._currentProvider!.asPreviewUri(leftResource); - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; + const operationUri = operation.uri; + const previewUri = this._currentProvider!.asPreviewUri(operationUri); + // delete -> show single editor + if (operation.type & BulkFileOperationType.Delete) { + resources.push({ + original: { resource: undefined }, + modified: { resource: URI.revive(previewUri) } + }); + + } else { + // rename, create, edits -> show diff editr + let leftResource: URI | undefined; + try { + (await this._textModelService.createModelReference(operationUri)).dispose(); + leftResource = operationUri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + resources.push({ + original: { resource: URI.revive(leftResource) }, + modified: { resource: URI.revive(previewUri) } + }); } - resources.push({ - originalUri: leftResource, - modifiedUri: rightResource - }); - } - - let typeLabel: string | undefined; - if (fileElement.edit.type & BulkFileOperationType.Rename) { - typeLabel = localize('rename', "rename"); - } else if (fileElement.edit.type & BulkFileOperationType.Create) { - typeLabel = localize('create', "create"); - } - - let label: string; - if (typeLabel) { - label = localize('edt.title.2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel); - } else { - label = localize('edt.title.1', "{0} (refactor preview)", basename(fileElement.edit.uri)); } - const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); - const description = 'Refactor Preview'; - - console.log('before open editor'); - this._multiDiffEditor = this._disposables.add(await this._editorService.openEditor({ - multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, - resources: resources?.map(r => ({ original: { resource: URI.revive(r.originalUri) }, modified: { resource: URI.revive(r.modifiedUri) } })), - label: label, - description: description, - options: options, - }) as MultiDiffEditor); - - + const label = 'Refactor Preview'; + this._multiDiffEditor = this._sessionDisposables.add( + await this._editorService.openEditor({ + multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + resources, + label: label, + description: label, + options: options, + }) as MultiDiffEditor); } private _onContextMenu(e: ITreeContextMenuEvent): void { From 5ff3ac10849efdc3c54c9d2c66ceb584eb4ccc4b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:34:25 -0600 Subject: [PATCH 0170/1863] first pass of renaming --- build/lib/i18n.resources.json | 2 +- .../components/accessibleDiffViewer.ts | 8 +- .../widget/diffEditor/diffEditorWidget.ts | 16 +- .../widget/embeddedCodeEditorWidget.ts | 6 +- .../editor/contrib/format/browser/format.ts | 10 +- .../contrib/format/browser/formatActions.ts | 6 +- .../browser/inlineCompletionsController.ts | 6 +- .../browser/inlineCompletionsProvider.test.ts | 4 +- .../test/browser/suggestWidgetModel.test.ts | 4 +- .../browser/standaloneCodeEditor.ts | 6 +- .../standalone/browser/standaloneServices.ts | 20 +- .../browser/accessibilitySignalService.ts | 593 ++++++++++++++++++ .../browser/media/break.mp3 | Bin .../browser/media/chatRequestSent.mp3 | Bin .../browser/media/chatResponsePending.mp3 | Bin .../browser/media/chatResponseReceived1.mp3 | Bin .../browser/media/chatResponseReceived2.mp3 | Bin .../browser/media/chatResponseReceived3.mp3 | Bin .../browser/media/chatResponseReceived4.mp3 | Bin .../browser/media/clear.mp3 | Bin .../browser/media/diffLineDeleted.mp3 | Bin .../browser/media/diffLineInserted.mp3 | Bin .../browser/media/diffLineModified.mp3 | Bin .../browser/media/error.mp3 | Bin .../browser/media/foldedAreas.mp3 | Bin .../browser/media/format.mp3 | Bin .../browser/media/quickFixes.mp3 | Bin .../browser/media/save.mp3 | Bin .../browser/media/taskCompleted.mp3 | Bin .../browser/media/taskFailed.mp3 | Bin .../browser/media/terminalBell.mp3 | Bin .../browser/media/warning.mp3 | Bin .../audioCues/browser/audioCueService.ts | 593 ------------------ .../notifications/notificationsCenter.ts | 6 +- .../notifications/notificationsCommands.ts | 6 +- .../browser/accessibilityConfiguration.ts | 147 ++--- .../browser/accessibilityContributions.ts | 10 +- .../accessibility/browser/saveAudioCue.ts | 6 +- .../accessibilitySignal.contribution.ts} | 16 +- ...ccessibilitySignalDebuggerContribution.ts} | 12 +- ...ssibilitySignalLineFeatureContribution.ts} | 56 +- .../browser/commands.ts | 74 +-- .../chat/browser/actions/chatClearActions.ts | 4 +- .../chat/browser/chatAccessibilityService.ts | 12 +- .../workbench/contrib/debug/browser/repl.ts | 6 +- .../files/test/browser/editorAutoSave.test.ts | 4 +- .../browser/inlayHintsAccessibilty.ts | 6 +- .../notebookExecutionStateServiceImpl.ts | 8 +- .../output/browser/output.contribution.ts | 6 +- .../preferences/browser/settingsLayout.ts | 6 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 18 +- .../contrib/search/browser/searchView.ts | 6 +- .../tasks/browser/taskTerminalStatus.ts | 12 +- .../tasks/electron-sandbox/taskService.ts | 4 +- .../test/browser/taskTerminalStatus.test.ts | 12 +- .../terminal/browser/terminalInstance.ts | 6 +- .../terminal/browser/xterm/decorationAddon.ts | 6 +- .../terminal/browser/xterm/xtermTerminal.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 4 +- .../quickFix/browser/quickFixAddon.ts | 6 +- .../test/browser/workbenchTestServices.ts | 4 +- src/vs/workbench/workbench.common.main.ts | 2 +- 62 files changed, 873 insertions(+), 872 deletions(-) create mode 100644 src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/break.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatRequestSent.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponsePending.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived1.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived2.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived3.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/chatResponseReceived4.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/clear.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineDeleted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineInserted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/diffLineModified.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/error.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/foldedAreas.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/format.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/quickFixes.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/save.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/taskCompleted.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/taskFailed.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/terminalBell.mp3 (100%) rename src/vs/platform/{audioCues => accessibilitySignal}/browser/media/warning.mp3 (100%) delete mode 100644 src/vs/platform/audioCues/browser/audioCueService.ts rename src/vs/workbench/contrib/{audioCues/browser/audioCues.contribution.ts => accessibilitySignals/browser/accessibilitySignal.contribution.ts} (90%) rename src/vs/workbench/contrib/{audioCues/browser/audioCueDebuggerContribution.ts => accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts} (77%) rename src/vs/workbench/contrib/{audioCues/browser/audioCueLineFeatureContribution.ts => accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts} (77%) rename src/vs/workbench/contrib/{audioCues => accessibilitySignals}/browser/commands.ts (55%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index cf74aac44be46..6b8f0738b7a2b 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -327,7 +327,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/audioCues", + "name": "vs/workbench/contrib/accessibilitySignals", "project": "vscode-workbench" }, { diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index 786a8c26192a8..aa3d6da540292 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -30,7 +30,7 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { RenderLineInput, renderViewLine2 } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineRenderingData } from 'vs/editor/common/viewModel'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import 'vs/css!./accessibleDiffViewer'; @@ -109,7 +109,7 @@ class ViewModel extends Disposable { private readonly _editors: DiffEditorEditors, private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, public readonly canClose: IObservable, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, ) { super(); @@ -143,9 +143,9 @@ class ViewModel extends Disposable { /** @description play audio-cue for diff */ const currentViewItem = this.currentElement.read(reader); if (currentViewItem?.type === LineType.Deleted) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'accessibleDiffViewer.currentElementChanged' }); } else if (currentViewItem?.type === LineType.Added) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'accessibleDiffViewer.currentElementChanged' }); } })); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 3fd6e543a6103..acf848c25c418 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -35,7 +35,7 @@ import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/ra import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -97,7 +97,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IEditorProgressService private readonly _editorProgressService: IEditorProgressService, ) { super(); @@ -271,11 +271,11 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { if (e?.reason === CursorChangeReason.Explicit) { const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modified.contains(e.position.lineNumber)); if (diff?.lineRangeMapping.modified.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.cursorPositionChanged' }); } else if (diff?.lineRangeMapping.original.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.cursorPositionChanged' }); } else if (diff) { - this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.cursorPositionChanged' }); } } })); @@ -528,11 +528,11 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._goTo(diff); if (diff.lineRangeMapping.modified.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineDeleted, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { source: 'diffEditor.goToDiff' }); } else if (diff.lineRangeMapping.original.isEmpty) { - this._audioCueService.playAudioCue(AudioCue.diffLineInserted, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { source: 'diffEditor.goToDiff' }); } else if (diff) { - this._audioCueService.playAudioCue(AudioCue.diffLineModified, { source: 'diffEditor.goToDiff' }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { source: 'diffEditor.goToDiff' }); } } diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 8d63ee6f754b8..4a5dffa53477d 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -12,7 +12,7 @@ import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'v import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -79,10 +79,10 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IAudioCueService audioCueService: IAudioCueService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, audioCueService, editorProgressService); + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/contrib/format/browser/format.ts b/src/vs/editor/contrib/format/browser/format.ts index 742518daae3b7..dbf6ede9eae1b 100644 --- a/src/vs/editor/contrib/format/browser/format.ts +++ b/src/vs/editor/contrib/format/browser/format.ts @@ -30,7 +30,7 @@ import { IProgress } from 'vs/platform/progress/common/progress'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { ILogService } from 'vs/platform/log/common/log'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export function getRealAndSyntheticDocumentFormattersOrdered( documentFormattingEditProvider: LanguageFeatureRegistry, @@ -135,7 +135,7 @@ export async function formatDocumentRangesWithProvider( ): Promise { const workerService = accessor.get(IEditorWorkerService); const logService = accessor.get(ILogService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); let model: ITextModel; let cts: CancellationTokenSource; @@ -279,7 +279,7 @@ export async function formatDocumentRangesWithProvider( return null; }); } - audioCueService.playAudioCue(AudioCue.format, { userGesture }); + accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture }); return true; } @@ -312,7 +312,7 @@ export async function formatDocumentWithProvider( userGesture?: boolean ): Promise { const workerService = accessor.get(IEditorWorkerService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); let model: ITextModel; let cts: CancellationTokenSource; @@ -373,7 +373,7 @@ export async function formatDocumentWithProvider( return null; }); } - audioCueService.playAudioCue(AudioCue.format, { userGesture }); + accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture }); return true; } diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index a9c4984e4c1d5..1f345ac532961 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -21,7 +21,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode, getOnTypeFormattingEdits } from 'vs/editor/contrib/format/browser/format'; import { FormattingEdit } from 'vs/editor/contrib/format/browser/formattingEdit'; import * as nls from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -40,7 +40,7 @@ export class FormatOnType implements IEditorContribution { private readonly _editor: ICodeEditor, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IEditorWorkerService private readonly _workerService: IEditorWorkerService, - @IAudioCueService private readonly _audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService ) { this._disposables.add(_languageFeaturesService.onTypeFormattingEditProvider.onDidChange(this._update, this)); this._disposables.add(_editor.onDidChangeModel(() => this._update())); @@ -143,7 +143,7 @@ export class FormatOnType implements IEditorContribution { return; } if (isNonEmptyArray(edits)) { - this._audioCueService.playAudioCue(AudioCue.format, { userGesture: false }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.format, { userGesture: false }); FormattingEdit.execute(this._editor, edits, true); } }).finally(() => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 4f38487a6ffb8..aad71f482db0d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -23,7 +23,7 @@ import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from import { InlineCompletionsModel, VersionIdChangeReason } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel'; import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -81,7 +81,7 @@ export class InlineCompletionsController extends Disposable { @ICommandService private readonly _commandService: ICommandService, @ILanguageFeatureDebounceService private readonly _debounceService: ILanguageFeatureDebounceService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); @@ -221,7 +221,7 @@ export class InlineCompletionsController extends Disposable { if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.inlineCompletion.semanticId; const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - this._audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { + this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { this.provideScreenReaderUpdate(state.ghostText.renderForScreenReader(lineText)); } diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index 20dd10e2d6709..aa5c7a4e4a638 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -19,7 +19,7 @@ import { SingleTextEdit } from 'vs/editor/contrib/inlineCompletions/browser/sing import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/browser/utils'; import { ITestCodeEditor, TestCodeEditorInstantiationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Selection } from 'vs/editor/common/core/selection'; @@ -775,7 +775,7 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( options.serviceCollection = new ServiceCollection(); } options.serviceCollection.set(ILanguageFeaturesService, languageFeaturesService); - options.serviceCollection.set(IAudioCueService, { + options.serviceCollection.set(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; }, } as any); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 06cc564f3d926..969ab2fec427f 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -34,7 +34,7 @@ import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/brow import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { autorun } from 'vs/base/common/observable'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Suggest Widget Model', () => { @@ -160,7 +160,7 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( }], [ILabelService, new class extends mock() { }], [IWorkspaceContextService, new class extends mock() { }], - [IAudioCueService, { + [IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; }, } as any] diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 60c16599bf393..739e76f97c22f 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -37,7 +37,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { mainWindow } from 'vs/base/browser/window'; /** @@ -499,7 +499,7 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandalo @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, @IClipboardService clipboardService: IClipboardService, - @IAudioCueService audioCueService: IAudioCueService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, true); @@ -518,7 +518,7 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget implements IStandalo contextKeyService, instantiationService, codeEditorService, - audioCueService, + accessibilitySignalService, editorProgressService, ); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 97841a6012baf..351f85537d8e2 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -88,7 +88,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { WorkspaceEdit } from 'vs/editor/common/languages'; -import { AudioCue, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService, Sound } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -1057,33 +1057,33 @@ class StandaloneContextMenuService extends ContextMenuService { } } -class StandaloneAudioService implements IAudioCueService { +class StandaloneAccessbilitySignalService implements IAccessibilitySignalService { _serviceBrand: undefined; - async playAudioCue(cue: AudioCue, options: {}): Promise { + async playSignal(cue: AccessibilitySignal, options: {}): Promise { } - async playAudioCues(cues: AudioCue[]): Promise { + async playAccessibilitySignals(cues: AccessibilitySignal[]): Promise { } - isCueEnabled(cue: AudioCue): boolean { + isSoundEnabled(cue: AccessibilitySignal): boolean { return false; } - isAlertEnabled(cue: AudioCue): boolean { + isAnnouncementEnabled(cue: AccessibilitySignal): boolean { return false; } - onEnabledChanged(cue: AudioCue): Event { + onSoundEnabledChanged(cue: AccessibilitySignal): Event { return Event.None; } - onAlertEnabledChanged(cue: AudioCue): Event { + onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { return Event.None; } async playSound(cue: Sound, allowManyInParallel?: boolean | undefined): Promise { } - playAudioCueLoop(cue: AudioCue): IDisposable { + playSignalLoop(cue: AccessibilitySignal): IDisposable { return toDisposable(() => { }); } } @@ -1125,7 +1125,7 @@ registerSingleton(IOpenerService, OpenerService, InstantiationType.Eager); registerSingleton(IClipboardService, BrowserClipboardService, InstantiationType.Eager); registerSingleton(IContextMenuService, StandaloneContextMenuService, InstantiationType.Eager); registerSingleton(IMenuService, MenuService, InstantiationType.Eager); -registerSingleton(IAudioCueService, StandaloneAudioService, InstantiationType.Eager); +registerSingleton(IAccessibilitySignalService, StandaloneAccessbilitySignalService, InstantiationType.Eager); /** * We don't want to eagerly instantiate services because embedders get a one time chance diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts new file mode 100644 index 0000000000000..9bbbf0b1db984 --- /dev/null +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -0,0 +1,593 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { FileAccess } from 'vs/base/common/network'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { localize } from 'vs/nls'; +import { observableFromEvent, derived } from 'vs/base/common/observable'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +export const IAccessibilitySignalService = createDecorator('accessibilitySignalService'); + +export interface IAccessibilitySignalService { + readonly _serviceBrand: undefined; + playSignal(cue: AccessibilitySignal, options?: IAccessbilitySignalOptions): Promise; + playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise; + isSoundEnabled(cue: AccessibilitySignal): boolean; + isAnnouncementEnabled(cue: AccessibilitySignal): boolean; + onSoundEnabledChanged(cue: AccessibilitySignal): Event; + onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event; + + playSound(cue: Sound, allowManyInParallel?: boolean): Promise; + playSignalLoop(cue: AccessibilitySignal, milliseconds: number): IDisposable; +} + +export interface IAccessbilitySignalOptions { + allowManyInParallel?: boolean; + source?: string; + /** + * For actions like save or format, depending on the + * configured value, we will only + * play the sound if the user triggered the action. + */ + userGesture?: boolean; +} + +export class AccessibilitySignalService extends Disposable implements IAccessibilitySignalService { + readonly _serviceBrand: undefined; + private readonly sounds: Map = new Map(); + private readonly screenReaderAttached = observableFromEvent( + this.accessibilityService.onDidChangeScreenReaderOptimized, + () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() + ); + private readonly sentTelemetry = new Set(); + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { + super(); + } + + public async playSignal(signal: AccessibilitySignal, options: IAccessbilitySignalOptions = {}): Promise { + const alertMessage = signal.announcementMessage; + if (this.isAnnouncementEnabled(signal, options.userGesture) && alertMessage) { + this.accessibilityService.status(alertMessage); + } + + if (this.isSoundEnabled(signal, options.userGesture)) { + this.sendAudioCueTelemetry(signal, options.source); + await this.playSound(signal.sound.getSound(), options.allowManyInParallel); + } + } + + public async playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise { + for (const cue of cues) { + this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); + } + const cueArray = cues.map(c => 'cue' in c ? c.cue : c); + const alerts = cueArray.filter(cue => this.isAnnouncementEnabled(cue)).map(c => c.announcementMessage); + if (alerts.length) { + this.accessibilityService.status(alerts.join(', ')); + } + + // Some audio cues might reuse sounds. Don't play the same sound twice. + const sounds = new Set(cueArray.filter(cue => this.isSoundEnabled(cue)).map(cue => cue.sound.getSound())); + await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); + + } + + + private sendAudioCueTelemetry(cue: AccessibilitySignal, source: string | undefined): void { + const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); + const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); + // Only send once per user session + if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { + return; + } + this.sentTelemetry.add(key); + + this.telemetryService.publicLog2<{ + audioCue: string; + source: string; + isScreenReaderOptimized: boolean; + }, { + owner: 'hediet'; + + audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; + isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; + + comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; + }>('audioCue.played', { + audioCue: cue.name, + source: source ?? '', + isScreenReaderOptimized, + }); + } + + private getVolumeInPercent(): number { + const volume = this.configurationService.getValue('accessibilitySignals.volume'); + if (typeof volume !== 'number') { + return 50; + } + + return Math.max(Math.min(volume, 100), 0); + } + + private readonly playingSounds = new Set(); + + public async playSound(sound: Sound, allowManyInParallel = false): Promise { + if (!allowManyInParallel && this.playingSounds.has(sound)) { + return; + } + this.playingSounds.add(sound); + const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignals/browser/media/${sound.fileName}`).toString(true); + + try { + const sound = this.sounds.get(url); + if (sound) { + sound.volume = this.getVolumeInPercent() / 100; + sound.currentTime = 0; + await sound.play(); + } else { + const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); + this.sounds.set(url, playedSound); + } + } catch (e) { + if (!e.message.includes('play() can only be initiated by a user gesture')) { + // tracking this issue in #178642, no need to spam the console + console.error('Error while playing sound', e); + } + } finally { + this.playingSounds.delete(sound); + } + } + + public playSignalLoop(signal: AccessibilitySignal, milliseconds: number): IDisposable { + let playing = true; + const playSound = () => { + if (playing) { + this.playSignal(signal, { allowManyInParallel: true }).finally(() => { + setTimeout(() => { + if (playing) { + playSound(); + } + }, milliseconds); + }); + } + }; + playSound(); + return toDisposable(() => playing = false); + } + + private readonly obsoleteAccessibilitySignalsEnabled = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration('accessibilitySignals.enabled') + ), + () => /** @description config: accessibilitySignals.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('accessibilitySignals.enabled') + ); + + private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { + const settingObservable = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.signalSettingsKey) + ), + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') + ); + return derived(reader => { + /** @description audio cue enabled */ + const setting = settingObservable.read(reader); + if ( + setting === 'on' || + (setting === 'auto' && this.screenReaderAttached.read(reader)) + ) { + return true; + } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { + return true; + } + + const obsoleteSetting = this.obsoleteAccessibilitySignalsEnabled.read(reader); + if ( + obsoleteSetting === 'on' || + (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) + ) { + return true; + } + + return false; + }); + }, JSON.stringify); + + private readonly isAnnouncementEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { + const settingObservable = observableFromEvent( + Event.filter(this.configurationService.onDidChangeConfiguration, (e) => + e.affectsConfiguration(event.signal.alertSettingsKey!) || e.affectsConfiguration(event.signal.signalSettingsKey) + ), + () => event.signal.alertSettingsKey ? this.configurationService.getValue(event.signal.signalSettingsKey + '.alert') : false + ); + return derived(reader => { + /** @description alert enabled */ + const setting = settingObservable.read(reader); + if ( + !this.screenReaderAttached.read(reader) + ) { + return false; + } + return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; + }); + }, JSON.stringify); + + public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { + if (!cue.alertSettingsKey) { + return false; + } + return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + } + + public isSoundEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { + return this.isSoundEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + } + + public onSoundEnabledChanged(cue: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal: cue })); + } + + public onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isAnnouncementEnabledCache.get({ signal: cue })); + } +} + + +/** + * Play the given audio url. + * @volume value between 0 and 1 + */ +function playAudio(url: string, volume: number): Promise { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.volume = volume; + audio.addEventListener('ended', () => { + resolve(audio); + }); + audio.addEventListener('error', (e) => { + // When the error event fires, ended might not be called + reject(e.error); + }); + audio.play().catch(e => { + // When play fails, the error event is not fired. + reject(e); + }); + }); +} + +class Cache { + private readonly map = new Map(); + constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { + } + + public get(arg: TArg): TValue { + if (this.map.has(arg)) { + return this.map.get(arg)!; + } + + const value = this.getValue(arg); + const key = this.getKey(arg); + this.map.set(key, value); + return value; + } +} + +/** + * Corresponds to the audio files in ./media. +*/ +export class Sound { + private static register(options: { fileName: string }): Sound { + const sound = new Sound(options.fileName); + return sound; + } + + public static readonly error = Sound.register({ fileName: 'error.mp3' }); + public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); + public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); + public static readonly break = Sound.register({ fileName: 'break.mp3' }); + public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); + public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); + public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); + public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); + public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); + public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); + public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); + public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); + public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); + public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); + public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); + public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); + public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); + public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); + public static readonly save = Sound.register({ fileName: 'save.mp3' }); + public static readonly format = Sound.register({ fileName: 'format.mp3' }); + + private constructor(public readonly fileName: string) { } +} + +export class SoundSource { + constructor( + public readonly randomOneOf: Sound[] + ) { } + + public getSound(deterministic = false): Sound { + if (deterministic || this.randomOneOf.length === 1) { + return this.randomOneOf[0]; + } else { + const index = Math.floor(Math.random() * this.randomOneOf.length); + return this.randomOneOf[index]; + } + } +} + +export const enum AccessibilityAlertSettingId { + Save = 'accessibility.alert.save', + Format = 'accessibility.alert.format', + Clear = 'accessibility.alert.clear', + Breakpoint = 'accessibility.alert.breakpoint', + Error = 'accessibility.alert.error', + Warning = 'accessibility.alert.warning', + FoldedArea = 'accessibility.alert.foldedArea', + TerminalQuickFix = 'accessibility.alert.terminalQuickFix', + TerminalBell = 'accessibility.alert.terminalBell', + TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', + TaskCompleted = 'accessibility.alert.taskCompleted', + TaskFailed = 'accessibility.alert.taskFailed', + ChatRequestSent = 'accessibility.alert.chatRequestSent', + NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', + NotebookCellFailed = 'accessibility.alert.notebookCellFailed', + OnDebugBreak = 'accessibility.alert.onDebugBreak', + NoInlayHints = 'accessibility.alert.noInlayHints', + LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', + ChatResponsePending = 'accessibility.alert.chatResponsePending' +} + + +export class AccessibilitySignal { + private static _signals = new Set(); + private static register(options: { + name: string; + sound: Sound | { + /** + * Gaming and other apps often play a sound variant when the same event happens again + * for an improved experience. This option enables audio cues to play a random sound. + */ + randomOneOf: Sound[]; + }; + legacyAudioCueSettingsKey: string; + settingsKey: string; + legacyAnnouncementSettingsKey?: AccessibilityAlertSettingId; + announcementMessage?: string; + }): AccessibilitySignal { + const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); + const signal = new AccessibilitySignal(soundSource, options.name, options.legacyAudioCueSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); + AccessibilitySignal._signals.add(signal); + return signal; + } + + public static get allAccessibilitySignals() { + return [...this._signals]; + } + + public static readonly error = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasError', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, + announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), + settingsKey: 'accessibility.signals.lineHasError' + }); + public static readonly warning = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), + sound: Sound.warning, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasWarning', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, + announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), + settingsKey: 'accessibility.signals.lineHasWarning' + }); + public static readonly foldedArea = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), + sound: Sound.foldedArea, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, + announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), + settingsKey: 'accessibility.signals.lineHasFoldedArea' + }); + public static readonly break = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), + sound: Sound.break, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, + announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), + settingsKey: 'accessibility.signals.lineHasBreakpoint' + }); + public static readonly inlineSuggestion = AccessibilitySignal.register({ + name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), + sound: Sound.quickFixes, + legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + settingsKey: 'accessibility.signals.lineHasInlineSuggestion' + }); + + public static readonly terminalQuickFix = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), + sound: Sound.quickFixes, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, + announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), + settingsKey: 'accessibility.signals.terminalQuickFix' + }); + + public static readonly onDebugBreak = AccessibilitySignal.register({ + name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), + sound: Sound.break, + legacyAudioCueSettingsKey: 'accessibilitySignals.onDebugBreak', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, + announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), + settingsKey: 'accessibility.signals.onDebugBreak' + }); + + public static readonly noInlayHints = AccessibilitySignal.register({ + name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.noInlayHints', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, + announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), + settingsKey: 'accessibility.signals.noInlayHints' + }); + + public static readonly taskCompleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), + sound: Sound.taskCompleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.taskCompleted', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, + announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), + settingsKey: 'accessibility.signals.taskCompleted' + }); + + public static readonly taskFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.taskFailed', 'Task Failed'), + sound: Sound.taskFailed, + legacyAudioCueSettingsKey: 'accessibilitySignals.taskFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, + announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), + settingsKey: 'accessibility.signals.taskFailed' + }); + + public static readonly terminalCommandFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), + sound: Sound.error, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, + announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), + settingsKey: 'accessibility.signals.terminalCommandFailed' + }); + + public static readonly terminalBell = AccessibilitySignal.register({ + name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), + sound: Sound.terminalBell, + legacyAudioCueSettingsKey: 'accessibilitySignals.terminalBell', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, + announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), + settingsKey: 'accessibility.signals.terminalBell' + }); + + public static readonly notebookCellCompleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), + sound: Sound.taskCompleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, + announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), + settingsKey: 'accessibility.signals.notebookCellCompleted' + }); + + public static readonly notebookCellFailed = AccessibilitySignal.register({ + name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), + sound: Sound.taskFailed, + legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, + announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), + settingsKey: 'accessibility.signals.notebookCellFailed' + }); + + public static readonly diffLineInserted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), + sound: Sound.diffLineInserted, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineInserted', + settingsKey: 'accessibility.signals.diffLineInserted' + }); + + public static readonly diffLineDeleted = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), + sound: Sound.diffLineDeleted, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineDeleted', + settingsKey: 'accessibility.signals.diffLineDeleted' + }); + + public static readonly diffLineModified = AccessibilitySignal.register({ + name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), + sound: Sound.diffLineModified, + legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineModified', + settingsKey: 'accessibility.signals.diffLineModified' + }); + + public static readonly chatRequestSent = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), + sound: Sound.chatRequestSent, + legacyAudioCueSettingsKey: 'accessibilitySignals.chatRequestSent', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, + announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), + settingsKey: 'accessibility.signals.chatRequestSent' + }); + + public static readonly chatResponseReceived = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), + legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponseReceived', + sound: { + randomOneOf: [ + Sound.chatResponseReceived1, + Sound.chatResponseReceived2, + Sound.chatResponseReceived3, + Sound.chatResponseReceived4 + ] + }, + settingsKey: 'accessibility.signals.chatResponseReceived' + }); + + public static readonly chatResponsePending = AccessibilitySignal.register({ + name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), + sound: Sound.chatResponsePending, + legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponsePending', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, + announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), + settingsKey: 'accessibility.signals.chatResponsePending' + }); + + public static readonly clear = AccessibilitySignal.register({ + name: localize('accessibilitySignals.clear', 'Clear'), + sound: Sound.clear, + legacyAudioCueSettingsKey: 'accessibilitySignals.clear', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, + announcementMessage: localize('accessibility.signals.clear', 'Clear'), + settingsKey: 'accessibility.signals.clear' + }); + + public static readonly save = AccessibilitySignal.register({ + name: localize('accessibilitySignals.save', 'Save'), + sound: Sound.save, + legacyAudioCueSettingsKey: 'accessibilitySignals.save', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, + announcementMessage: localize('accessibility.signals.save', 'Save'), + settingsKey: 'accessibility.signals.save' + }); + + public static readonly format = AccessibilitySignal.register({ + name: localize('accessibilitySignals.format', 'Format'), + sound: Sound.format, + legacyAudioCueSettingsKey: 'accessibilitySignals.format', + legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, + announcementMessage: localize('accessibility.signals.format', 'Format'), + settingsKey: 'accessibility.signals.format' + }); + + private constructor( + public readonly sound: SoundSource, + public readonly name: string, + public readonly settingsKey: string, + public readonly signalSettingsKey: string, + public readonly alertSettingsKey?: string, + public readonly announcementMessage?: string, + ) { } +} diff --git a/src/vs/platform/audioCues/browser/media/break.mp3 b/src/vs/platform/accessibilitySignal/browser/media/break.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/break.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/break.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatRequestSent.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponsePending.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponsePending.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponsePending.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived1.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived2.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived3.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 diff --git a/src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 b/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/chatResponseReceived4.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 diff --git a/src/vs/platform/audioCues/browser/media/clear.mp3 b/src/vs/platform/accessibilitySignal/browser/media/clear.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/clear.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/clear.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineDeleted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineDeleted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineDeleted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineInserted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineInserted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineInserted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/diffLineModified.mp3 b/src/vs/platform/accessibilitySignal/browser/media/diffLineModified.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/diffLineModified.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/diffLineModified.mp3 diff --git a/src/vs/platform/audioCues/browser/media/error.mp3 b/src/vs/platform/accessibilitySignal/browser/media/error.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/error.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/error.mp3 diff --git a/src/vs/platform/audioCues/browser/media/foldedAreas.mp3 b/src/vs/platform/accessibilitySignal/browser/media/foldedAreas.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/foldedAreas.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/foldedAreas.mp3 diff --git a/src/vs/platform/audioCues/browser/media/format.mp3 b/src/vs/platform/accessibilitySignal/browser/media/format.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/format.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/format.mp3 diff --git a/src/vs/platform/audioCues/browser/media/quickFixes.mp3 b/src/vs/platform/accessibilitySignal/browser/media/quickFixes.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/quickFixes.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/quickFixes.mp3 diff --git a/src/vs/platform/audioCues/browser/media/save.mp3 b/src/vs/platform/accessibilitySignal/browser/media/save.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/save.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/save.mp3 diff --git a/src/vs/platform/audioCues/browser/media/taskCompleted.mp3 b/src/vs/platform/accessibilitySignal/browser/media/taskCompleted.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/taskCompleted.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/taskCompleted.mp3 diff --git a/src/vs/platform/audioCues/browser/media/taskFailed.mp3 b/src/vs/platform/accessibilitySignal/browser/media/taskFailed.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/taskFailed.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/taskFailed.mp3 diff --git a/src/vs/platform/audioCues/browser/media/terminalBell.mp3 b/src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/terminalBell.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 diff --git a/src/vs/platform/audioCues/browser/media/warning.mp3 b/src/vs/platform/accessibilitySignal/browser/media/warning.mp3 similarity index 100% rename from src/vs/platform/audioCues/browser/media/warning.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/warning.mp3 diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts deleted file mode 100644 index 7c7dfbe056719..0000000000000 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ /dev/null @@ -1,593 +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, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { FileAccess } from 'vs/base/common/network'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { observableFromEvent, derived } from 'vs/base/common/observable'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -export const IAudioCueService = createDecorator('audioCue'); - -export interface IAudioCueService { - readonly _serviceBrand: undefined; - playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise; - playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise; - isCueEnabled(cue: AudioCue): boolean; - isAlertEnabled(cue: AudioCue): boolean; - onEnabledChanged(cue: AudioCue): Event; - onAlertEnabledChanged(cue: AudioCue): Event; - - playSound(cue: Sound, allowManyInParallel?: boolean): Promise; - playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; -} - -export interface IAudioCueOptions { - allowManyInParallel?: boolean; - source?: string; - /** - * For actions like save or format, depending on the - * configured value, we will only - * play the sound if the user triggered the action. - */ - userGesture?: boolean; -} - -export class AudioCueService extends Disposable implements IAudioCueService { - readonly _serviceBrand: undefined; - private readonly sounds: Map = new Map(); - private readonly screenReaderAttached = observableFromEvent( - this.accessibilityService.onDidChangeScreenReaderOptimized, - () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() - ); - private readonly sentTelemetry = new Set(); - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super(); - } - - public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { - const alertMessage = cue.alertMessage; - if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { - this.accessibilityService.status(alertMessage); - } - - if (this.isCueEnabled(cue, options.userGesture)) { - this.sendAudioCueTelemetry(cue, options.source); - await this.playSound(cue.sound.getSound(), options.allowManyInParallel); - } - } - - public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise { - for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); - } - const cueArray = cues.map(c => 'cue' in c ? c.cue : c); - const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); - if (alerts.length) { - this.accessibilityService.status(alerts.join(', ')); - } - - // Some audio cues might reuse sounds. Don't play the same sound twice. - const sounds = new Set(cueArray.filter(cue => this.isCueEnabled(cue)).map(cue => cue.sound.getSound())); - await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); - - } - - - private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void { - const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); - const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); - // Only send once per user session - if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { - return; - } - this.sentTelemetry.add(key); - - this.telemetryService.publicLog2<{ - audioCue: string; - source: string; - isScreenReaderOptimized: boolean; - }, { - owner: 'hediet'; - - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; - isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, - source: source ?? '', - isScreenReaderOptimized, - }); - } - - private getVolumeInPercent(): number { - const volume = this.configurationService.getValue('audioCues.volume'); - if (typeof volume !== 'number') { - return 50; - } - - return Math.max(Math.min(volume, 100), 0); - } - - private readonly playingSounds = new Set(); - - public async playSound(sound: Sound, allowManyInParallel = false): Promise { - if (!allowManyInParallel && this.playingSounds.has(sound)) { - return; - } - this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(true); - - try { - const sound = this.sounds.get(url); - if (sound) { - sound.volume = this.getVolumeInPercent() / 100; - sound.currentTime = 0; - await sound.play(); - } else { - const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); - this.sounds.set(url, playedSound); - } - } catch (e) { - if (!e.message.includes('play() can only be initiated by a user gesture')) { - // tracking this issue in #178642, no need to spam the console - console.error('Error while playing sound', e); - } - } finally { - this.playingSounds.delete(sound); - } - } - - public playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable { - let playing = true; - const playSound = () => { - if (playing) { - this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => { - setTimeout(() => { - if (playing) { - playSound(); - } - }, milliseconds); - }); - } - }; - playSound(); - return toDisposable(() => playing = false); - } - - private readonly obsoleteAudioCuesEnabled = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration('audioCues.enabled') - ), - () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('audioCues.enabled') - ); - - private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') - ); - return derived(reader => { - /** @description audio cue enabled */ - const setting = settingObservable.read(reader); - if ( - setting === 'on' || - (setting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { - return true; - } - - const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); - if ( - obsoleteSetting === 'on' || - (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } - - return false; - }); - }, JSON.stringify); - - private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false - ); - return derived(reader => { - /** @description alert enabled */ - const setting = settingObservable.read(reader); - if ( - !this.screenReaderAttached.read(reader) - ) { - return false; - } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; - }); - }, JSON.stringify); - - public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { - return false; - } - return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public isCueEnabled(cue: AudioCue, userGesture?: boolean): boolean { - return this.isCueEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public onEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); - } - - public onAlertEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isAlertEnabledCache.get({ cue })); - } -} - - -/** - * Play the given audio url. - * @volume value between 0 and 1 - */ -function playAudio(url: string, volume: number): Promise { - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.volume = volume; - audio.addEventListener('ended', () => { - resolve(audio); - }); - audio.addEventListener('error', (e) => { - // When the error event fires, ended might not be called - reject(e.error); - }); - audio.play().catch(e => { - // When play fails, the error event is not fired. - reject(e); - }); - }); -} - -class Cache { - private readonly map = new Map(); - constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { - } - - public get(arg: TArg): TValue { - if (this.map.has(arg)) { - return this.map.get(arg)!; - } - - const value = this.getValue(arg); - const key = this.getKey(arg); - this.map.set(key, value); - return value; - } -} - -/** - * Corresponds to the audio files in ./media. -*/ -export class Sound { - private static register(options: { fileName: string }): Sound { - const sound = new Sound(options.fileName); - return sound; - } - - public static readonly error = Sound.register({ fileName: 'error.mp3' }); - public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); - public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); - public static readonly break = Sound.register({ fileName: 'break.mp3' }); - public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); - public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); - public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); - public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); - public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); - public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); - public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); - public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); - public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); - public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); - public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); - public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); - public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); - public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); - public static readonly save = Sound.register({ fileName: 'save.mp3' }); - public static readonly format = Sound.register({ fileName: 'format.mp3' }); - - private constructor(public readonly fileName: string) { } -} - -export class SoundSource { - constructor( - public readonly randomOneOf: Sound[] - ) { } - - public getSound(deterministic = false): Sound { - if (deterministic || this.randomOneOf.length === 1) { - return this.randomOneOf[0]; - } else { - const index = Math.floor(Math.random() * this.randomOneOf.length); - return this.randomOneOf[index]; - } - } -} - -export const enum AccessibilityAlertSettingId { - Save = 'accessibility.alert.save', - Format = 'accessibility.alert.format', - Clear = 'accessibility.alert.clear', - Breakpoint = 'accessibility.alert.breakpoint', - Error = 'accessibility.alert.error', - Warning = 'accessibility.alert.warning', - FoldedArea = 'accessibility.alert.foldedArea', - TerminalQuickFix = 'accessibility.alert.terminalQuickFix', - TerminalBell = 'accessibility.alert.terminalBell', - TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', - TaskCompleted = 'accessibility.alert.taskCompleted', - TaskFailed = 'accessibility.alert.taskFailed', - ChatRequestSent = 'accessibility.alert.chatRequestSent', - NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', - NotebookCellFailed = 'accessibility.alert.notebookCellFailed', - OnDebugBreak = 'accessibility.alert.onDebugBreak', - NoInlayHints = 'accessibility.alert.noInlayHints', - LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', - ChatResponsePending = 'accessibility.alert.chatResponsePending' -} - - -export class AudioCue { - private static _audioCues = new Set(); - private static register(options: { - name: string; - sound: Sound | { - /** - * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. - */ - randomOneOf: Sound[]; - }; - settingsKey: string; - signalSettingsKey: string; - alertSettingsKey?: AccessibilityAlertSettingId; - alertMessage?: string; - }): AudioCue { - const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); - AudioCue._audioCues.add(audioCue); - return audioCue; - } - - public static get allAudioCues() { - return [...this._audioCues]; - } - - public static readonly error = AudioCue.register({ - name: localize('audioCues.lineHasError.name', 'Error on Line'), - sound: Sound.error, - settingsKey: 'audioCues.lineHasError', - alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.signals.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.signals.lineHasError' - }); - public static readonly warning = AudioCue.register({ - name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), - sound: Sound.warning, - settingsKey: 'audioCues.lineHasWarning', - alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.signals.lineHasWarning' - }); - public static readonly foldedArea = AudioCue.register({ - name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), - sound: Sound.foldedArea, - settingsKey: 'audioCues.lineHasFoldedArea', - alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' - }); - public static readonly break = AudioCue.register({ - name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), - sound: Sound.break, - settingsKey: 'audioCues.lineHasBreakpoint', - alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' - }); - public static readonly inlineSuggestion = AudioCue.register({ - name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' - }); - - public static readonly terminalQuickFix = AudioCue.register({ - name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.terminalQuickFix', - alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.signals.terminalQuickFix' - }); - - public static readonly onDebugBreak = AudioCue.register({ - name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), - sound: Sound.break, - settingsKey: 'audioCues.onDebugBreak', - alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.onDebugBreak' - }); - - public static readonly noInlayHints = AudioCue.register({ - name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), - sound: Sound.error, - settingsKey: 'audioCues.noInlayHints', - alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.signals.noInlayHints' - }); - - public static readonly taskCompleted = AudioCue.register({ - name: localize('audioCues.taskCompleted', 'Task Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.taskCompleted', - alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.signals.taskCompleted' - }); - - public static readonly taskFailed = AudioCue.register({ - name: localize('audioCues.taskFailed', 'Task Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.taskFailed', - alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.signals.taskFailed' - }); - - public static readonly terminalCommandFailed = AudioCue.register({ - name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), - sound: Sound.error, - settingsKey: 'audioCues.terminalCommandFailed', - alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.signals.terminalCommandFailed' - }); - - public static readonly terminalBell = AudioCue.register({ - name: localize('audioCues.terminalBell', 'Terminal Bell'), - sound: Sound.terminalBell, - settingsKey: 'audioCues.terminalBell', - alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.signals.terminalBell' - }); - - public static readonly notebookCellCompleted = AudioCue.register({ - name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.notebookCellCompleted', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.signals.notebookCellCompleted' - }); - - public static readonly notebookCellFailed = AudioCue.register({ - name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.notebookCellFailed', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.signals.notebookCellFailed' - }); - - public static readonly diffLineInserted = AudioCue.register({ - name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), - sound: Sound.diffLineInserted, - settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.signals.diffLineInserted' - }); - - public static readonly diffLineDeleted = AudioCue.register({ - name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), - sound: Sound.diffLineDeleted, - settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.signals.diffLineDeleted' - }); - - public static readonly diffLineModified = AudioCue.register({ - name: localize('audioCues.diffLineModified', 'Diff Line Modified'), - sound: Sound.diffLineModified, - settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.signals.diffLineModified' - }); - - public static readonly chatRequestSent = AudioCue.register({ - name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), - sound: Sound.chatRequestSent, - settingsKey: 'audioCues.chatRequestSent', - alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.signals.chatRequestSent' - }); - - public static readonly chatResponseReceived = AudioCue.register({ - name: localize('audioCues.chatResponseReceived', 'Chat Response Received'), - settingsKey: 'audioCues.chatResponseReceived', - sound: { - randomOneOf: [ - Sound.chatResponseReceived1, - Sound.chatResponseReceived2, - Sound.chatResponseReceived3, - Sound.chatResponseReceived4 - ] - }, - signalSettingsKey: 'accessibility.signals.chatResponseReceived' - }); - - public static readonly chatResponsePending = AudioCue.register({ - name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), - sound: Sound.chatResponsePending, - settingsKey: 'audioCues.chatResponsePending', - alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.signals.chatResponsePending' - }); - - public static readonly clear = AudioCue.register({ - name: localize('audioCues.clear', 'Clear'), - sound: Sound.clear, - settingsKey: 'audioCues.clear', - alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.signals.clear', 'Clear'), - signalSettingsKey: 'accessibility.signals.clear' - }); - - public static readonly save = AudioCue.register({ - name: localize('audioCues.save', 'Save'), - sound: Sound.save, - settingsKey: 'audioCues.save', - alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.signals.save', 'Save'), - signalSettingsKey: 'accessibility.signals.save' - }); - - public static readonly format = AudioCue.register({ - name: localize('audioCues.format', 'Format'), - sound: Sound.format, - settingsKey: 'audioCues.format', - alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.signals.format', 'Format'), - signalSettingsKey: 'accessibility.signals.format' - }); - - private constructor( - public readonly sound: SoundSource, - public readonly name: string, - public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, - public readonly alertMessage?: string, - ) { } -} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 8a88a9e5f4a81..e4cbaff22726c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -28,7 +28,7 @@ import { INotificationService, NotificationsFilter } from 'vs/platform/notificat import { mainWindow } from 'vs/base/browser/window'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -59,7 +59,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IAudioCueService private readonly audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(themeService); @@ -383,7 +383,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente if (!notification.hasProgress) { notification.close(); } - this.audioCueService.playAudioCue(AudioCue.clear); + this.accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 5e8135b4fcce5..6f205b9d81fdf 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -21,7 +21,7 @@ import { hash } from 'vs/base/common/hash'; import { firstOrDefault } from 'vs/base/common/arrays'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -142,11 +142,11 @@ export function registerNotificationCommands(center: INotificationsCenterControl primary: KeyMod.CtrlCmd | KeyCode.Backspace }, handler: (accessor, args?) => { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification && !notification.hasProgress) { notification.close(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } }); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 3f601b3b408e2..243068f398339 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -8,12 +8,12 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPrope 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, AudioCue } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilityAlertSettingId, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ISpeechService } 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'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import { audioCueFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -97,22 +97,22 @@ const signalFeatureBase: IConfigurationPropertySchema = { additionalProperties: false, default: { audioCue: 'auto', - alert: 'auto' + announcement: 'auto' } }; -export const alertFeatureBase: IConfigurationPropertySchema = { +export const announcementFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable alert, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable alert.") + localize('audioCues.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), + localize('audioCues.enabled.off', "Disable announcement.") ], tags: ['accessibility'], }; -const defaultNoAlert: IConfigurationPropertySchema = { +const defaultNoAnnouncement: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], additionalProperties: true, @@ -169,96 +169,96 @@ const configuration: IConfigurationNode = { ...baseVerbosityProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), + 'markdownDescription': localize('announcement.save', "Indicates when a file is saved. Also see {0}.", '`#audioCues.save#`'), 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.save.userGesture', "Indicates when a file is saved via user gesture."), - localize('alert.save.always', "Indicates whenever is a file is saved, including auto save."), - localize('alert.save.never', "Never alerts.") + localize('announcement.save.userGesture', "Indicates when a file is saved via user gesture."), + localize('announcement.save.always', "Indicates whenever is a file is saved, including auto save."), + localize('announcement.save.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Clear]: { - 'markdownDescription': localize('alert.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), + 'markdownDescription': localize('announcement.clear', "Indicates when a feature is cleared (for example, the terminal, Debug Console, or Output channel). Also see {0}.", '`#audioCues.clear#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), + 'markdownDescription': localize('announcement.format', "Indicates when a file or notebook cell is formatted. Also see {0}.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'always', 'enumDescriptions': [ - localize('alert.format.userGesture', "Indicates when a file is formatted via user gesture."), - localize('alert.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), - localize('alert.format.never', "Never alerts.") + localize('announcement.format.userGesture', "Indicates when a file is formatted via user gesture."), + localize('announcement.format.always', "Indicates whenever is a file is formatted, including auto save, on cell execution, and more."), + localize('announcement.format.never', "Never alerts.") ], tags: ['accessibility'], markdownDeprecationMessage }, [AccessibilityAlertSettingId.Breakpoint]: { - 'markdownDescription': localize('alert.breakpoint', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('announcement.breakpoint', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Error]: { - 'markdownDescription': localize('alert.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), + 'markdownDescription': localize('announcement.error', "Indicates when the active line has an error. Also see {0}.", '`#audioCues.lineHasError#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.Warning]: { - 'markdownDescription': localize('alert.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), + 'markdownDescription': localize('announcement.warning', "Indicates when the active line has a warning. Also see {0}.", '`#audioCues.lineHasWarning#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.FoldedArea]: { - 'markdownDescription': localize('alert.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), + 'markdownDescription': localize('announcement.foldedArea', "Indicates when the active line has a folded area that can be unfolded. Also see {0}.", '`#audioCues.lineHasFoldedArea#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalQuickFix]: { - 'markdownDescription': localize('alert.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), + 'markdownDescription': localize('announcement.terminalQuickFix', "Indicates when there is an available terminal quick fix. Also see {0}.", '`#audioCues.terminalQuickFix#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalBell]: { - 'markdownDescription': localize('alert.terminalBell', "Indicates when the terminal bell is activated."), + 'markdownDescription': localize('announcement.terminalBell', "Indicates when the terminal bell is activated."), ...baseAlertProperty }, [AccessibilityAlertSettingId.TerminalCommandFailed]: { - 'markdownDescription': localize('alert.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), + 'markdownDescription': localize('announcement.terminalCommandFailed', "Indicates when a terminal command fails (non-zero exit code). Also see {0}.", '`#audioCues.terminalCommandFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskFailed]: { - 'markdownDescription': localize('alert.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), + 'markdownDescription': localize('announcement.taskFailed', "Indicates when a task fails (non-zero exit code). Also see {0}.", '`#audioCues.taskFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.TaskCompleted]: { - 'markdownDescription': localize('alert.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), + 'markdownDescription': localize('announcement.taskCompleted', "Indicates when a task completes successfully (zero exit code). Also see {0}.", '`#audioCues.taskCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatRequestSent]: { - 'markdownDescription': localize('alert.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), + 'markdownDescription': localize('announcement.chatRequestSent', "Indicates when a chat request is sent. Also see {0}.", '`#audioCues.chatRequestSent#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.ChatResponsePending]: { - 'markdownDescription': localize('alert.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), + 'markdownDescription': localize('announcement.chatResponsePending', "Indicates when a chat response is pending. Also see {0}.", '`#audioCues.chatResponsePending#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NoInlayHints]: { - 'markdownDescription': localize('alert.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), + 'markdownDescription': localize('announcement.noInlayHints', "Indicates when there are no inlay hints. Also see {0}.", '`#audioCues.noInlayHints#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.LineHasBreakpoint]: { - 'markdownDescription': localize('alert.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), + 'markdownDescription': localize('announcement.lineHasBreakpoint', "Indicates when on a line with a breakpoint. Also see {0}.", '`#audioCues.lineHasBreakpoint#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellCompleted]: { - 'markdownDescription': localize('alert.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), + 'markdownDescription': localize('announcement.notebookCellCompleted', "Indicates when a notebook cell completes successfully. Also see {0}.", '`#audioCues.notebookCellCompleted#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.NotebookCellFailed]: { - 'markdownDescription': localize('alert.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), + 'markdownDescription': localize('announcement.notebookCellFailed', "Indicates when a notebook cell fails. Also see {0}.", '`#audioCues.notebookCellFailed#`'), ...baseAlertProperty }, [AccessibilityAlertSettingId.OnDebugBreak]: { - 'markdownDescription': localize('alert.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), + 'markdownDescription': localize('announcement.onDebugBreak', "Indicates when the debugger breaks. Also see {0}.", '`#audioCues.onDebugBreak#`'), ...baseAlertProperty }, [AccessibilityWorkbenchSettingId.AccessibleViewCloseOnKeyPress]: { @@ -280,15 +280,15 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, }, 'accessibility.signals.lineHasInlineSuggestion': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { 'audioCue': { @@ -305,9 +305,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, @@ -321,9 +321,9 @@ const configuration: IConfigurationNode = { ...audioCueFeatureBase, default: 'off' }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -335,9 +335,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), - ...alertFeatureBase, + ...announcementFeatureBase, default: 'off' }, }, @@ -350,9 +350,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -364,9 +364,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -378,9 +378,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -392,9 +392,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -406,9 +406,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -420,9 +420,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -434,14 +434,14 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, 'accessibility.signals.diffLineInserted': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -451,7 +451,7 @@ const configuration: IConfigurationNode = { } }, 'accessibility.signals.diffLineModified': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -461,7 +461,7 @@ const configuration: IConfigurationNode = { } }, 'accessibility.signals.diffLineDeleted': { - ...defaultNoAlert, + ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { 'audioCue': { @@ -478,9 +478,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -492,9 +492,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -506,9 +506,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), - ...alertFeatureBase + ...announcementFeatureBase }, } }, @@ -520,14 +520,15 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), - ...alertFeatureBase + ...announcementFeatureBase }, }, }, 'accessibility.signals.chatResponseReceived': { - ...defaultNoAlert, + ...defaultNoAnnouncement, + additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'audioCue': { @@ -544,9 +545,9 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), ...audioCueFeatureBase }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), - ...alertFeatureBase + ...announcementFeatureBase }, }, }, @@ -567,7 +568,7 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") ], }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], @@ -581,7 +582,7 @@ const configuration: IConfigurationNode = { }, default: { 'audioCue': 'never', - 'alert': 'never' + 'announcement': 'never' } }, 'accessibility.signals.format': { @@ -601,7 +602,7 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.format.never', "Never plays the audio cue.") ], }, - 'alert': { + 'announcement': { 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], @@ -701,7 +702,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi }]); Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AudioCue.allAudioCues.map(item => ({ + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.map(item => ({ key: item.settingsKey, migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 815ac45fe1d0a..b3237138f6012 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -32,7 +32,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; export function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string { const kb = keybindingService.lookupKeybinding(commandId); @@ -104,7 +104,7 @@ export class NotificationAccessibleViewContribution extends Disposable { const accessibleViewService = accessor.get(IAccessibleViewService); const listService = accessor.get(IListService); const commandService = accessor.get(ICommandService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); function renderAccessibleView(): boolean { const notification = getNotificationFromContext(listService); @@ -165,7 +165,7 @@ export class NotificationAccessibleViewContribution extends Disposable { }, verbositySettingKey: AccessibilityVerbositySettingId.Notification, options: { type: AccessibleViewType.View }, - actions: getActionsFromNotification(notification, audioCueService) + actions: getActionsFromNotification(notification, accessibilitySignalService) }); return true; } @@ -174,7 +174,7 @@ export class NotificationAccessibleViewContribution extends Disposable { } } -function getActionsFromNotification(notification: INotificationViewItem, audioCueService: IAudioCueService): IAction[] | undefined { +function getActionsFromNotification(notification: INotificationViewItem, accessibilitySignalService: IAccessibilitySignalService): IAction[] | undefined { let actions = undefined; if (notification.actions) { actions = []; @@ -203,7 +203,7 @@ function getActionsFromNotification(notification: INotificationViewItem, audioCu actions.push({ id: 'clearNotification', label: localize('clearNotification', "Clear Notification"), tooltip: localize('clearNotification', "Clear Notification"), run: () => { notification.close(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); }, enabled: true, class: ThemeIcon.asClassName(Codicon.clearAll) }); } diff --git a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts index 32ef2425e81c2..87abfd5cb8dfd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts +++ b/src/vs/workbench/contrib/accessibility/browser/saveAudioCue.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SaveReason } from 'vs/workbench/common/editor'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -14,12 +14,12 @@ export class SaveAudioCueContribution extends Disposable implements IWorkbenchCo static readonly ID = 'workbench.contrib.saveAudioCues'; constructor( - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, ) { super(); this._register(this._workingCopyService.onDidSave((e) => { - this._audioCueService.playAudioCue(AudioCue.save, { userGesture: e.reason === SaveReason.EXPLICIT }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.save, { userGesture: e.reason === SaveReason.EXPLICIT }); })); } } diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts similarity index 90% rename from src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index 9059db0b31fb8..4f916fb4d5c7e 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShowAccessibilityAlertHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/audioCues/browser/commands'; +import { ShowAccessibilityAnnouncementHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -11,14 +11,14 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IAudioCueService, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { AudioCueLineDebuggerContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution'; -import { AudioCueLineFeatureContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution'; +import { IAccessibilitySignalService, AccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { AccessibilitySignalLineDebuggerContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution'; +import { SignalLineFeatureContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution'; -registerSingleton(IAudioCueService, AudioCueService, InstantiationType.Delayed); +registerSingleton(IAccessibilitySignalService, AccessibilitySignalService, InstantiationType.Delayed); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineFeatureContribution, LifecyclePhase.Restored); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueLineDebuggerContribution, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SignalLineFeatureContribution, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilitySignalLineDebuggerContribution, LifecyclePhase.Restored); export const audioCueFeatureBase: IConfigurationPropertySchema = { 'type': 'string', @@ -178,5 +178,5 @@ Registry.as(ConfigurationExtensions.Configuration).regis }); registerAction2(ShowAudioCueHelp); -registerAction2(ShowAccessibilityAlertHelp); +registerAction2(ShowAccessibilityAnnouncementHelp); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts similarity index 77% rename from src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts index 6150308ccfa19..45d5f2b312120 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalDebuggerContribution.ts @@ -5,23 +5,23 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, observableFromEvent } from 'vs/base/common/observable'; -import { IAudioCueService, AudioCue, AudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService, AccessibilitySignal, AccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; -export class AudioCueLineDebuggerContribution +export class AccessibilitySignalLineDebuggerContribution extends Disposable implements IWorkbenchContribution { constructor( @IDebugService debugService: IDebugService, - @IAudioCueService private readonly audioCueService: AudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: AccessibilitySignalService, ) { super(); const isEnabled = observableFromEvent( - audioCueService.onEnabledChanged(AudioCue.onDebugBreak), - () => audioCueService.isCueEnabled(AudioCue.onDebugBreak) + accessibilitySignalService.onSoundEnabledChanged(AccessibilitySignal.onDebugBreak), + () => accessibilitySignalService.isSoundEnabled(AccessibilitySignal.onDebugBreak) ); this._register(autorunWithStore((reader, store) => { /** @description subscribe to debug sessions */ @@ -60,7 +60,7 @@ export class AudioCueLineDebuggerContribution const stoppedDetails = session.getStoppedDetails(); const BREAKPOINT_STOP_REASON = 'breakpoint'; if (stoppedDetails && stoppedDetails.reason === BREAKPOINT_STOP_REASON) { - this.audioCueService.playAudioCue(AudioCue.onDebugBreak); + this.accessibilitySignalService.playSignal(AccessibilitySignal.onDebugBreak); } }); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts similarity index 77% rename from src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts index e7bba6b3f35b2..bff29815a4044 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignalLineFeatureContribution.ts @@ -12,7 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { ITextModel } from 'vs/editor/common/model'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; @@ -20,39 +20,39 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class AudioCueLineFeatureContribution +export class SignalLineFeatureContribution extends Disposable implements IWorkbenchContribution { private readonly store = this._register(new DisposableStore()); private readonly features: LineFeature[] = [ - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.error, MarkerSeverity.Error), - this.instantiationService.createInstance(MarkerLineFeature, AudioCue.warning, MarkerSeverity.Warning), + this.instantiationService.createInstance(MarkerLineFeature, AccessibilitySignal.error, MarkerSeverity.Error), + this.instantiationService.createInstance(MarkerLineFeature, AccessibilitySignal.warning, MarkerSeverity.Warning), this.instantiationService.createInstance(FoldedAreaLineFeature), this.instantiationService.createInstance(BreakpointLineFeature), ]; - private readonly isEnabledCache = new CachedFunction>((cue) => observableFromEvent( - this.audioCueService.onEnabledChanged(cue), - () => this.audioCueService.isCueEnabled(cue) + private readonly isSoundEnabledCache = new CachedFunction>((cue) => observableFromEvent( + this.accessibilitySignalService.onSoundEnabledChanged(cue), + () => this.accessibilitySignalService.isSoundEnabled(cue) )); - private readonly isAlertEnabledCache = new CachedFunction>((cue) => observableFromEvent( - this.audioCueService.onAlertEnabledChanged(cue), - () => this.audioCueService.isAlertEnabled(cue) + private readonly isAnnouncmentEnabledCahce = new CachedFunction>((cue) => observableFromEvent( + this.accessibilitySignalService.onAnnouncementEnabledChanged(cue), + () => this.accessibilitySignalService.isAnnouncementEnabled(cue) )); constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IAudioCueService private readonly audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); - const someAudioCueFeatureIsEnabled = derived( - (reader) => /** @description someAudioCueFeatureIsEnabled */ this.features.some((feature) => - this.isEnabledCache.get(feature.audioCue).read(reader) || this.isAlertEnabledCache.get(feature.audioCue).read(reader) + const someAccessibilitySignalIsEnabled = derived( + (reader) => /** @description someAccessibilitySignalFeatureIsEnabled */ this.features.some((feature) => + this.isSoundEnabledCache.get(feature.signal).read(reader) || this.isAnnouncmentEnabledCahce.get(feature.signal).read(reader) ) ); @@ -74,22 +74,22 @@ export class AudioCueLineFeatureContribution this._register( autorun(reader => { - /** @description updateAudioCuesEnabled */ + /** @description updateSignalsEnabled */ this.store.clear(); - if (!someAudioCueFeatureIsEnabled.read(reader)) { + if (!someAccessibilitySignalIsEnabled.read(reader)) { return; } const activeEditor = activeEditorObservable.read(reader); if (activeEditor) { - this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store); + this.registerAccessibilitySignalsForEditor(activeEditor.editor, activeEditor.model, this.store); } }) ); } - private registerAudioCuesForEditor( + private registerAccessibilitySignalsForEditor( editor: ICodeEditor, editorModel: ITextModel, store: DisposableStore @@ -109,7 +109,7 @@ export class AudioCueLineFeatureContribution return editor.getPosition(); } ); - const debouncedPosition = debouncedObservable(curPosition, this._configurationService.getValue('audioCues.debouncePositionChanges') ? 300 : 0, store); + const debouncedPosition = debouncedObservable(curPosition, this._configurationService.getValue('accessibility.signals.debouncePositionChanges') ? 300 : 0, store); const isTyping = wasEventTriggeredRecently( editorModel.onDidChangeContent.bind(editorModel), 1000, @@ -119,9 +119,9 @@ export class AudioCueLineFeatureContribution const featureStates = this.features.map((feature) => { const lineFeatureState = feature.getObservableState(editor, editorModel); const isFeaturePresent = derivedOpts( - { debugName: `isPresentInLine:${feature.audioCue.name}` }, + { debugName: `isPresentInLine:${feature.signal.name}` }, (reader) => { - if (!this.isEnabledCache.get(feature.audioCue).read(reader) && !this.isAlertEnabledCache.get(feature.audioCue).read(reader)) { + if (!this.isSoundEnabledCache.get(feature.signal).read(reader) && !this.isAnnouncmentEnabledCahce.get(feature.signal).read(reader)) { return false; } const position = debouncedPosition.read(reader); @@ -132,7 +132,7 @@ export class AudioCueLineFeatureContribution } ); return derivedOpts( - { debugName: `typingDebouncedFeatureState:\n${feature.audioCue.name}` }, + { debugName: `typingDebouncedFeatureState:\n${feature.signal.name}` }, (reader) => feature.debounceWhileTyping && isTyping.read(reader) ? (debouncedPosition.read(reader), isFeaturePresent.get()) @@ -154,21 +154,21 @@ export class AudioCueLineFeatureContribution store.add( autorunDelta(state, ({ lastValue, newValue }) => { - /** @description Play Audio Cue */ + /** @description Play Accessibility Signal */ const newFeatures = this.features.filter( feature => newValue?.featureStates.get(feature) && (!lastValue?.featureStates?.get(feature) || newValue.lineNumber !== lastValue.lineNumber) ); - this.audioCueService.playAudioCues(newFeatures.map(f => f.audioCue)); + this.accessibilitySignalService.playAccessibilitySignals(newFeatures.map(f => f.signal)); }) ); } } interface LineFeature { - audioCue: AudioCue; + signal: AccessibilitySignal; debounceWhileTyping?: boolean; getObservableState( editor: ICodeEditor, @@ -184,7 +184,7 @@ class MarkerLineFeature implements LineFeature { public readonly debounceWhileTyping = true; private _previousLine: number = 0; constructor( - public readonly audioCue: AudioCue, + public readonly signal: AccessibilitySignal, private readonly severity: MarkerSeverity, @IMarkerService private readonly markerService: IMarkerService, @@ -214,7 +214,7 @@ class MarkerLineFeature implements LineFeature { } class FoldedAreaLineFeature implements LineFeature { - public readonly audioCue = AudioCue.foldedArea; + public readonly signal = AccessibilitySignal.foldedArea; getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { const foldingController = FoldingController.get(editor); @@ -241,7 +241,7 @@ class FoldedAreaLineFeature implements LineFeature { } class BreakpointLineFeature implements LineFeature { - public readonly audioCue = AudioCue.break; + public readonly signal = AccessibilitySignal.break; constructor(@IDebugService private readonly debugService: IDebugService) { } diff --git a/src/vs/workbench/contrib/audioCues/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts similarity index 55% rename from src/vs/workbench/contrib/audioCues/browser/commands.ts rename to src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 624c67985db52..1236f98f57a80 100644 --- a/src/vs/workbench/contrib/audioCues/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -8,7 +8,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { localize, localize2 } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { Action2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -25,42 +25,42 @@ export class ShowAudioCueHelp extends Action2 { } override async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); - const userGestureCues = [AudioCue.save, AudioCue.format]; - const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.map((cue, idx) => ({ - label: userGestureCues.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.audioCue')})` : cue.name, - audioCue: cue, - buttons: userGestureCues.includes(cue) ? [{ + const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; + const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.sound')})` : signal.name, + audioCue: signal, + buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('audioCues.help.settings', 'Enable/Disable Audio Cue'), + tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => audioCueService.isCueEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.audioCue)); qp.onDidAccept(() => { const enabledCues = qp.selectedItems.map(i => i.audioCue); - const disabledCues = AudioCue.allAudioCues.filter(cue => !enabledCues.includes(cue)); + const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { - if (!userGestureCues.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); + if (!userGestureSignals.includes(cue)) { + let { audioCue, announcement } = configurationService.getValue<{ audioCue: string; announcement?: string }>(cue.signalSettingsKey); audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + if (announcement) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); } else { configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } } } for (const cue of disabledCues) { - const alert = cue.alertMessage ? configurationService.getValue(cue.signalSettingsKey + '.alert') : undefined; - const audioCue = userGestureCues.includes(cue) ? 'never' : 'off'; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + const announcement = cue.announcementMessage ? configurationService.getValue(cue.signalSettingsKey + '.announcement') : undefined; + const audioCue = userGestureSignals.includes(cue) ? 'never' : 'off'; + if (announcement) { + configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); } else { configurationService.updateValue(cue.signalSettingsKey, { audioCue }); } @@ -68,7 +68,7 @@ export class ShowAudioCueHelp extends Action2 { qp.hide(); }); qp.onDidChangeActive(() => { - audioCueService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); + accessibilitySignalService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); }); qp.placeholder = localize('audioCues.help.placeholder', 'Select an audio cue to play and configure'); qp.canSelectMany = true; @@ -76,49 +76,49 @@ export class ShowAudioCueHelp extends Action2 { } } -export class ShowAccessibilityAlertHelp extends Action2 { - static readonly ID = 'accessibility.alert.help'; +export class ShowAccessibilityAnnouncementHelp extends Action2 { + static readonly ID = 'accessibility.announcement.help'; constructor() { super({ - id: ShowAccessibilityAlertHelp.ID, - title: localize2('accessibility.alert.help', "Help: List Alerts"), + id: ShowAccessibilityAnnouncementHelp.ID, + title: localize2('accessibility.announcement.help', "Help: List Announcements"), f1: true, }); } override async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); - const userGestureAlerts = [AudioCue.save, AudioCue.format]; - const items: (IQuickPickItem & { audioCue: AudioCue })[] = AudioCue.allAudioCues.filter(c => c.alertSettingsKey).map((cue, idx) => ({ - label: userGestureAlerts.includes(cue) ? `${cue.name} (${configurationService.getValue(cue.signalSettingsKey + '.alert')})` : cue.name, - audioCue: cue, - buttons: userGestureAlerts.includes(cue) ? [{ + const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; + const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.announcement')})` : signal.name, + audioCue: signal, + buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('alert.help.settings', 'Enable/Disable Alert'), + tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => audioCueService.isAlertEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.audioCue)); qp.onDidAccept(() => { const enabledAlerts = qp.selectedItems.map(i => i.audioCue); - const disabledAlerts = AudioCue.allAudioCues.filter(cue => !enabledAlerts.includes(cue)); + const disabledAlerts = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAlerts.includes(cue)); for (const cue of enabledAlerts) { - if (!userGestureAlerts.includes(cue)) { + if (!userGestureSignals.includes(cue)) { let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); - alert = cue.alertMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + alert = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; if (alert) { configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } } } for (const cue of disabledAlerts) { - const alert = userGestureAlerts.includes(cue) ? 'never' : 'off'; + const alert = userGestureSignals.includes(cue) ? 'never' : 'off'; const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index c3c1f31f1f1a4..2736da158c57f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -8,7 +8,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; @@ -117,5 +117,5 @@ export function getNewChatAction(viewId: string, providerId: string) { } function announceChatCleared(accessor: ServicesAccessor): void { - accessor.get(IAudioCueService).playAudioCue(AudioCue.clear); + accessor.get(IAccessibilitySignalService).playSignal(AccessibilitySignal.clear); } diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index fa522294c2635..b6c66797247fb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -6,7 +6,7 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -19,12 +19,12 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _requestId: number = 0; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { + constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } acceptRequest(): number { this._requestId++; - this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.chatRequestSent, { allowManyInParallel: true }); this._pendingCueMap.set(this._requestId, this._instantiationService.createInstance(AudioCueScheduler)); return this._requestId; } @@ -32,7 +32,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi this._pendingCueMap.deleteAndDispose(requestId); const isPanelChat = typeof response !== 'string'; const responseContent = typeof response === 'string' ? response : response?.response.asString(); - this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, { allowManyInParallel: true }); + this._accessibilitySignalService.playSignal(AccessibilitySignal.chatResponseReceived, { allowManyInParallel: true }); if (!response) { return; } @@ -49,10 +49,10 @@ const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; class AudioCueScheduler extends Disposable { private _scheduler: RunOnceScheduler; private _audioCueLoop: IDisposable | undefined; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); this._scheduler = new RunOnceScheduler(() => { - this._audioCueLoop = this._audioCueService.playAudioCueLoop(AudioCue.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); + this._audioCueLoop = this._accessibilitySignalService.playSignalLoop(AccessibilitySignal.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); }, CHAT_RESPONSE_PENDING_ALLOWANCE_MS); this._scheduler.schedule(); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index eaa382ce67d0a..4eef489c49f65 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -70,7 +70,7 @@ import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const $ = dom.$; @@ -989,9 +989,9 @@ registerAction2(class extends ViewAction { } runInView(_accessor: ServicesAccessor, view: Repl): void { - const audioCueService = _accessor.get(IAudioCueService); + const accessibilitySignalService = _accessor.get(IAccessibilitySignalService); view.clearRepl(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } }); diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 27594f7cd7c51..86fbc23d97833 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -23,7 +23,7 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestContextService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; suite('EditorAutoSave', () => { @@ -43,7 +43,7 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 16e3d189f0050..bd8db57491b3f 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -15,7 +15,7 @@ import { InlayHintItem, asCommandLink } from 'vs/editor/contrib/inlayHints/brows import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController'; import { localize, localize2 } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -40,7 +40,7 @@ export class InlayHintsAccessibility implements IEditorContribution { constructor( private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IInstantiationService private readonly _instaService: IInstantiationService, ) { this._ariaElement = document.createElement('span'); @@ -156,7 +156,7 @@ export class InlayHintsAccessibility implements IEditorContribution { const line = this._editor.getPosition().lineNumber; const hints = InlayHintsController.get(this._editor)?.getInlayHintsForLine(line); if (!hints || hints.length === 0) { - this._audioCueService.playAudioCue(AudioCue.noInlayHints); + this._accessibilitySignalService.playSignal(AccessibilitySignal.noInlayHints); } else { this._read(line, hints); } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts index a5736966b8a17..dbc9e88e9a41f 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookExecutionStateServiceImpl.ts @@ -9,7 +9,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -38,7 +38,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, - @IAudioCueService private readonly _audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService ) { super(); } @@ -112,11 +112,11 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (lastRunSuccess !== undefined) { if (lastRunSuccess) { if (this._executions.size === 0) { - this._audioCueService.playAudioCue(AudioCue.notebookCellCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.notebookCellCompleted); } this._clearLastFailedCell(notebookUri); } else { - this._audioCueService.playAudioCue(AudioCue.notebookCellFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.notebookCellFailed); this._setLastFailedCell(notebookUri, cellHandle); } } diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 5d31bf6585046..794a8e60d5710 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -29,7 +29,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; 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 { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -224,11 +224,11 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { } async run(accessor: ServicesAccessor): Promise { const outputService = accessor.get(IOutputService); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); - audioCueService.playAudioCue(AudioCue.clear); + accessibilitySignalService.playSignal(AccessibilitySignal.clear); } } })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 54ac57ce22444..cf33ec2516343 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -222,9 +222,9 @@ export const tocData: ITOCEntry = { settings: ['notebook.*', 'interactiveWindow.*'] }, { - id: 'features/audioCues', - label: localize('audioCues', 'Audio Cues'), - settings: ['audioCues.*'] + id: 'features/signals', + label: localize('signals', 'Signals'), + settings: ['signals.*'] }, { id: 'features/accessibilitySignals', diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index e99121e3d6d51..f9963ff03406c 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -54,7 +54,7 @@ import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { Color } from 'vs/base/common/color'; import { ResourceMap } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickDiffService, QuickDiff } from 'vs/workbench/contrib/scm/common/quickDiff'; import { IQuickDiffSelectItem, SwitchQuickDiffBaseAction, SwitchQuickDiffViewItem } from 'vs/workbench/contrib/scm/browser/dirtyDiffSwitcher'; @@ -578,7 +578,7 @@ export class GotoPreviousChangeAction extends EditorAction { async run(accessor: ServicesAccessor): Promise { const outerEditor = getOuterEditorFromDiffEditor(accessor); - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const accessibilityService = accessor.get(IAccessibilityService); const codeEditorService = accessor.get(ICodeEditorService); @@ -600,7 +600,7 @@ export class GotoPreviousChangeAction extends EditorAction { const index = model.findPreviousClosestChange(lineNumber, false); const change = model.changes[index]; - await playAudioCueForChange(change.change, audioCueService); + await playAudioCueForChange(change.change, accessibilitySignalService); setPositionAndSelection(change.change, outerEditor, accessibilityService, codeEditorService); } } @@ -619,7 +619,7 @@ export class GotoNextChangeAction extends EditorAction { } async run(accessor: ServicesAccessor): Promise { - const audioCueService = accessor.get(IAudioCueService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const outerEditor = getOuterEditorFromDiffEditor(accessor); const accessibilityService = accessor.get(IAccessibilityService); const codeEditorService = accessor.get(ICodeEditorService); @@ -643,7 +643,7 @@ export class GotoNextChangeAction extends EditorAction { const index = model.findNextClosestChange(lineNumber, false); const change = model.changes[index].change; - await playAudioCueForChange(change, audioCueService); + await playAudioCueForChange(change, accessibilitySignalService); setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService); } } @@ -658,17 +658,17 @@ function setPositionAndSelection(change: IChange, editor: ICodeEditor, accessibi } } -async function playAudioCueForChange(change: IChange, audioCueService: IAudioCueService) { +async function playAudioCueForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { const changeType = getChangeType(change); switch (changeType) { case ChangeType.Add: - audioCueService.playAudioCue(AudioCue.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineInserted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; case ChangeType.Delete: - audioCueService.playAudioCue(AudioCue.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineDeleted, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; case ChangeType.Modify: - audioCueService.playAudioCue(AudioCue.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); + accessibilitySignalService.playSignal(AccessibilitySignal.diffLineModified, { allowManyInParallel: true, source: 'dirtyDiffDecoration' }); break; } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index fc89084618298..654ac8d1ed2c9 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -80,7 +80,7 @@ import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/s import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const $ = dom.$; @@ -192,7 +192,7 @@ export class SearchView extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @INotebookService private readonly notebookService: INotebookService, @ILogService private readonly logService: ILogService, - @IAudioCueService private readonly audioCueService: IAudioCueService + @IAccessibilitySignalService private readonly accessibilitySignalService: IAccessibilitySignalService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -1279,7 +1279,7 @@ export class SearchView extends ViewPane { this.viewModel.cancelSearch(); this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search"); - this.audioCueService.playAudioCue(AudioCue.clear); + this.accessibilitySignalService.playSignal(AccessibilitySignal.clear); this.reLayout(); } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index df71476712482..12767f599ae08 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -14,7 +14,7 @@ import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/termina import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { IMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/common/terminal'; interface ITerminalData { @@ -40,7 +40,7 @@ const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID export class TaskTerminalStatus extends Disposable { private terminalMap: Map = new Map(); private _marker: IMarker | undefined; - constructor(@ITaskService taskService: ITaskService, @IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@ITaskService taskService: ITaskService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); this._register(taskService.onDidStateChange((event) => { switch (event.kind) { @@ -95,7 +95,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskCompleted); if (terminalData.task.configurationProperties.isBackground) { for (const status of terminalData.terminal.statusList.statuses) { terminalData.terminal.statusList.remove(status); @@ -104,7 +104,7 @@ export class TaskTerminalStatus extends Disposable { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); } } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskFailed); terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -120,10 +120,10 @@ export class TaskTerminalStatus extends Disposable { } terminalData.terminal.statusList.remove(terminalData.status); if (terminalData.problemMatcher.numberOfMatches === 0) { - this._audioCueService.playAudioCue(AudioCue.taskCompleted); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskCompleted); terminalData.terminal.statusList.add(SUCCEEDED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { - this._audioCueService.playAudioCue(AudioCue.taskFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.taskFailed); terminalData.terminal.statusList.add(FAILED_INACTIVE_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_INACTIVE_TASK_STATUS); diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts index 5eeee32c2642c..9efbc2ac32da1 100644 --- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts @@ -47,7 +47,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; interface IWorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -92,7 +92,7 @@ export class TaskService extends AbstractTaskService { @IThemeService themeService: IThemeService, @IInstantiationService instantiationService: IInstantiationService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IAudioCueService audioCueService: IAudioCueService + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService ) { super(configurationService, markerService, diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index 5e413afc9c1a9..c3bf26252fda5 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -7,7 +7,7 @@ import { ok } from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ACTIVE_TASK_STATUS, FAILED_TASK_STATUS, SUCCEEDED_TASK_STATUS, TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; @@ -28,8 +28,8 @@ class TestTaskService implements Partial { } } -class TestAudioCueService implements Partial { - async playAudioCue(cue: AudioCue): Promise { +class TestaccessibilitySignalService implements Partial { + async playSignal(cue: AccessibilitySignal): Promise { return; } } @@ -74,13 +74,13 @@ suite('Task Terminal Status', () => { let testTerminal: ITerminalInstance; let testTask: Task; let problemCollector: AbstractProblemCollector; - let audioCueService: TestAudioCueService; + let accessibilitySignalService: TestaccessibilitySignalService; const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { instantiationService = store.add(new TestInstantiationService()); taskService = new TestTaskService(); - audioCueService = new TestAudioCueService(); - taskTerminalStatus = store.add(new TaskTerminalStatus(taskService as any, audioCueService as any)); + accessibilitySignalService = new TestaccessibilitySignalService(); + taskTerminalStatus = store.add(new TaskTerminalStatus(taskService as any, accessibilitySignalService as any)); testTerminal = store.add(instantiationService.createInstance(TestTerminal) as any); testTask = instantiationService.createInstance(TestTask) as unknown as Task; problemCollector = store.add(instantiationService.createInstance(TestProblemCollector) as any); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9bd13171c8fe0..e46e771f2a898 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -26,7 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; import * as nls from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -363,7 +363,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IOpenerService private readonly _openerService: IOpenerService, @ICommandService private readonly _commandService: ICommandService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); @@ -761,7 +761,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { tooltip: nls.localize('bellStatus', "Bell") }, this._configHelper.config.bellDuration); } - this._audioCueService.playAudioCue(AudioCue.terminalBell); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalBell); })); }, 1000, this._store); this._register(xterm.raw.onSelectionChange(async () => this._onSelectionChange())); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index da0a7fc9d0c8b..9b025bd50b6db 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -52,7 +52,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @ILifecycleService lifecycleService: ILifecycleService, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService instantiationService: IInstantiationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @INotificationService private readonly _notificationService: INotificationService ) { super(); @@ -219,7 +219,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { commandDetectionListeners.push(capability.onCommandFinished(command => { this.registerCommandDecoration(command); if (command.exitCode) { - this._audioCueService.playAudioCue(AudioCue.terminalCommandFailed); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalCommandFailed); } })); // Command invalidated diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 6f9028565c584..0f2ef8f01f4cb 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -43,7 +43,7 @@ import { debounce } from 'vs/base/common/decorators'; import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const enum RenderConstants { /** @@ -205,7 +205,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach @ITelemetryService private readonly _telemetryService: ITelemetryService, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @ILayoutService layoutService: ILayoutService ) { super(); @@ -584,7 +584,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach // the prompt being written this._capabilities.get(TerminalCapability.CommandDetection)?.handlePromptStart(); this._capabilities.get(TerminalCapability.CommandDetection)?.handleCommandStart(); - this._audioCueService.playAudioCue(AudioCue.clear); + this._accessibilitySignalService.playSignal(AccessibilitySignal.clear); } hasSelection(): boolean { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 833db09d807b7..51fb47c8c6eb7 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -30,7 +30,7 @@ import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecy import { TestLayoutService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestLoggerService } from 'vs/workbench/test/common/workbenchTestServices'; import type { Terminal } from '@xterm/xterm'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; const defaultTerminalConfig: Partial = { fontFamily: 'monospace', @@ -67,7 +67,7 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(ILayoutService, new TestLayoutService()); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index ea488b6fe0e9f..21c627770546a 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -20,7 +20,7 @@ import type { IDecoration, Terminal } from '@xterm/xterm'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; import { getLinesForCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -77,7 +77,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, @ITerminalQuickFixService private readonly _quickFixService: ITerminalQuickFixService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IExtensionService private readonly _extensionService: IExtensionService, @@ -284,7 +284,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, e.classList.add(...ThemeIcon.asClassNameArray(isExplainOnly ? Codicon.sparkle : Codicon.lightBulb)); updateLayout(this._configurationService, e); - this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); + this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalQuickFix); const parentElement = (e.closest('.xterm') as HTMLElement).parentElement; if (!parentElement) { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d9b0f8df7663c..be911f2f4c5e7 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -170,7 +170,7 @@ import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; import { EditorPaneService } from 'vs/workbench/services/editor/browser/editorPaneService'; @@ -282,7 +282,7 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); - instantiationService.stub(IAudioCueService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(ILanguageFeaturesService, new LanguageFeaturesService()); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 6aa1bf45c0d8d..b83c1fcd8949a 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -369,7 +369,7 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; import 'vs/workbench/contrib/list/browser/list.contribution'; // Audio Cues -import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; // Deprecated Extension Migrator import 'vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution'; From 2f6b7e163c93b73564bb3f413b3f39e1d0028d0d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:40:02 +0100 Subject: [PATCH 0171/1863] Git - Update showProgress value in DiffOperation (#205012) --- extensions/git/src/operation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index ead345c0b0621..223f1945b0214 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -146,7 +146,7 @@ export const Operation = { DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, - Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as DiffOperation, + Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as DiffOperation, Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, GetBranch: { kind: OperationKind.GetBranch, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchOperation, From c7648aaf427f31455d03c7021a8f963d8ec248cd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:45:44 -0600 Subject: [PATCH 0172/1863] replace most instances of audio cues --- src/vs/editor/common/standaloneStrings.ts | 4 +- .../browser/accessibilitySignalService.ts | 74 +++++++++---------- .../browser/accessibilityConfiguration.ts | 21 +++++- .../browser/editorAccessibilityHelp.ts | 2 +- .../accessibilitySignal.contribution.ts | 10 +-- .../accessibilitySignals/browser/commands.ts | 10 +-- .../browser/actions/chatAccessibilityHelp.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 4 +- src/vs/workbench/workbench.common.main.ts | 2 +- 9 files changed, 71 insertions(+), 58 deletions(-) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index c6472a818999d..1bcfecfeb9793 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -25,8 +25,8 @@ export namespace AccessibilityHelpNLS { export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior {0}."); export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding."); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); - export const listAudioCues = nls.localize("listAudioCuesCommand", "Run the command: List Audio Cues for an overview of all audio cues and their current status."); - export const listAlerts = nls.localize("listAlertsCommand", "Run the command: List Alerts for an overview of alerts and their current status."); + export const listSignalSounds = nls.localize("listSignalSoundsCommand", "Run the command: List Signal Sounds for an overview of all sounds and their current status."); + export const listAlerts = nls.localize("listAnnouncementsCommand", "Run the command: List Signal Announcements for an overview of announcements and their current status."); export const quickChat = nls.localize("quickChatCommand", "Toggle quick chat ({0}) to open or close a chat session."); export const quickChatNoKb = nls.localize("quickChatCommandNoKb", "Toggle quick chat is not currently triggerable by a keybinding."); export const startInlineChat = nls.localize("startInlineChatCommand", "Start inline chat ({0}) to create an in editor chat session."); diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 9bbbf0b1db984..503a178b46d61 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -63,14 +63,14 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi } if (this.isSoundEnabled(signal, options.userGesture)) { - this.sendAudioCueTelemetry(signal, options.source); + this.sendSignalTelemetry(signal, options.source); await this.playSound(signal.sound.getSound(), options.allowManyInParallel); } } public async playAccessibilitySignals(cues: (AccessibilitySignal | { cue: AccessibilitySignal; source: string })[]): Promise { for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); + this.sendSignalTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); } const cueArray = cues.map(c => 'cue' in c ? c.cue : c); const alerts = cueArray.filter(cue => this.isAnnouncementEnabled(cue)).map(c => c.announcementMessage); @@ -78,14 +78,14 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi this.accessibilityService.status(alerts.join(', ')); } - // Some audio cues might reuse sounds. Don't play the same sound twice. + // Some sounds are reused. Don't play the same sound twice. const sounds = new Set(cueArray.filter(cue => this.isSoundEnabled(cue)).map(cue => cue.sound.getSound())); await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); } - private sendAudioCueTelemetry(cue: AccessibilitySignal, source: string | undefined): void { + private sendSignalTelemetry(cue: AccessibilitySignal, source: string | undefined): void { const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); // Only send once per user session @@ -95,19 +95,19 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi this.sentTelemetry.add(key); this.telemetryService.publicLog2<{ - audioCue: string; + signal: string; source: string; isScreenReaderOptimized: boolean; }, { owner: 'hediet'; - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; + signal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The signal that was played.' }; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the signal (e.g. "diffEditorNavigation").' }; isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, + comment: 'This data is collected to understand how signals are used and if more signals should be added.'; + }>('signal.played', { + signal: cue.name, source: source ?? '', isScreenReaderOptimized, }); @@ -183,7 +183,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') ); return derived(reader => { - /** @description audio cue enabled */ + /** @description sound enabled */ const setting = settingObservable.read(reader); if ( setting === 'on' || @@ -363,17 +363,17 @@ export class AccessibilitySignal { sound: Sound | { /** * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. + * for an improved experience. This option enables playing a random sound. */ randomOneOf: Sound[]; }; - legacyAudioCueSettingsKey: string; + legacySoundSettingsKey: string; settingsKey: string; legacyAnnouncementSettingsKey?: AccessibilityAlertSettingId; announcementMessage?: string; }): AccessibilitySignal { const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const signal = new AccessibilitySignal(soundSource, options.name, options.legacyAudioCueSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); + const signal = new AccessibilitySignal(soundSource, options.name, options.legacySoundSettingsKey, options.settingsKey, options.legacyAnnouncementSettingsKey, options.announcementMessage); AccessibilitySignal._signals.add(signal); return signal; } @@ -385,7 +385,7 @@ export class AccessibilitySignal { public static readonly error = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasError', + legacySoundSettingsKey: 'accessibilitySignals.lineHasError', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), settingsKey: 'accessibility.signals.lineHasError' @@ -393,7 +393,7 @@ export class AccessibilitySignal { public static readonly warning = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasWarning', + legacySoundSettingsKey: 'accessibilitySignals.lineHasWarning', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), settingsKey: 'accessibility.signals.lineHasWarning' @@ -401,7 +401,7 @@ export class AccessibilitySignal { public static readonly foldedArea = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacySoundSettingsKey: 'accessibilitySignals.lineHasFoldedArea', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), settingsKey: 'accessibility.signals.lineHasFoldedArea' @@ -409,7 +409,7 @@ export class AccessibilitySignal { public static readonly break = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacySoundSettingsKey: 'accessibilitySignals.lineHasBreakpoint', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), settingsKey: 'accessibility.signals.lineHasBreakpoint' @@ -417,14 +417,14 @@ export class AccessibilitySignal { public static readonly inlineSuggestion = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, - legacyAudioCueSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + legacySoundSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', settingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacySoundSettingsKey: 'accessibilitySignals.terminalQuickFix', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), settingsKey: 'accessibility.signals.terminalQuickFix' @@ -433,7 +433,7 @@ export class AccessibilitySignal { public static readonly onDebugBreak = AccessibilitySignal.register({ name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, - legacyAudioCueSettingsKey: 'accessibilitySignals.onDebugBreak', + legacySoundSettingsKey: 'accessibilitySignals.onDebugBreak', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), settingsKey: 'accessibility.signals.onDebugBreak' @@ -442,7 +442,7 @@ export class AccessibilitySignal { public static readonly noInlayHints = AccessibilitySignal.register({ name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.noInlayHints', + legacySoundSettingsKey: 'accessibilitySignals.noInlayHints', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), settingsKey: 'accessibility.signals.noInlayHints' @@ -451,7 +451,7 @@ export class AccessibilitySignal { public static readonly taskCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.taskCompleted', + legacySoundSettingsKey: 'accessibilitySignals.taskCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), settingsKey: 'accessibility.signals.taskCompleted' @@ -460,7 +460,7 @@ export class AccessibilitySignal { public static readonly taskFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskFailed', 'Task Failed'), sound: Sound.taskFailed, - legacyAudioCueSettingsKey: 'accessibilitySignals.taskFailed', + legacySoundSettingsKey: 'accessibilitySignals.taskFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), settingsKey: 'accessibility.signals.taskFailed' @@ -469,7 +469,7 @@ export class AccessibilitySignal { public static readonly terminalCommandFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), sound: Sound.error, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacySoundSettingsKey: 'accessibilitySignals.terminalCommandFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), settingsKey: 'accessibility.signals.terminalCommandFailed' @@ -478,7 +478,7 @@ export class AccessibilitySignal { public static readonly terminalBell = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, - legacyAudioCueSettingsKey: 'accessibilitySignals.terminalBell', + legacySoundSettingsKey: 'accessibilitySignals.terminalBell', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), settingsKey: 'accessibility.signals.terminalBell' @@ -487,7 +487,7 @@ export class AccessibilitySignal { public static readonly notebookCellCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacySoundSettingsKey: 'accessibilitySignals.notebookCellCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), settingsKey: 'accessibility.signals.notebookCellCompleted' @@ -496,7 +496,7 @@ export class AccessibilitySignal { public static readonly notebookCellFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, - legacyAudioCueSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacySoundSettingsKey: 'accessibilitySignals.notebookCellFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), settingsKey: 'accessibility.signals.notebookCellFailed' @@ -505,28 +505,28 @@ export class AccessibilitySignal { public static readonly diffLineInserted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineInserted', + legacySoundSettingsKey: 'accessibilitySignals.diffLineInserted', settingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineDeleted', + legacySoundSettingsKey: 'accessibilitySignals.diffLineDeleted', settingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, - legacyAudioCueSettingsKey: 'accessibilitySignals.diffLineModified', + legacySoundSettingsKey: 'accessibilitySignals.diffLineModified', settingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), sound: Sound.chatRequestSent, - legacyAudioCueSettingsKey: 'accessibilitySignals.chatRequestSent', + legacySoundSettingsKey: 'accessibilitySignals.chatRequestSent', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), settingsKey: 'accessibility.signals.chatRequestSent' @@ -534,7 +534,7 @@ export class AccessibilitySignal { public static readonly chatResponseReceived = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), - legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponseReceived', + legacySoundSettingsKey: 'accessibilitySignals.chatResponseReceived', sound: { randomOneOf: [ Sound.chatResponseReceived1, @@ -549,7 +549,7 @@ export class AccessibilitySignal { public static readonly chatResponsePending = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), sound: Sound.chatResponsePending, - legacyAudioCueSettingsKey: 'accessibilitySignals.chatResponsePending', + legacySoundSettingsKey: 'accessibilitySignals.chatResponsePending', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), settingsKey: 'accessibility.signals.chatResponsePending' @@ -558,7 +558,7 @@ export class AccessibilitySignal { public static readonly clear = AccessibilitySignal.register({ name: localize('accessibilitySignals.clear', 'Clear'), sound: Sound.clear, - legacyAudioCueSettingsKey: 'accessibilitySignals.clear', + legacySoundSettingsKey: 'accessibilitySignals.clear', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, announcementMessage: localize('accessibility.signals.clear', 'Clear'), settingsKey: 'accessibility.signals.clear' @@ -567,7 +567,7 @@ export class AccessibilitySignal { public static readonly save = AccessibilitySignal.register({ name: localize('accessibilitySignals.save', 'Save'), sound: Sound.save, - legacyAudioCueSettingsKey: 'accessibilitySignals.save', + legacySoundSettingsKey: 'accessibilitySignals.save', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, announcementMessage: localize('accessibility.signals.save', 'Save'), settingsKey: 'accessibility.signals.save' @@ -576,7 +576,7 @@ export class AccessibilitySignal { public static readonly format = AccessibilitySignal.register({ name: localize('accessibilitySignals.format', 'Format'), sound: Sound.format, - legacyAudioCueSettingsKey: 'accessibilitySignals.format', + legacySoundSettingsKey: 'accessibilitySignals.format', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, announcementMessage: localize('accessibility.signals.format', 'Format'), settingsKey: 'accessibility.signals.format' diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 243068f398339..529a7f35bc798 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -266,6 +266,14 @@ const configuration: IConfigurationNode = { type: 'boolean', default: true }, + 'accessibility.signals.sounds.volume': { + 'description': localize('accessibility.signals.sounds.volume', "The volume of the sounds in percent (0-100)."), + 'type': 'number', + 'minimum': 0, + 'maximum': 100, + 'default': 70, + tags: ['accessibility'] + }, 'accessibility.signals.debouncePositionChanges': { 'description': localize('accessibility.signals.debouncePositionChanges', "Whether or not position changes should be debounced"), 'type': 'boolean', @@ -688,7 +696,16 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen }); } } - +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'audioCues.volume', + migrateFn: (value, accessor) => { + return [ + ['accessibility.signals.sounds.volume', { value }], + ['audioCues.volume', { value: undefined }] + ]; + } + }]); Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ @@ -696,7 +713,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (value, accessor) => { return [ ['accessibility.signals.debouncePositionChanges', { value }], - ['audioCues.debouncePositionChangess', { value: undefined }] + ['audioCues.debouncePositionChanges', { value: undefined }] ]; } }]); diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index a084c6b378db6..a28f965e588ab 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -75,7 +75,7 @@ class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { } } - content.push(AccessibilityHelpNLS.listAudioCues); + content.push(AccessibilityHelpNLS.listSignalSounds); content.push(AccessibilityHelpNLS.listAlerts); const chatCommandInfo = getChatCommandInfo(this._keybindingService, this._contextKeyService); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index 4f916fb4d5c7e..e7ac5c3539074 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShowAccessibilityAnnouncementHelp, ShowAudioCueHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; +import { ShowAccessibilityAnnouncementHelp, ShowSignalSoundHelp } from 'vs/workbench/contrib/accessibilitySignals/browser/commands'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -44,11 +44,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis tags: ['accessibility'] }, 'audioCues.volume': { - 'description': localize('audioCues.volume', "The volume of the audio cues in percent (0-100)."), - 'type': 'number', - 'minimum': 0, - 'maximum': 100, - 'default': 70, + markdownDeprecationMessage: 'Deprecated. Use `accessibility.signals.sounds.volume` instead.', tags: ['accessibility'] }, 'audioCues.debouncePositionChanges': { @@ -177,6 +173,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, }); -registerAction2(ShowAudioCueHelp); +registerAction2(ShowSignalSoundHelp); registerAction2(ShowAccessibilityAnnouncementHelp); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 1236f98f57a80..8f47611cfd18a 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -13,13 +13,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -export class ShowAudioCueHelp extends Action2 { - static readonly ID = 'audioCues.help'; +export class ShowSignalSoundHelp extends Action2 { + static readonly ID = 'signals.sounds.help'; constructor() { super({ - id: ShowAudioCueHelp.ID, - title: localize2('audioCues.help', "Help: List Audio Cues"), + id: ShowSignalSoundHelp.ID, + title: localize2('signals.sound.help', "Help: List Signal Sounds"), f1: true, }); } @@ -82,7 +82,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { constructor() { super({ id: ShowAccessibilityAnnouncementHelp.ID, - title: localize2('accessibility.announcement.help', "Help: List Announcements"), + title: localize2('accessibility.announcement.help', "Help: List Signal Announcements"), f1: true, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 6be43b0fcff20..9fd67d69b88ee 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -45,7 +45,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes.")); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); } - content.push(localize('chat.audioCues', "Audio cues can be changed via settings with a prefix of audioCues.chat. By default, if a request takes more than 4 seconds, you will hear an audio cue indicating that progress is still occurring.")); + 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/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index cfbcd71273a60..204ebdd135844 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -103,11 +103,11 @@ function createScreenReaderHelp(): IDisposable { return; } - const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; + const keys = ['accessibility.signals.diffLineDeleted', 'accessibility.signals.diffLineInserted', 'accessibility.signals.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), localize('msg2', "View the next ({0}) or previous ({1}) diff in diff review mode, which is optimized for screen readers.", next, previous), - localize('msg3', "To control which audio cues should be played, the following settings can be configured: {0}.", keys.join(', ')), + localize('msg3', "To control which accessibility signals should be played, the following settings can be configured: {0}.", keys.join(', ')), ]; const commentCommandInfo = getCommentCommandInfo(keybindingService, contextKeyService, codeEditor); if (commentCommandInfo) { diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index b83c1fcd8949a..719bd0bc24c69 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -368,7 +368,7 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; // List import 'vs/workbench/contrib/list/browser/list.contribution'; -// Audio Cues +// Accessibility Signals import 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; // Deprecated Extension Migrator From 6cbe80c310e810e2b1cd4b92e6e8f99ee1ea6000 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:46:41 -0600 Subject: [PATCH 0173/1863] alert -> announcement --- .../browser/accessibilityConfiguration.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 529a7f35bc798..7bdbe32f90e1f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -724,14 +724,14 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (audioCue, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; const alertSettingsKey = item.alertSettingsKey; - let alert: string | undefined; + let announcement: string | undefined; if (alertSettingsKey) { - alert = accessor(alertSettingsKey) ?? undefined; - if (typeof alert !== 'string') { - alert = alert ? 'auto' : 'off'; + announcement = accessor(alertSettingsKey) ?? undefined; + if (typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: alert ? { alert, audioCue } : { audioCue } }]); + configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: announcement ? { announcement, audioCue } : { audioCue } }]); return configurationKeyValuePairs; } }))); From c69e96e6bf08f4416fd15a84a67babcaac1e4cb5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 10:52:43 -0600 Subject: [PATCH 0174/1863] audio cue -> sound --- .../browser/accessibilityConfiguration.ts | 150 +++++++++--------- .../accessibilitySignal.contribution.ts | 54 +++---- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 7bdbe32f90e1f..8ed1e20369a6f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -13,7 +13,7 @@ import { ISpeechService } 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'; -import { audioCueFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; +import { soundFeatureBase } from 'vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution'; export const accessibilityHelpIsShown = new RawContextKey('accessibilityHelpIsShown', false, true); export const accessibleViewIsShown = new RawContextKey('accessibleViewIsShown', false, true); @@ -106,8 +106,8 @@ export const announcementFeatureBase: IConfigurationPropertySchema = { 'enum': ['auto', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), - localize('audioCues.enabled.off', "Disable announcement.") + localize('announcement.enabled.auto', "Enable announcement, will only play when in screen reader optimized mode."), + localize('announcement.enabled.off', "Disable announcement.") ], tags: ['accessibility'], }; @@ -117,7 +117,7 @@ const defaultNoAnnouncement: IConfigurationPropertySchema = { 'tags': ['accessibility'], additionalProperties: true, 'default': { - 'audioCue': 'auto', + 'sound': 'auto', } }; @@ -284,9 +284,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasBreakpoint', "Plays a signal when the active line has a breakpoint."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasBreakpoint.audioCue', "Plays an audio cue when the active line has a breakpoint."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasBreakpoint.sound', "Plays a sound when the active line has a breakpoint."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), @@ -299,9 +299,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.lineHasInlineSuggestion', "Indicates when the active line has an inline suggestion."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasInlineSuggestion.audioCue', "Plays an audio cue when the active line has an inline suggestion."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasInlineSuggestion.sound', "Plays a sound when the active line has an inline suggestion."), + ...soundFeatureBase } } }, @@ -309,9 +309,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasError', "Indicates when the active line has an error."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasError.audioCue', "Plays an audio cue when the active line has an error."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasError.sound', "Plays a sound when the active line has an error."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), @@ -324,9 +324,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasFoldedArea', "Indicates when the active line has a folded area that can be unfolded."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasFoldedArea.audioCue', "Plays an audio cue when the active line has a folded area that can be unfolded."), - ...audioCueFeatureBase, + 'sound': { + 'description': localize('accessibility.signals.lineHasFoldedArea.sound', "Plays a sound when the active line has a folded area that can be unfolded."), + ...soundFeatureBase, default: 'off' }, 'announcement': { @@ -339,9 +339,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.lineHasWarning', "Plays a signal when the active line has a warning."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.lineHasWarning.audioCue', "Plays an audio cue when the active line has a warning."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.lineHasWarning.sound', "Plays a sound when the active line has a warning."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), @@ -354,9 +354,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.onDebugBreak', "Plays a signal when the debugger stopped on a breakpoint."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.onDebugBreak.audioCue', "Plays an audio cue when the debugger stopped on a breakpoint."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.onDebugBreak.sound', "Plays a sound when the debugger stopped on a breakpoint."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), @@ -368,9 +368,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.noInlayHints', "Plays a signal when trying to read a line with inlay hints that has no inlay hints."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.noInlayHints.audioCue', "Plays an audio cue when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.noInlayHints.sound', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), @@ -382,9 +382,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.taskCompleted', "Plays a signal when a task is completed."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.taskCompleted.audioCue', "Plays an audio cue when a task is completed."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.taskCompleted.sound', "Plays a sound when a task is completed."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), @@ -396,9 +396,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.taskFailed', "Plays a signal when a task fails (non-zero exit code)."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.taskFailed.audioCue', "Plays an audio cue when a task fails (non-zero exit code)."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.taskFailed.sound', "Plays a sound when a task fails (non-zero exit code)."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), @@ -410,9 +410,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalCommandFailed', "Plays a signal when a terminal command fails (non-zero exit code)."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalCommandFailed.audioCue', "Plays an audio cue when a terminal command fails (non-zero exit code)."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalCommandFailed.sound', "Plays a sound when a terminal command fails (non-zero exit code)."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), @@ -424,9 +424,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalQuickFix', "Plays a signal when terminal Quick Fixes are available."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalQuickFix.audioCue', "Plays an audio cue when terminal Quick Fixes are available."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalQuickFix.sound', "Plays a sound when terminal Quick Fixes are available."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), @@ -438,9 +438,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.terminalBell', "Plays a signal when the terminal bell is ringing."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.terminalBell.audioCue', "Plays an audio cue when the terminal bell is ringing."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.terminalBell.sound', "Plays a sound when the terminal bell is ringing."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), @@ -452,9 +452,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineInserted', "Indicates when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.audioCue', "Plays an audio cue when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.sound', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -462,9 +462,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineModified', "Indicates when the focus moves to an modified line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.diffLineModified.audioCue', "Plays an audio cue when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.diffLineModified.sound', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -472,9 +472,9 @@ const configuration: IConfigurationNode = { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.diffLineDeleted', "Indicates when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.diffLineDeleted.audioCue', "Plays an audio cue when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.diffLineDeleted.sound', "Plays a sound when the focus moves to an deleted line in Accessible Diff Viewer mode or to the next/previous change."), + ...soundFeatureBase } } }, @@ -482,9 +482,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.notebookCellCompleted', "Plays a signal when a notebook cell execution is successfully completed."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.notebookCellCompleted.audioCue', "Plays an audio cue when a notebook cell execution is successfully completed."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.notebookCellCompleted.sound', "Plays a sound when a notebook cell execution is successfully completed."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), @@ -496,9 +496,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.notebookCellFailed', "Plays a signal when a notebook cell execution fails."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.notebookCellFailed.audioCue', "Plays an audio cue when a notebook cell execution fails."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.notebookCellFailed.sound', "Plays a sound when a notebook cell execution fails."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), @@ -510,9 +510,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal when a chat request is made."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatRequestSent.audioCue', "Plays an audio cue when a chat request is made."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatRequestSent.sound', "Plays a sound when a chat request is made."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), @@ -524,9 +524,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.chatResponsePending', "Plays a signal on loop while the response is pending."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatResponsePending.audioCue', "Plays an audio cue on loop while the response is pending."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatResponsePending.sound', "Plays a sound on loop while the response is pending."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), @@ -539,9 +539,9 @@ const configuration: IConfigurationNode = { additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.chatResponseReceived.audioCue', "Plays an audio cue on loop while the response has been received."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.chatResponseReceived.sound', "Plays a sound on loop while the response has been received."), + ...soundFeatureBase }, } }, @@ -549,9 +549,9 @@ const configuration: IConfigurationNode = { ...signalFeatureBase, 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.clear.audioCue', "Plays an audio cue when a feature is cleared."), - ...audioCueFeatureBase + 'sound': { + 'description': localize('accessibility.signals.clear.sound', "Plays a sound when a feature is cleared."), + ...soundFeatureBase }, 'announcement': { 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), @@ -565,15 +565,15 @@ const configuration: IConfigurationNode = { additionalProperties: true, 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.save.audioCue', "Plays an audio cue when a file is saved."), + 'sound': { + 'description': localize('accessibility.signals.save.sound', "Plays a sound when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.save.audioCue.userGesture', "Plays the audio cue when a user explicitly saves a file."), - localize('accessibility.signals.save.audioCue.always', "Plays the audio cue whenever a file is saved, including auto save."), - localize('accessibility.signals.save.audioCue.never', "Never plays the audio cue.") + localize('accessibility.signals.save.sound.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('accessibility.signals.save.sound.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('accessibility.signals.save.sound.never', "Never plays the audio cue.") ], }, 'announcement': { @@ -589,7 +589,7 @@ const configuration: IConfigurationNode = { }, }, default: { - 'audioCue': 'never', + 'sound': 'never', 'announcement': 'never' } }, @@ -599,8 +599,8 @@ const configuration: IConfigurationNode = { additionalProperties: true, 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { - 'audioCue': { - 'description': localize('accessibility.signals.format.audioCue', "Plays an audio cue when a file or notebook is formatted."), + 'sound': { + 'description': localize('accessibility.signals.format.sound', "Plays a sound when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts index e7ac5c3539074..8469855da9fb1 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/accessibilitySignal.contribution.ts @@ -20,20 +20,20 @@ registerSingleton(IAccessibilitySignalService, AccessibilitySignalService, Insta Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SignalLineFeatureContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilitySignalLineDebuggerContribution, LifecyclePhase.Restored); -export const audioCueFeatureBase: IConfigurationPropertySchema = { +export const soundFeatureBase: IConfigurationPropertySchema = { 'type': 'string', 'enum': ['auto', 'on', 'off'], 'default': 'auto', 'enumDescriptions': [ - localize('audioCues.enabled.auto', "Enable audio cue when a screen reader is attached."), - localize('audioCues.enabled.on', "Enable audio cue."), - localize('audioCues.enabled.off', "Disable audio cue.") + localize('audioCues.enabled.auto', "Enable sound when a screen reader is attached."), + localize('audioCues.enabled.on', "Enable sound."), + localize('audioCues.enabled.off', "Disable sound.") ], tags: ['accessibility'], }; const markdownDeprecationMessage = localize('audioCues.enabled.deprecated', "This setting is deprecated. Use `signals` settings instead."); -const audioCueDeprecatedFeatureBase: IConfigurationPropertySchema = { - ...audioCueFeatureBase, +const soundDeprecatedFeatureBase: IConfigurationPropertySchema = { + ...soundFeatureBase, markdownDeprecationMessage }; @@ -56,92 +56,92 @@ Registry.as(ConfigurationExtensions.Configuration).regis }, 'audioCues.lineHasBreakpoint': { 'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."), - ...audioCueDeprecatedFeatureBase + ...soundDeprecatedFeatureBase }, 'audioCues.lineHasInlineSuggestion': { 'description': localize('audioCues.lineHasInlineSuggestion', "Plays a sound when the active line has an inline suggestion."), - ...audioCueDeprecatedFeatureBase + ...soundDeprecatedFeatureBase }, 'audioCues.lineHasError': { 'description': localize('audioCues.lineHasError', "Plays a sound when the active line has an error."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.lineHasFoldedArea': { 'description': localize('audioCues.lineHasFoldedArea', "Plays a sound when the active line has a folded area that can be unfolded."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.lineHasWarning': { 'description': localize('audioCues.lineHasWarning', "Plays a sound when the active line has a warning."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off', }, 'audioCues.onDebugBreak': { 'description': localize('audioCues.onDebugBreak', "Plays a sound when the debugger stopped on a breakpoint."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.noInlayHints': { 'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.taskCompleted': { 'description': localize('audioCues.taskCompleted', "Plays a sound when a task is completed."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.taskFailed': { 'description': localize('audioCues.taskFailed', "Plays a sound when a task fails (non-zero exit code)."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalCommandFailed': { 'description': localize('audioCues.terminalCommandFailed', "Plays a sound when a terminal command fails (non-zero exit code)."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalQuickFix': { 'description': localize('audioCues.terminalQuickFix', "Plays a sound when terminal Quick Fixes are available."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.terminalBell': { 'description': localize('audioCues.terminalBell', "Plays a sound when the terminal bell is ringing."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'on' }, 'audioCues.diffLineInserted': { 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.diffLineDeleted': { 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.diffLineModified': { 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.notebookCellCompleted': { 'description': localize('audioCues.notebookCellCompleted', "Plays a sound when a notebook cell execution is successfully completed."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.notebookCellFailed': { 'description': localize('audioCues.notebookCellFailed', "Plays a sound when a notebook cell execution fails."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, }, 'audioCues.chatRequestSent': { 'description': localize('audioCues.chatRequestSent', "Plays a sound when a chat request is made."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.chatResponsePending': { 'description': localize('audioCues.chatResponsePending', "Plays a sound on loop while the response is pending."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'auto' }, 'audioCues.chatResponseReceived': { 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.clear': { 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), - ...audioCueDeprecatedFeatureBase, + ...soundDeprecatedFeatureBase, default: 'off' }, 'audioCues.save': { From 1a598ffe00c050dd484c1b39601fd863fb074755 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Mon, 12 Feb 2024 17:56:56 +0100 Subject: [PATCH 0175/1863] fix: memory leak in notebook editor widget (#204892) --- .../contrib/notebook/browser/notebookEditorWidget.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0719ee2ca4fa1..c57af0eb8ad62 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -313,16 +313,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._notebookOptions = creationOptions.options ?? new NotebookOptions(this.creationOptions?.codeWindow ?? mainWindow, this.configurationService, notebookExecutionStateService, codeEditorService, this._readOnly); this._register(this._notebookOptions); + const eventDispatcher = this._register(new NotebookEventDispatcher()); this._viewContext = new ViewContext( this._notebookOptions, - new NotebookEventDispatcher(), + eventDispatcher, language => this.getBaseCellEditorOptions(language)); this._register(this._viewContext.eventDispatcher.onDidChangeCellState(e => { this._onDidChangeCellState.fire(e); })); this._overlayContainer = document.createElement('div'); - this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); + this.scopedContextKeyService = this._register(contextKeyService.createScoped(this._overlayContainer)); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); this._register(_notebookService.onDidChangeOutputRenderers(() => { 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 0176/1863] 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 d993243770f5a..163b64068d2ab 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 0177/1863] 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 163b64068d2ab..7e944aa9753db 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 8fe3c2dabbd716445e866dbd3e956fb564c21331 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 11:32:24 -0600 Subject: [PATCH 0178/1863] Get migration to work --- .../browser/accessibilitySignalService.ts | 60 +++++++------- .../browser/accessibilityConfiguration.ts | 83 +++++++++++-------- .../accessibilitySignals/browser/commands.ts | 66 +++++++-------- 3 files changed, 110 insertions(+), 99 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 503a178b46d61..1e1262ec66853 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -178,9 +178,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.signalSettingsKey) + e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.settingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.signalSettingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.audioCue') ); return derived(reader => { /** @description sound enabled */ @@ -209,9 +209,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isAnnouncementEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.alertSettingsKey!) || e.affectsConfiguration(event.signal.signalSettingsKey) + e.affectsConfiguration(event.signal.legacyAnnouncementSettingsKey!) || e.affectsConfiguration(event.signal.settingsKey) ), - () => event.signal.alertSettingsKey ? this.configurationService.getValue(event.signal.signalSettingsKey + '.alert') : false + () => event.signal.announcementMessage ? this.configurationService.getValue(event.signal.settingsKey + '.announcement') : false ); return derived(reader => { /** @description alert enabled */ @@ -226,7 +226,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi }, JSON.stringify); public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { + if (!cue.announcementMessage) { return false; } return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; @@ -385,7 +385,7 @@ export class AccessibilitySignal { public static readonly error = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.lineHasError', + legacySoundSettingsKey: 'audioCues.lineHasError', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Error, announcementMessage: localize('accessibility.signals.lineHasError', 'Error'), settingsKey: 'accessibility.signals.lineHasError' @@ -393,7 +393,7 @@ export class AccessibilitySignal { public static readonly warning = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'), sound: Sound.warning, - legacySoundSettingsKey: 'accessibilitySignals.lineHasWarning', + legacySoundSettingsKey: 'audioCues.lineHasWarning', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Warning, announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), settingsKey: 'accessibility.signals.lineHasWarning' @@ -401,7 +401,7 @@ export class AccessibilitySignal { public static readonly foldedArea = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'), sound: Sound.foldedArea, - legacySoundSettingsKey: 'accessibilitySignals.lineHasFoldedArea', + legacySoundSettingsKey: 'audioCues.lineHasFoldedArea', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.FoldedArea, announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), settingsKey: 'accessibility.signals.lineHasFoldedArea' @@ -409,7 +409,7 @@ export class AccessibilitySignal { public static readonly break = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'), sound: Sound.break, - legacySoundSettingsKey: 'accessibilitySignals.lineHasBreakpoint', + legacySoundSettingsKey: 'audioCues.lineHasBreakpoint', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Breakpoint, announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), settingsKey: 'accessibility.signals.lineHasBreakpoint' @@ -417,14 +417,14 @@ export class AccessibilitySignal { public static readonly inlineSuggestion = AccessibilitySignal.register({ name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), sound: Sound.quickFixes, - legacySoundSettingsKey: 'accessibilitySignals.lineHasInlineSuggestion', + legacySoundSettingsKey: 'audioCues.lineHasInlineSuggestion', settingsKey: 'accessibility.signals.lineHasInlineSuggestion' }); public static readonly terminalQuickFix = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'), sound: Sound.quickFixes, - legacySoundSettingsKey: 'accessibilitySignals.terminalQuickFix', + legacySoundSettingsKey: 'audioCues.terminalQuickFix', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), settingsKey: 'accessibility.signals.terminalQuickFix' @@ -433,7 +433,7 @@ export class AccessibilitySignal { public static readonly onDebugBreak = AccessibilitySignal.register({ name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), sound: Sound.break, - legacySoundSettingsKey: 'accessibilitySignals.onDebugBreak', + legacySoundSettingsKey: 'audioCues.onDebugBreak', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), settingsKey: 'accessibility.signals.onDebugBreak' @@ -442,7 +442,7 @@ export class AccessibilitySignal { public static readonly noInlayHints = AccessibilitySignal.register({ name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.noInlayHints', + legacySoundSettingsKey: 'audioCues.noInlayHints', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NoInlayHints, announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), settingsKey: 'accessibility.signals.noInlayHints' @@ -451,7 +451,7 @@ export class AccessibilitySignal { public static readonly taskCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskCompleted', 'Task Completed'), sound: Sound.taskCompleted, - legacySoundSettingsKey: 'accessibilitySignals.taskCompleted', + legacySoundSettingsKey: 'audioCues.taskCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskCompleted, announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), settingsKey: 'accessibility.signals.taskCompleted' @@ -460,7 +460,7 @@ export class AccessibilitySignal { public static readonly taskFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.taskFailed', 'Task Failed'), sound: Sound.taskFailed, - legacySoundSettingsKey: 'accessibilitySignals.taskFailed', + legacySoundSettingsKey: 'audioCues.taskFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TaskFailed, announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), settingsKey: 'accessibility.signals.taskFailed' @@ -469,7 +469,7 @@ export class AccessibilitySignal { public static readonly terminalCommandFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'), sound: Sound.error, - legacySoundSettingsKey: 'accessibilitySignals.terminalCommandFailed', + legacySoundSettingsKey: 'audioCues.terminalCommandFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), settingsKey: 'accessibility.signals.terminalCommandFailed' @@ -478,7 +478,7 @@ export class AccessibilitySignal { public static readonly terminalBell = AccessibilitySignal.register({ name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'), sound: Sound.terminalBell, - legacySoundSettingsKey: 'accessibilitySignals.terminalBell', + legacySoundSettingsKey: 'audioCues.terminalBell', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.TerminalBell, announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), settingsKey: 'accessibility.signals.terminalBell' @@ -487,7 +487,7 @@ export class AccessibilitySignal { public static readonly notebookCellCompleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'), sound: Sound.taskCompleted, - legacySoundSettingsKey: 'accessibilitySignals.notebookCellCompleted', + legacySoundSettingsKey: 'audioCues.notebookCellCompleted', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), settingsKey: 'accessibility.signals.notebookCellCompleted' @@ -496,7 +496,7 @@ export class AccessibilitySignal { public static readonly notebookCellFailed = AccessibilitySignal.register({ name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'), sound: Sound.taskFailed, - legacySoundSettingsKey: 'accessibilitySignals.notebookCellFailed', + legacySoundSettingsKey: 'audioCues.notebookCellFailed', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), settingsKey: 'accessibility.signals.notebookCellFailed' @@ -505,28 +505,28 @@ export class AccessibilitySignal { public static readonly diffLineInserted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'), sound: Sound.diffLineInserted, - legacySoundSettingsKey: 'accessibilitySignals.diffLineInserted', + legacySoundSettingsKey: 'audioCues.diffLineInserted', settingsKey: 'accessibility.signals.diffLineInserted' }); public static readonly diffLineDeleted = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'), sound: Sound.diffLineDeleted, - legacySoundSettingsKey: 'accessibilitySignals.diffLineDeleted', + legacySoundSettingsKey: 'audioCues.diffLineDeleted', settingsKey: 'accessibility.signals.diffLineDeleted' }); public static readonly diffLineModified = AccessibilitySignal.register({ name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'), sound: Sound.diffLineModified, - legacySoundSettingsKey: 'accessibilitySignals.diffLineModified', + legacySoundSettingsKey: 'audioCues.diffLineModified', settingsKey: 'accessibility.signals.diffLineModified' }); public static readonly chatRequestSent = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), sound: Sound.chatRequestSent, - legacySoundSettingsKey: 'accessibilitySignals.chatRequestSent', + legacySoundSettingsKey: 'audioCues.chatRequestSent', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), settingsKey: 'accessibility.signals.chatRequestSent' @@ -534,7 +534,7 @@ export class AccessibilitySignal { public static readonly chatResponseReceived = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'), - legacySoundSettingsKey: 'accessibilitySignals.chatResponseReceived', + legacySoundSettingsKey: 'audioCues.chatResponseReceived', sound: { randomOneOf: [ Sound.chatResponseReceived1, @@ -549,7 +549,7 @@ export class AccessibilitySignal { public static readonly chatResponsePending = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatResponsePending', 'Chat Response Pending'), sound: Sound.chatResponsePending, - legacySoundSettingsKey: 'accessibilitySignals.chatResponsePending', + legacySoundSettingsKey: 'audioCues.chatResponsePending', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, announcementMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), settingsKey: 'accessibility.signals.chatResponsePending' @@ -558,7 +558,7 @@ export class AccessibilitySignal { public static readonly clear = AccessibilitySignal.register({ name: localize('accessibilitySignals.clear', 'Clear'), sound: Sound.clear, - legacySoundSettingsKey: 'accessibilitySignals.clear', + legacySoundSettingsKey: 'audioCues.clear', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Clear, announcementMessage: localize('accessibility.signals.clear', 'Clear'), settingsKey: 'accessibility.signals.clear' @@ -567,7 +567,7 @@ export class AccessibilitySignal { public static readonly save = AccessibilitySignal.register({ name: localize('accessibilitySignals.save', 'Save'), sound: Sound.save, - legacySoundSettingsKey: 'accessibilitySignals.save', + legacySoundSettingsKey: 'audioCues.save', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Save, announcementMessage: localize('accessibility.signals.save', 'Save'), settingsKey: 'accessibility.signals.save' @@ -576,7 +576,7 @@ export class AccessibilitySignal { public static readonly format = AccessibilitySignal.register({ name: localize('accessibilitySignals.format', 'Format'), sound: Sound.format, - legacySoundSettingsKey: 'accessibilitySignals.format', + legacySoundSettingsKey: 'audioCues.format', legacyAnnouncementSettingsKey: AccessibilityAlertSettingId.Format, announcementMessage: localize('accessibility.signals.format', 'Format'), settingsKey: 'accessibility.signals.format' @@ -585,9 +585,9 @@ export class AccessibilitySignal { private constructor( public readonly sound: SoundSource, public readonly name: string, + public readonly legacySoundSettingsKey: string, public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, + public readonly legacyAnnouncementSettingsKey?: string, public readonly announcementMessage?: string, ) { } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 8ed1e20369a6f..c322814c2bae9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -76,7 +76,7 @@ const baseVerbosityProperty: IConfigurationPropertySchema = { default: true, tags: ['accessibility'] }; -const markdownDeprecationMessage = localize('accessibility.alert.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); +const markdownDeprecationMessage = localize('accessibility.announcement.deprecationMessage', "This setting is deprecated. Use the `signals` settings instead."); const baseAlertProperty: IConfigurationPropertySchema = { type: 'boolean', default: true, @@ -96,7 +96,7 @@ const signalFeatureBase: IConfigurationPropertySchema = { 'tags': ['accessibility'], additionalProperties: false, default: { - audioCue: 'auto', + sound: 'auto', announcement: 'auto' } }; @@ -289,9 +289,8 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasBreakpoint.alert', "Indicates when the active line has a breakpoint."), - ...announcementFeatureBase, - default: 'off' + 'description': localize('accessibility.signals.lineHasBreakpoint.announcement', "Indicates when the active line has a breakpoint."), + ...announcementFeatureBase }, }, }, @@ -301,7 +300,8 @@ const configuration: IConfigurationNode = { 'properties': { 'sound': { 'description': localize('accessibility.signals.lineHasInlineSuggestion.sound', "Plays a sound when the active line has an inline suggestion."), - ...soundFeatureBase + ...soundFeatureBase, + 'default': 'off' } } }, @@ -314,7 +314,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasError.alert', "Indicates when the active line has an error."), + 'description': localize('accessibility.signals.lineHasError.announcement', "Indicates when the active line has an error."), ...announcementFeatureBase, default: 'off' }, @@ -330,7 +330,7 @@ const configuration: IConfigurationNode = { default: 'off' }, 'announcement': { - 'description': localize('accessibility.signals.lineHasFoldedArea.alert', "Indicates when the active line has a folded area that can be unfolded."), + 'description': localize('accessibility.signals.lineHasFoldedArea.announcement', "Indicates when the active line has a folded area that can be unfolded."), ...announcementFeatureBase }, } @@ -344,7 +344,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.lineHasWarning.alert', "Indicates when the active line has a warning."), + 'description': localize('accessibility.signals.lineHasWarning.announcement', "Indicates when the active line has a warning."), ...announcementFeatureBase, default: 'off' }, @@ -359,7 +359,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.onDebugBreak.alert', "Indicates when the debugger stopped on a breakpoint."), + 'description': localize('accessibility.signals.onDebugBreak.announcement', "Indicates when the debugger stopped on a breakpoint."), ...announcementFeatureBase }, } @@ -373,7 +373,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.noInlayHints.alert', "Indicates when trying to read a line with inlay hints that has no inlay hints."), + 'description': localize('accessibility.signals.noInlayHints.announcement', "Indicates when trying to read a line with inlay hints that has no inlay hints."), ...announcementFeatureBase }, } @@ -387,7 +387,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.taskCompleted.alert', "Indicates when a task is completed."), + 'description': localize('accessibility.signals.taskCompleted.announcement', "Indicates when a task is completed."), ...announcementFeatureBase }, } @@ -401,7 +401,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.taskFailed.alert', "Indicates when a task fails (non-zero exit code)."), + 'description': localize('accessibility.signals.taskFailed.announcement', "Indicates when a task fails (non-zero exit code)."), ...announcementFeatureBase }, } @@ -415,7 +415,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalCommandFailed.alert', "Indicates when a terminal command fails (non-zero exit code)."), + 'description': localize('accessibility.signals.terminalCommandFailed.announcement', "Indicates when a terminal command fails (non-zero exit code)."), ...announcementFeatureBase }, } @@ -429,7 +429,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalQuickFix.alert', "Indicates when terminal Quick Fixes are available."), + 'description': localize('accessibility.signals.terminalQuickFix.announcement', "Indicates when terminal Quick Fixes are available."), ...announcementFeatureBase }, } @@ -443,7 +443,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.terminalBell.alert', "Indicates when the terminal bell is ringing."), + 'description': localize('accessibility.signals.terminalBell.announcement', "Indicates when the terminal bell is ringing."), ...announcementFeatureBase }, } @@ -487,7 +487,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.notebookCellCompleted.alert', "Indicates when a notebook cell execution is successfully completed."), + 'description': localize('accessibility.signals.notebookCellCompleted.announcement', "Indicates when a notebook cell execution is successfully completed."), ...announcementFeatureBase }, } @@ -501,7 +501,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.notebookCellFailed.alert', "Indicates when a notebook cell execution fails."), + 'description': localize('accessibility.signals.notebookCellFailed.announcement', "Indicates when a notebook cell execution fails."), ...announcementFeatureBase }, } @@ -515,7 +515,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.chatRequestSent.alert', "Indicates when a chat request is made."), + 'description': localize('accessibility.signals.chatRequestSent.announcement', "Indicates when a chat request is made."), ...announcementFeatureBase }, } @@ -529,7 +529,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.chatResponsePending.alert', "Alerts on loop while the response is pending."), + 'description': localize('accessibility.signals.chatResponsePending.announcement', "Alerts on loop while the response is pending."), ...announcementFeatureBase }, }, @@ -554,7 +554,7 @@ const configuration: IConfigurationNode = { ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.clear.alert', "Indicates when a feature is cleared."), + 'description': localize('accessibility.signals.clear.announcement', "Indicates when a feature is cleared."), ...announcementFeatureBase }, }, @@ -577,14 +577,14 @@ const configuration: IConfigurationNode = { ], }, 'announcement': { - 'description': localize('accessibility.signals.save.alert', "Indicates when a file is saved."), + 'description': localize('accessibility.signals.save.announcement', "Indicates when a file is saved."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.save.alert.userGesture', "Plays the alert when a user explicitly saves a file."), - localize('accessibility.signals.save.alert.always', "Plays the alert whenever a file is saved, including auto save."), - localize('accessibility.signals.save.alert.never', "Never plays the audio cue.") + localize('accessibility.signals.save.announcement.userGesture', "Announces when a user explicitly saves a file."), + localize('accessibility.signals.save.announcement.always', "Announces whenever a file is saved, including auto save."), + localize('accessibility.signals.save.announcement.never', "Never plays the audio cue.") ], }, }, @@ -611,14 +611,14 @@ const configuration: IConfigurationNode = { ], }, 'announcement': { - 'description': localize('accessibility.signals.format.alert', "Indicates when a file or notebook is formatted."), + 'description': localize('accessibility.signals.format.announcement', "Indicates when a file or notebook is formatted."), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], 'default': 'never', 'enumDescriptions': [ - localize('accessibility.signals.format.alert.userGesture', "Plays the alertwhen a user explicitly formats a file."), - localize('accessibility.signals.format.alert.always', "Plays the alert whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), - localize('accessibility.signals.format.alert.never', "Never plays the alert.") + localize('accessibility.signals.format.announcement.userGesture', "Announceswhen a user explicitly formats a file."), + localize('accessibility.signals.format.announcement.always', "Announces whenever a file is formatted, including if it is set to format on save, type, or, paste, or run of a cell."), + localize('accessibility.signals.format.announcement.never', "Never announces.") ], }, } @@ -720,18 +720,29 @@ Registry.as(WorkbenchExtensions.ConfigurationMi Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.map(item => ({ - key: item.settingsKey, - migrateFn: (audioCue, accessor) => { + key: item.legacySoundSettingsKey, + migrateFn: (sound, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - const alertSettingsKey = item.alertSettingsKey; + const legacyAnnouncementSettingsKey = item.legacyAnnouncementSettingsKey; let announcement: string | undefined; - if (alertSettingsKey) { - announcement = accessor(alertSettingsKey) ?? undefined; - if (typeof announcement !== 'string') { + if (legacyAnnouncementSettingsKey) { + announcement = accessor(legacyAnnouncementSettingsKey) ?? undefined; + if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.signalSettingsKey}`, { value: announcement ? { announcement, audioCue } : { audioCue } }]); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + return configurationKeyValuePairs; + } + }))); + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.announcementMessage).map(item => ({ + key: item.legacyAnnouncementSettingsKey!, + migrateFn: (announcement, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + const sound = accessor(item.legacySoundSettingsKey); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 8f47611cfd18a..2ec576a22e196 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -30,47 +30,47 @@ export class ShowSignalSoundHelp extends Action2 { const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ - label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.sound')})` : signal.name, - audioCue: signal, + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.sound')})` : signal.name, + signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal)); qp.onDidAccept(() => { - const enabledCues = qp.selectedItems.map(i => i.audioCue); + const enabledCues = qp.selectedItems.map(i => i.signal); const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); for (const cue of enabledCues) { if (!userGestureSignals.includes(cue)) { - let { audioCue, announcement } = configurationService.getValue<{ audioCue: string; announcement?: string }>(cue.signalSettingsKey); - audioCue = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); + sound = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; if (announcement) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + configurationService.updateValue(cue.settingsKey, { sound }); } } } for (const cue of disabledCues) { - const announcement = cue.announcementMessage ? configurationService.getValue(cue.signalSettingsKey + '.announcement') : undefined; - const audioCue = userGestureSignals.includes(cue) ? 'never' : 'off'; + const announcement = cue.announcementMessage ? configurationService.getValue(cue.settingsKey + '.announcement') : undefined; + const sound = userGestureSignals.includes(cue) ? 'never' : 'off'; if (announcement) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, announcement }); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.signalSettingsKey, { audioCue }); + configurationService.updateValue(cue.settingsKey, { sound }); } } qp.hide(); }); qp.onDidChangeActive(() => { - accessibilitySignalService.playSound(qp.activeItems[0].audioCue.sound.getSound(true), true); + accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true); }); - qp.placeholder = localize('audioCues.help.placeholder', 'Select an audio cue to play and configure'); + qp.placeholder = localize('audioCues.help.placeholder', 'Select a sound to play and configure'); qp.canSelectMany = true; await qp.show(); } @@ -93,38 +93,38 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { audioCue: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ - label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.signalSettingsKey + '.announcement')})` : signal.name, - audioCue: signal, + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.announcement')})` : signal.name, + signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), alwaysVisible: true }] : [] })); - const qp = quickInputService.createQuickPick(); + const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.audioCue)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal)); qp.onDidAccept(() => { - const enabledAlerts = qp.selectedItems.map(i => i.audioCue); - const disabledAlerts = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAlerts.includes(cue)); - for (const cue of enabledAlerts) { + const enabledAnnouncements = qp.selectedItems.map(i => i.signal); + const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); + for (const cue of enabledAnnouncements) { if (!userGestureSignals.includes(cue)) { - let { audioCue, alert } = configurationService.getValue<{ audioCue: string; alert?: string }>(cue.signalSettingsKey); - alert = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (alert) { - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); + announcement = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (announcement) { + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } } } - for (const cue of disabledAlerts) { - const alert = userGestureSignals.includes(cue) ? 'never' : 'off'; - const audioCue = configurationService.getValue(cue.signalSettingsKey + '.audioCue'); - configurationService.updateValue(cue.signalSettingsKey, { audioCue, alert }); + for (const cue of disabledAnnouncements) { + const announcement = userGestureSignals.includes(cue) ? 'never' : 'off'; + const sound = configurationService.getValue(cue.settingsKey + '.sound'); + configurationService.updateValue(cue.settingsKey, { sound, announcement }); } qp.hide(); }); - qp.placeholder = localize('alert.help.placeholder', 'Select an alert to configure'); + qp.placeholder = localize('announcement.help.placeholder', 'Select an announcement to configure'); qp.canSelectMany = true; await qp.show(); } From c4bea7bd3d2133326760b5d5e5f9625d1da12b87 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 12 Feb 2024 09:32:56 -0800 Subject: [PATCH 0179/1863] Support Tabs vs Spaces in notebook editors (#204783) * settings override for notebook indentation + new statusbar entry * remove dead code for statusbar command. coming in dif PR * disposed checks --- .../browser/parts/editor/editorStatus.ts | 12 +++ .../editorStatusBar/editorStatusBar.ts | 82 ++++++++++++++++++- .../browser/controller/editActions.ts | 1 + .../notebook/browser/notebookOptions.ts | 2 +- .../view/cellParts/cellEditorOptions.ts | 64 ++++++++++++++- .../browser/view/cellParts/codeCell.ts | 38 +++++++-- .../browser/view/cellParts/markupCell.ts | 36 +++++++- 7 files changed, 220 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 0c08179077ac0..968668456a4d2 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -449,6 +449,12 @@ class EditorStatus extends Disposable { return; } + const editorURI = getCodeEditor(this.editorService.activeTextEditorControl)?.getModel()?.uri; + if (editorURI?.scheme === Schemas.vscodeNotebookCell) { + this.selectionElement.clear(); + return; + } + const props: IStatusbarEntry = { name: localize('status.editor.selection', "Editor Selection"), text, @@ -466,6 +472,12 @@ class EditorStatus extends Disposable { return; } + const editorURI = getCodeEditor(this.editorService.activeTextEditorControl)?.getModel()?.uri; + if (editorURI?.scheme === Schemas.vscodeNotebookCell) { + this.indentationElement.clear(); + return; + } + const props: IStatusbarEntry = { name: localize('status.editor.indentation', "Editor Indentation"), text, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 9dd8e75711773..68b5eba7d19a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -7,15 +7,17 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/ import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +// import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; import { getNotebookEditorFromEditorPane, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -254,3 +256,79 @@ export class ActiveCellStatus extends Disposable implements IWorkbenchContributi } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ActiveCellStatus, LifecyclePhase.Restored); + +export class NotebookIndentationStatus extends Disposable implements IWorkbenchContribution { + + private readonly _itemDisposables = this._register(new DisposableStore()); + private readonly _accessor = this._register(new MutableDisposable()); + + static readonly ID = 'selectNotebookIndentation'; + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + this._register(this._editorService.onDidActiveEditorChange(() => this._update())); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) { + this._update(); + } + })); + } + + private _update() { + this._itemDisposables.clear(); + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (activeEditor) { + this._show(activeEditor); + } else { + this._accessor.clear(); + } + } + + private _show(editor: INotebookEditor) { + if (!editor.hasModel()) { + this._accessor.clear(); + return; + } + + const cellEditorOverrides = this._configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + + const indentSize = cellEditorOverrides['editor.indentSize'] ?? this._configurationService.getValue('editor.indentSize'); + const tabSize = cellEditorOverrides['editor.tabSize'] ?? this._configurationService.getValue('editor.tabSize'); + const insertSpaces = cellEditorOverrides['editor.insertSpaces'] ?? this._configurationService.getValue('editor.insertSpaces'); + + const width = typeof indentSize === 'number' ? indentSize : tabSize; + + const message = insertSpaces ? `Spaces: ${width}` : `Tab Size: ${width}`; + const newText = message; + if (!newText) { + this._accessor.clear(); + return; + } + + const entry: IStatusbarEntry = { + name: nls.localize('notebook.indentation', "Notebook Indentation"), + text: newText, + ariaLabel: newText, + // tooltip: nls.localize('selectNotebookIndentation', "Select Notebook Indentation"), + tooltip: nls.localize('selectNotebookIndentation', "Notebook Indentation"), + // command: SELECT_NOTEBOOK_INDENTATION_ID // TODO@Yoyokrazy -- finish hooking this up + }; + + if (!this._accessor.value) { + this._accessor.value = this._statusbarService.addEntry( + entry, + 'notebook.status.indentation', + StatusbarAlignment.RIGHT, + 100.4 + ); + } else { + this._accessor.value.update(entry); + } + } +} + +registerWorkbenchContribution2(NotebookIndentationStatus.ID, NotebookIndentationStatus, WorkbenchPhase.AfterRestored); // TODO@Yoyokrazy -- unsure on the phase diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index d43cc47b2c793..59f95276c9fab 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -38,6 +38,7 @@ const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; export const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; +export const SELECT_NOTEBOOK_INDENTATION_ID = 'notebook.selectIndentation'; registerAction2(class EditCellAction extends NotebookCellAction { constructor() { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index c4356bd6ed013..36ed3544b98b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -20,7 +20,7 @@ const SCROLLABLE_ELEMENT_PADDING_TOP = 18; export const OutputInnerContainerTopPadding = 4; -export interface NotebookDisplayOptions { +export interface NotebookDisplayOptions { // TODO @Yoyokrazy rename to a more generic name, not display showCellStatusBar: ShowCellStatusBarType; cellToolbarLocation: string | { [key: string]: string }; cellToolbarInteraction: string; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts index 0804f29c3acec..fe000cac9479a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions.ts @@ -21,14 +21,56 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell import { NotebookCellInternalMetadata, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; +import { ITextModelUpdateOptions } from 'vs/editor/common/model'; -export class CellEditorOptions extends CellContentPart { +//todo@Yoyokrazy implenets is needed or not? +export class CellEditorOptions extends CellContentPart implements ITextModelUpdateOptions { private _lineNumbers: 'on' | 'off' | 'inherit' = 'inherit'; + private _tabSize?: number; + private _indentSize?: number | 'tabSize'; + private _insertSpaces?: boolean; + + set tabSize(value: number | undefined) { + if (this._tabSize !== value) { + this._tabSize = value; + this._onDidChange.fire(); + } + } + + get tabSize() { + return this._tabSize; + } + + set indentSize(value: number | 'tabSize' | undefined) { + if (this._indentSize !== value) { + this._indentSize = value; + this._onDidChange.fire(); + } + } + + get indentSize() { + return this._indentSize; + } + + set insertSpaces(value: boolean | undefined) { + if (this._insertSpaces !== value) { + this._insertSpaces = value; + this._onDidChange.fire(); + } + } + + get insertSpaces() { + return this._insertSpaces; + } + private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; private _value: IEditorOptions; - constructor(private readonly base: IBaseCellEditorOptions, readonly notebookOptions: NotebookOptions, readonly configurationService: IConfigurationService) { + constructor( + private readonly base: IBaseCellEditorOptions, + readonly notebookOptions: NotebookOptions, + readonly configurationService: IConfigurationService) { super(); this._register(base.onDidChange(() => { @@ -50,7 +92,23 @@ export class CellEditorOptions extends CellContentPart { } private _computeEditorOptions() { - const value = this.base.value; + const value = this.base.value; // base IEditorOptions + + // TODO @Yoyokrazy find a different way to get the editor overrides, this is not the right way + const cellEditorOverridesRaw = this.notebookOptions.getDisplayOptions().editorOptionsCustomizations; + const indentSize = cellEditorOverridesRaw['editor.indentSize']; + if (indentSize !== undefined) { + this.indentSize = indentSize; + } + const insertSpaces = cellEditorOverridesRaw['editor.insertSpaces']; + if (insertSpaces !== undefined) { + this.insertSpaces = insertSpaces; + } + const tabSize = cellEditorOverridesRaw['editor.tabSize']; + if (tabSize !== undefined) { + this.tabSize = tabSize; + } + let cellRenderLineNumber = value.lineNumbers; switch (this._lineNumbers) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index cdfd236913a1c..a74983b6b5fae 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -42,6 +42,7 @@ export class CodeCell extends Disposable { private readonly cellParts: CellPartsCollection; private _collapsedExecutionIcon: CollapsedCodeCellExecutionIcon; + private _cellEditorOptions: CellEditorOptions; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, @@ -52,13 +53,13 @@ export class CodeCell extends Disposable { @IOpenerService openerService: IOpenerService, @ILanguageService private readonly languageService: ILanguageService, @IConfigurationService private configurationService: IConfigurationService, - @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, ) { super(); - const cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); + this._cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: outputDisplayLimit }); - this.cellParts = this._register(templateData.cellParts.concatContentPart([cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); + this.cellParts = this._register(templateData.cellParts.concatContentPart([this._cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); // this.viewCell.layoutInfo.editorHeight or estimation when this.viewCell.layoutInfo.editorHeight === 0 const editorHeight = this.calculateInitEditorHeight(); @@ -135,9 +136,29 @@ export class CodeCell extends Disposable { this._register(Event.runAndSubscribe(viewCell.onDidChangeOutputs, this.updateForOutputs.bind(this))); this._register(Event.runAndSubscribe(viewCell.onDidChangeLayout, this.updateForLayout.bind(this))); - cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); - this._register(cellEditorOptions.onDidChange(() => templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)))); - templateData.editor.updateOptions(cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + this._cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); + this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(templateData))); + templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + } + + private updateCodeCellOptions(templateData: CodeCellRenderTemplate) { + templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(this.viewCell.resolveTextModel(), cts.token).then(model => { + if (this._isDisposed) { + return; + } + + if (model) { + model.updateOptions({ + indentSize: this._cellEditorOptions.indentSize, + tabSize: this._cellEditorOptions.tabSize, + insertSpaces: this._cellEditorOptions.insertSpaces, + }); + } + }); } private _pendingLayout: IDisposable | undefined; @@ -186,6 +207,11 @@ export class CodeCell extends Disposable { if (model && this.templateData.editor) { this._reigsterModelListeners(model); this.templateData.editor.setModel(model); + model.updateOptions({ + indentSize: this._cellEditorOptions.indentSize, + tabSize: this._cellEditorOptions.tabSize, + insertSpaces: this._cellEditorOptions.insertSpaces, + }); this.viewCell.attachTextEditor(this.templateData.editor, this.viewCell.layoutInfo.estimatedHasHorizontalScrolling); const focusEditorIfNeeded = () => { if ( diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index 7636dbf004c93..5806dd97b5f1c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -44,6 +44,7 @@ export class MarkupCell extends Disposable { private foldingState: CellFoldingState; private cellEditorOptions: CellEditorOptions; private editorOptions: IEditorOptions; + private _isDisposed: boolean = false; constructor( private readonly notebookEditor: IActiveNotebookEditorDelegate, @@ -174,9 +175,31 @@ export class MarkupCell extends Disposable { } })); - this._register(this.cellEditorOptions.onDidChange(() => { - this.updateEditorOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); - })); + this._register(this.cellEditorOptions.onDidChange(() => this.updateMarkupCellOptions())); + } + + private updateMarkupCellOptions(): any { + this.updateEditorOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + if (this.editor) { + this.editor.updateOptions(this.cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); + + const cts = new CancellationTokenSource(); + this._register({ dispose() { cts.dispose(true); } }); + raceCancellation(this.viewCell.resolveTextModel(), cts.token).then(model => { + if (this._isDisposed) { + return; + } + + if (model) { + model.updateOptions({ + indentSize: this.cellEditorOptions.indentSize, + tabSize: this.cellEditorOptions.tabSize, + insertSpaces: this.cellEditorOptions.insertSpaces, + }); + } + }); + } } private updateCollapsedState() { @@ -223,6 +246,8 @@ export class MarkupCell extends Disposable { } override dispose() { + this._isDisposed = true; + // move focus back to the cell list otherwise the focus goes to body if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor && (this.notebookEditor.hasEditorFocus() || this.notebookEditor.getDomNode().ownerDocument.activeElement === this.notebookEditor.getDomNode().ownerDocument.body)) { this.notebookEditor.focusContainer(); @@ -354,6 +379,11 @@ export class MarkupCell extends Disposable { } this.editor!.setModel(model); + model.updateOptions({ + indentSize: this.cellEditorOptions.indentSize, + tabSize: this.cellEditorOptions.tabSize, + insertSpaces: this.cellEditorOptions.insertSpaces, + }); const realContentHeight = this.editor!.getContentHeight(); if (realContentHeight !== editorHeight) { From 7d04ffa55d8bffcd4edcdeba556986e6f1b460b8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 12 Feb 2024 09:57:12 -0800 Subject: [PATCH 0180/1863] fix: rerender empty editor hint if a file changes readonly status (#205016) --- .../browser/emptyTextEditorHint/emptyTextEditorHint.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index c103f95c23aed..fb540a708d1cd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -83,6 +83,11 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.toDispose.push(this.editor.onDidChangeModelContent(() => this.update())); this.toDispose.push(this.inlineChatService.onDidChangeProviders(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.update())); + this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + if (e.hasChanged(EditorOption.readOnly)) { + this.update(); + } + })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(emptyTextEditorHintSetting)) { this.update(); From f24957e5d21fec398564e6f1d33043fbb6f4a3f3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:02:26 -0600 Subject: [PATCH 0181/1863] Fix issues --- .../browser/accessibilitySignalService.ts | 22 ++++---- .../accessibilitySignals/browser/commands.ts | 54 +++++++++---------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 1e1262ec66853..915a61aba5bf9 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -178,9 +178,9 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi private readonly isSoundEnabledCache = new Cache((event: { readonly signal: AccessibilitySignal; readonly userGesture?: boolean }) => { const settingObservable = observableFromEvent( Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.signal.settingsKey) || e.affectsConfiguration(event.signal.settingsKey) + e.affectsConfiguration(event.signal.legacySoundSettingsKey) || e.affectsConfiguration(event.signal.settingsKey) ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.audioCue') + () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.sound') ); return derived(reader => { /** @description sound enabled */ @@ -211,7 +211,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration(event.signal.legacyAnnouncementSettingsKey!) || e.affectsConfiguration(event.signal.settingsKey) ), - () => event.signal.announcementMessage ? this.configurationService.getValue(event.signal.settingsKey + '.announcement') : false + () => event.signal.announcementMessage ? this.configurationService.getValue<'auto' | 'off' | 'userGesture' | 'always' | 'never'>(event.signal.settingsKey + '.announcement') : false ); return derived(reader => { /** @description alert enabled */ @@ -221,23 +221,23 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi ) { return false; } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; + return setting === 'auto' || setting === 'always' || setting === 'userGesture' && event.userGesture; }); }, JSON.stringify); - public isAnnouncementEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - if (!cue.announcementMessage) { + public isAnnouncementEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean { + if (!signal.announcementMessage) { return false; } - return this.isAnnouncementEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + return this.isAnnouncementEnabledCache.get({ signal, userGesture }).get() ?? false; } - public isSoundEnabled(cue: AccessibilitySignal, userGesture?: boolean): boolean { - return this.isSoundEnabledCache.get({ signal: cue, userGesture }).get() ?? false; + public isSoundEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean { + return this.isSoundEnabledCache.get({ signal, userGesture }).get() ?? false; } - public onSoundEnabledChanged(cue: AccessibilitySignal): Event { - return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal: cue })); + public onSoundEnabledChanged(signal: AccessibilitySignal): Event { + return Event.fromObservableLight(this.isSoundEnabledCache.get({ signal })); } public onAnnouncementEnabledChanged(cue: AccessibilitySignal): Event { diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 2ec576a22e196..5a7bacde1c5f2 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -41,28 +41,26 @@ export class ShowSignalSoundHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); qp.onDidAccept(() => { - const enabledCues = qp.selectedItems.map(i => i.signal); - const disabledCues = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledCues.includes(cue)); - for (const cue of enabledCues) { - if (!userGestureSignals.includes(cue)) { - let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); - sound = accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; - if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); - } else { - configurationService.updateValue(cue.settingsKey, { sound }); - } + const enabledSounds = qp.selectedItems.map(i => i.signal); + const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); + for (const signal of enabledSounds) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + sound = userGestureSignals.includes(signal) ? 'userGesture' : accessibilityService.isScreenReaderOptimized() ? 'auto' : 'on'; + if (announcement) { + configurationService.updateValue(signal.settingsKey, { sound, announcement }); + } else { + configurationService.updateValue(signal.settingsKey, { sound }); } } - for (const cue of disabledCues) { - const announcement = cue.announcementMessage ? configurationService.getValue(cue.settingsKey + '.announcement') : undefined; - const sound = userGestureSignals.includes(cue) ? 'never' : 'off'; + for (const signal of disabledSounds) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + sound = userGestureSignals.includes(signal) ? 'never' : 'off'; if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } else { - configurationService.updateValue(cue.settingsKey, { sound }); + configurationService.updateValue(signal.settingsKey, { sound }); } } qp.hide(); @@ -104,23 +102,21 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal)); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); - for (const cue of enabledAnnouncements) { - if (!userGestureSignals.includes(cue)) { - let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(cue.settingsKey); - announcement = cue.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (announcement) { - configurationService.updateValue(cue.settingsKey, { sound, announcement }); - } + for (const signal of enabledAnnouncements) { + let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); + announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; + if (announcement) { + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } } - for (const cue of disabledAnnouncements) { - const announcement = userGestureSignals.includes(cue) ? 'never' : 'off'; - const sound = configurationService.getValue(cue.settingsKey + '.sound'); - configurationService.updateValue(cue.settingsKey, { sound, announcement }); + for (const signal of disabledAnnouncements) { + const announcement = userGestureSignals.includes(signal) ? 'never' : 'off'; + const sound = configurationService.getValue(signal.settingsKey + '.sound'); + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } qp.hide(); }); From 4e4cc4c257ff20ec09fdffdb65470c9f5788b60b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:12:37 -0600 Subject: [PATCH 0182/1863] fix other issue --- .../contrib/accessibilitySignals/browser/commands.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 5a7bacde1c5f2..50db5214d76b5 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -109,14 +109,12 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { for (const signal of enabledAnnouncements) { let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; - if (announcement) { - configurationService.updateValue(signal.settingsKey, { sound, announcement }); - } + configurationService.updateValue(signal.settingsKey, { sound, announcement }); } for (const signal of disabledAnnouncements) { const announcement = userGestureSignals.includes(signal) ? 'never' : 'off'; const sound = configurationService.getValue(signal.settingsKey + '.sound'); - configurationService.updateValue(signal.settingsKey, { sound, announcement }); + configurationService.updateValue(signal.settingsKey, announcement ? { sound, announcement } : { sound }); } qp.hide(); }); From d9c1ee54b775801f23d345a884bdf777d24d8f21 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 15:20:12 -0300 Subject: [PATCH 0183/1863] Parameterize agentId and command in followups (#204905) * Parameterize agentId and command in followups For #199908 * Update * Safety * Fix build --- .../api/browser/mainThreadChatAgents2.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 31 ++++++++------- .../api/common/extHostTypeConverters.ts | 38 +++++++++++++++++-- .../contrib/chat/browser/chatFollowups.ts | 18 +++++++-- .../contrib/chat/browser/chatWidget.ts | 12 +++++- .../contrib/chat/common/chatAgents.ts | 8 ++-- .../contrib/chat/common/chatService.ts | 2 + .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../chat/test/common/chatService.test.ts | 4 +- .../chat/test/common/voiceChat.test.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 10 ++++- .../vscode.proposed.chatAgents2Additions.d.ts | 4 +- 13 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index cdd867d016068..3229725e96a8b 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -87,12 +87,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._pendingProgress.delete(request.requestId); } }, - provideFollowups: async (result, token): Promise => { + provideFollowups: async (request, result, token): Promise => { if (!this._agents.get(handle)?.hasFollowups) { return []; } - return this._proxy.$provideFollowups(handle, result, token); + return this._proxy.$provideFollowups(request, handle, result, token); }, get lastSlashCommands() { return lastSlashCommands; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 7626fb4364f1c..964afc507dc85 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1222,7 +1222,7 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise; + $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; $invokeCompletionProvider(handle: number, query: string, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 65a95d5374e58..a807fca2d00f5 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -278,14 +278,15 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.provideSlashCommands(token); } - $provideFollowups(handle: number, result: IChatAgentResult, token: CancellationToken): Promise { + async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { return Promise.resolve([]); } const ehResult = typeConvert.ChatAgentResult.to(result); - return agent.provideFollowups(ehResult, token); + return (await agent.provideFollowups(ehResult, token)) + .map(f => typeConvert.ChatFollowup.from(f, request)); } $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void { @@ -309,23 +310,19 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { Object.freeze({ result: ehResult, kind })); } - $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void { + $acceptAction(handle: number, result: IChatAgentResult, event: IChatUserActionEvent): void { const agent = this._agents.get(handle); if (!agent) { return; } - if (action.action.kind === 'vote') { + if (event.action.kind === 'vote') { // handled by $acceptFeedback return; } - const ehResult = typeConvert.ChatAgentResult.to(result); - if (action.action.kind === 'command') { - const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: typeConvert.ChatResponseProgress.toProgressContent(action.action.commandButton, this.commands.converter) as vscode.ChatAgentCommandButton }; - agent.acceptAction(Object.freeze({ action: commandAction, result: ehResult })); - return; - } else { - agent.acceptAction(Object.freeze({ action: action.action, result: ehResult })); + const ehAction = typeConvert.ChatAgentUserActionEvent.to(result, event, this.commands.converter); + if (ehAction) { + agent.acceptAction(Object.freeze(ehAction)); } } @@ -354,7 +351,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return; } - return await agent.provideSampleQuestions(token); + return (await agent.provideSampleQuestions(token)) + .map(f => typeConvert.ChatFollowup.from(f, undefined)); } } @@ -418,7 +416,7 @@ class ExtHostChatAgent { })); } - async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -429,7 +427,8 @@ class ExtHostChatAgent { return followups // Filter out "command followups" from older providers .filter(f => !(f && 'commandId' in f)) - .map(f => typeConvert.ChatFollowup.from(f)); + // Filter out followups from older providers before 'message' changed to 'prompt' + .filter(f => !(f && 'message' in f)); } async provideWelcomeMessage(token: CancellationToken): Promise<(string | IMarkdownString)[] | undefined> { @@ -449,7 +448,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -458,7 +457,7 @@ class ExtHostChatAgent { return []; } - return content?.map(f => typeConvert.ChatFollowup.from(f)); + return content; } get apiAgent(): vscode.ChatAgent2 { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index f41b939a927aa..2a3a71e9029a6 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -36,7 +36,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/edit import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -2194,14 +2194,26 @@ export namespace DataTransfer { } export namespace ChatFollowup { - export function from(followup: vscode.ChatAgentFollowup): IChatFollowup { + export function from(followup: vscode.ChatAgentFollowup, request: IChatAgentRequest | undefined): IChatFollowup { return { kind: 'reply', - message: followup.message, + agentId: followup.agentId ?? request?.agentId ?? '', + subCommand: followup.subCommand ?? request?.command, + message: followup.prompt, title: followup.title, tooltip: followup.tooltip, }; } + + export function to(followup: IChatFollowup): vscode.ChatAgentFollowup { + return { + prompt: followup.message, + title: followup.title, + agentId: followup.agentId, + subCommand: followup.subCommand, + tooltip: followup.tooltip, + }; + } } export namespace ChatInlineFollowup { @@ -2642,6 +2654,26 @@ export namespace ChatAgentResult { } } +export namespace ChatAgentUserActionEvent { + export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatAgentUserActionEvent | undefined { + if (event.action.kind === 'vote') { + // Is the "feedback" type + return; + } + + const ehResult = ChatAgentResult.to(result); + if (event.action.kind === 'command') { + const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatAgentCommandButton }; + return { action: commandAction, result: ehResult }; + } else if (event.action.kind === 'followUp') { + const followupAction: vscode.ChatAgentFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; + return { action: followupAction, result: ehResult }; + } else { + return { action: event.action, result: ehResult }; + } + } +} + export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 70e92d3f5b130..827de16655c5d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -9,6 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -42,9 +43,20 @@ export class ChatFollowups extend button.element.classList.add('interactive-followup-command'); } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); - const label = followup.kind === 'reply' ? - '$(sparkle) ' + (followup.title || followup.message) : - followup.title; + let prefix = ''; + if ('agentId' in followup && followup.agentId) { + prefix += `${chatAgentLeader}${followup.agentId} `; + if ('subCommand' in followup && followup.subCommand) { + prefix += `${chatSubcommandLeader}${followup.subCommand} `; + } + } + + let label = ''; + if (followup.kind === 'reply') { + label = '$(sparkle) ' + (followup.title || (prefix + followup.message)); + } else { + label = followup.title; + } button.label = new MarkdownString(label, { supportThemeIcons: true }); this._register(button.onDidClick(() => this.clickHandler(followup))); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 13bd6f3d7727e..e243033f71330 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -33,7 +33,7 @@ import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common 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 } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IParsedChatRequest, chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -443,7 +443,15 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - this.acceptInput(e.followup.message); + let msg = ''; + if (e.followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { + msg = `${chatAgentLeader}${e.followup.agentId} `; + if (e.followup.subCommand) { + msg += `${chatSubcommandLeader}${e.followup.subCommand} `; + } + } + msg += e.followup.message; + this.acceptInput(msg); if (!e.response) { // Followups can be shown by the welcome message, then there is no response associated. diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index e5d20e0db45e8..8647b65ad4310 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -31,7 +31,7 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - provideFollowups?(result: IChatAgentResult, token: CancellationToken): Promise; + provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; lastSlashCommands?: IChatAgentCommand[]; provideSlashCommands(token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; @@ -107,7 +107,7 @@ export interface IChatAgentService { readonly onDidChangeAgents: Event; registerAgent(agent: IChatAgent): IDisposable; invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; - getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise; + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; getAgents(): Array; getAgent(id: string): IChatAgent | undefined; getDefaultAgent(): IChatAgent | undefined; @@ -186,7 +186,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return await data.agent.invoke(request, progress, history, token); } - async getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { + async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id}`); @@ -196,6 +196,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return []; } - return data.agent.provideFollowups(result, token); + return data.agent.provideFollowups(request, result, token); } } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 665ecb9eb290b..4764e9f23bd4b 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -159,6 +159,8 @@ export interface IChatProvider { export interface IChatFollowup { kind: 'reply'; message: string; + agentId: string; + subCommand?: string; title?: string; tooltip?: string; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 8c363983bb196..1191afb209a07 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -568,7 +568,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; - agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, agentResult, followupsCancelToken); + agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { message, variables: {} }); // contributed slash commands diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 37ef4f4b4bd6e..3358df8cd4dcd 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -24,7 +24,7 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -78,7 +78,7 @@ const chatAgentWithUsedContext: IChatAgent = { return { metadata: { metadataKey: 'value' } }; }, async provideFollowups(sessionId, token) { - return [{ kind: 'reply', message: 'Something else', tooltip: 'a tooltip' }]; + return [{ kind: 'reply', message: 'Something else', agentId: '', tooltip: 'a tooltip' } satisfies IChatFollowup]; }, }; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index d40cdccffc87e..bfa4baf30e936 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -44,7 +44,7 @@ suite('VoiceChat', () => { readonly onDidChangeAgents = Event.None; registerAgent(agent: IChatAgent): IDisposable { throw new Error(); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } - getFollowups(id: string, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } + getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise { throw new Error(); } getAgents(): Array { return agents; } getAgent(id: string): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 9f0f46769f159..4ec0f8a92fc2d 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -216,8 +216,16 @@ declare module 'vscode' { export interface ChatAgentFollowup { /** * The message to send to the chat. + * TODO@API is it ok for variables to resolved from the text of this prompt, using the `#` syntax? */ - message: string; + prompt: string; + + /** + * By default, the followup goes to the same agent/subCommand. But these properties can be set to override that. + */ + agentId?: string; + + subCommand?: string; /** * A tooltip to show when hovering over the followup. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 712d31b296b59..f42f874d0dd3b 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -208,7 +208,7 @@ declare module 'vscode' { commandButton: ChatAgentCommandButton; } - export interface ChatAgentSessionFollowupAction { + export interface ChatAgentFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; followup: ChatAgentFollowup; @@ -221,7 +221,7 @@ declare module 'vscode' { export interface ChatAgentUserActionEvent { readonly result: ChatAgentResult2; - readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentSessionFollowupAction | ChatAgentBugReportAction; + readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentFollowupAction | ChatAgentBugReportAction; } export interface ChatVariableValue { From 0e1ccb789954fde827f6c67602174a2eed3d2458 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 12:36:55 -0600 Subject: [PATCH 0184/1863] Fix test issue --- .../test/browser/inlineCompletionsProvider.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index aa5c7a4e4a638..4c846025949f6 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -776,8 +776,8 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( } options.serviceCollection.set(ILanguageFeaturesService, languageFeaturesService); options.serviceCollection.set(IAccessibilitySignalService, { - playAudioCue: async () => { }, - isEnabled(cue: unknown) { return false; }, + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, } as any); const d = languageFeaturesService.inlineCompletionsProvider.register({ pattern: '**' }, options.provider); disposableStore.add(d); From 7ff2572a3e1ac8a038045790dd14660441317417 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:45:06 -0800 Subject: [PATCH 0185/1863] remove leftover console log in quickfix (#205022) removed leftover log --- .../src/languageFeatures/quickFix.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index afc489c0521b3..a627670c70abb 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -363,7 +363,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Mon, 12 Feb 2024 19:46:00 +0100 Subject: [PATCH 0186/1863] Set settings directly from the release notes (#204832) * Set settings directly from the release notes Fixes #204338 * Fix build --- build/lib/i18n.resources.json | 4 + src/vs/base/common/network.ts | 5 + .../browser/markdownDocumentRenderer.ts | 9 +- .../browser/markdownSettingRenderer.ts | 187 ++++++++++++++++++ .../browser/markdownSettingRenderer.test.ts | 147 ++++++++++++++ .../update/browser/releaseNotesEditor.ts | 63 ++++-- 6 files changed, 399 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts create mode 100644 src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index cf74aac44be46..e4acd66ea52df 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -62,6 +62,10 @@ "name": "vs/workbench/contrib/mappedEdits", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/markdown", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 3e0cc0071af57..5dde8ca080b7f 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -111,6 +111,11 @@ export namespace Schemas { * Scheme used for the Source Control commit input's text document */ export const vscodeSourceControl = 'vscode-scm'; + + /** + * Scheme used for special rendering of settings in the release notes + */ + export const codeSetting = 'code-setting'; } export function matchesScheme(target: URI | string, scheme: string): boolean { diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index fb9cf55df7871..fce923cad92a6 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -13,6 +13,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { escape } from 'vs/base/common/strings'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; export const DEFAULT_MARKDOWN_STYLES = ` body { @@ -195,6 +196,7 @@ export async function renderMarkdownDocument( shouldSanitize: boolean = true, allowUnknownProtocols: boolean = false, token?: CancellationToken, + settingRenderer?: SimpleSettingRenderer ): Promise { const highlight = (code: string, lang: string | undefined, callback: ((error: any, code: string) => void) | undefined): any => { @@ -220,8 +222,13 @@ export async function renderMarkdownDocument( return ''; }; + const renderer = new marked.Renderer(); + if (settingRenderer) { + renderer.html = settingRenderer.getHtmlRenderer(); + } + return new Promise((resolve, reject) => { - marked(text, { highlight }, (err, value) => err ? reject(err) : resolve(value)); + marked(text, { highlight, renderer }, (err, value) => err ? reject(err) : resolve(value)); }).then(raw => { if (shouldSanitize) { return sanitize(raw, allowUnknownProtocols); diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts new file mode 100644 index 0000000000000..b593321746b60 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -0,0 +1,187 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; + +const codeSettingRegex = /^/; + +export class SimpleSettingRenderer { + private defaultSettings: DefaultSettings; + private updatedSettings = new Map(); // setting ID to user's original setting value + private encounteredSettings = new Map(); // setting ID to setting + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + this.defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + } + + getHtmlRenderer(): (html: string) => string { + return (html): string => { + const match = codeSettingRegex.exec(html); + if (match && match.length === 3) { + const settingId = match[1]; + const rendered = this.render(settingId, match[2]); + if (rendered) { + html = html.replace(codeSettingRegex, rendered); + } + } + return html; + }; + } + + private settingsGroups: ISettingsGroup[] | undefined = undefined; + private getSetting(settingId: string): ISetting | undefined { + if (!this.settingsGroups) { + this.settingsGroups = this.defaultSettings.getSettingsGroups(); + } + if (this.encounteredSettings.has(settingId)) { + return this.encounteredSettings.get(settingId); + } + for (const group of this.settingsGroups) { + for (const section of group.sections) { + for (const setting of section.settings) { + if (setting.key === settingId) { + this.encounteredSettings.set(settingId, setting); + return setting; + } + } + } + } + return undefined; + } + + parseValue(settingId: string, value: string): any { + if (value === 'undefined') { + return undefined; + } + const setting = this.getSetting(settingId); + if (!setting) { + return value; + } + + switch (setting.type) { + case 'boolean': + return value === 'true'; + case 'number': + return parseInt(value, 10); + case 'string': + default: + return value; + } + } + + private render(settingId: string, newValue: string): string | undefined { + const setting = this.getSetting(settingId); + if (!setting) { + return ''; + } + + return this.renderSetting(setting, newValue); + } + + private viewInSettings(settingId: string, alreadySet: boolean): string { + let message: string; + if (alreadySet) { + const displayName = settingKeyToDisplayFormat(settingId); + message = nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); + } else { + message = nls.localize('viewInSettings', "View in Settings"); + } + return `${message}`; + } + + private renderRestorePreviousSetting(settingId: string): string { + const displayName = settingKeyToDisplayFormat(settingId); + const value = this.updatedSettings.get(settingId); + const message = nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); + return `${message}`; + } + + private renderBooleanSetting(setting: ISetting, value: string): string | undefined { + const booleanValue: boolean = value === 'true' ? true : false; + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === booleanValue || (currentValue === undefined && setting.value === booleanValue)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + let message: string; + if (booleanValue) { + message = nls.localize('trueMessage', "Enable \"{0}: {1}\" now", displayName.category, displayName.label); + } else { + message = nls.localize('falseMessage', "Disable \"{0}: {1}\" now", displayName.category, displayName.label); + } + return `${message}`; + } + + private renderStringSetting(setting: ISetting, value: string): string | undefined { + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === value || (currentValue === undefined && setting.value === value)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + const message = nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\" now", displayName.category, displayName.label, value); + return `${message}`; + } + + private renderNumberSetting(setting: ISetting, value: string): string | undefined { + const numberValue: number = parseInt(value, 10); + const currentValue = this.configurationService.getValue(setting.key); + if (currentValue === numberValue || (currentValue === undefined && setting.value === numberValue)) { + return undefined; + } + + const displayName = settingKeyToDisplayFormat(setting.key); + const message = nls.localize('numberValue', "Set \"{0}: {1}\" to {2} now", displayName.category, displayName.label, numberValue); + return `${message}`; + + } + + private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { + let renderedSetting: string | undefined; + + if (newValue !== undefined) { + if (this.updatedSettings.has(setting.key)) { + renderedSetting = this.renderRestorePreviousSetting(setting.key); + } else if (setting.type === 'boolean') { + renderedSetting = this.renderBooleanSetting(setting, newValue); + } else if (setting.type === 'string') { + renderedSetting = this.renderStringSetting(setting, newValue); + } else if (setting.type === 'number') { + renderedSetting = this.renderNumberSetting(setting, newValue); + } + } + + if (!renderedSetting) { + return `(${this.viewInSettings(setting.key, true)})`; + } + + return nls.localize({ key: 'fullRenderedSetting', comment: ['A pair of already localized links. The first argument is a link to change a setting, the second is a link to view the setting.'] }, + "({0} | {1})", renderedSetting, this.viewInSettings(setting.key, false),); + } + + async updateSettingValue(uri: URI) { + if (uri.scheme !== Schemas.codeSetting) { + return; + } + const settingId = uri.authority; + const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); + const oldSettingValue = this.configurationService.inspect(settingId).userValue; + if (newSettingValue === this.updatedSettings.get(settingId)) { + this.updatedSettings.delete(settingId); + } else { + this.updatedSettings.set(settingId, oldSettingValue); + } + await this.configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); + } +} diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts new file mode 100644 index 0000000000000..bc215847e4911 --- /dev/null +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; + +const configuration: IConfigurationNode = { + 'id': 'examples', + 'title': 'Examples', + 'type': 'object', + 'properties': { + 'example.booleanSetting': { + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION + }, + 'example.booleanSetting2': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION + }, + 'example.stringSetting': { + 'type': 'string', + 'default': 'one', + 'scope': ConfigurationScope.APPLICATION + }, + 'example.numberSetting': { + 'type': 'number', + 'default': 3, + 'scope': ConfigurationScope.APPLICATION + } + } +}; + +class MarkdownConfigurationService extends TestConfigurationService { + override async updateValue(key: string, value: any): Promise { + const [section, setting] = key.split('.'); + return this.setUserConfiguration(section, { [setting]: value }); + } +} + +suite('Markdown Setting Renderer Test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + let configurationService: TestConfigurationService; + let settingRenderer: SimpleSettingRenderer; + + suiteSetup(() => { + configurationService = new MarkdownConfigurationService(); + Registry.as(Extensions.Configuration).registerConfiguration(configuration); + settingRenderer = new SimpleSettingRenderer(configurationService); + }); + + suiteTeardown(() => { + Registry.as(Extensions.Configuration).deregisterConfigurations([configuration]); + }); + + test('render boolean setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: Boolean Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Enable "Example: Boolean Setting" now | View in Settings)`); + + const htmlWithValueSetToFalse = ''; + const renderedHtmlWithValueSetToFalse = htmlRenderer(htmlWithValueSetToFalse); + assert.equal(renderedHtmlWithValueSetToFalse, + `(Disable "Example: Boolean Setting2" now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: Boolean Setting" in Settings)`); + }); + + test('render string setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: String Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: String Setting" to "two" now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: String Setting" in Settings)`); + }); + + test('render number setting', () => { + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlNoValue = ''; + const renderedHtmlNoValue = htmlRenderer(htmlNoValue); + assert.equal(renderedHtmlNoValue, + `(View "Example: Number Setting" in Settings)`); + + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: Number Setting" to 2 now | View in Settings)`); + + const htmlSameValue = ''; + const renderedHtmlSameValue = htmlRenderer(htmlSameValue); + assert.equal(renderedHtmlSameValue, + `(View "Example: Number Setting" in Settings)`); + }); + + test('updating and restoring the setting through the renderer changes what is rendered', async () => { + await configurationService.setUserConfiguration('example', { stringSetting: 'two' }); + const htmlRenderer = settingRenderer.getHtmlRenderer(); + const htmlWithValue = ''; + const renderedHtmlWithValue = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValue, + `(Set "Example: String Setting" to "three" now | View in Settings)`); + assert.equal(configurationService.getValue('example.stringSetting'), 'two'); + + // Update the value + await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/three`)); + assert.equal(configurationService.getValue('example.stringSetting'), 'three'); + const renderedHtmlWithValueAfterUpdate = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValueAfterUpdate, + `(Restore value of "Example: String Setting" | View in Settings)`); + + // Restore the value + await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/two`)); + assert.equal(configurationService.getValue('example.stringSetting'), 'two'); + const renderedHtmlWithValueAfterRestore = htmlRenderer(htmlWithValue); + assert.equal(renderedHtmlWithValueAfterRestore, + `(Set "Example: String Setting" to "three" now | View in Settings)`); + }); +}); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 411c6105a7382..27846ded77bae 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -30,10 +30,14 @@ import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/comm import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Schemas } from 'vs/base/common/network'; 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; @@ -50,20 +54,28 @@ export class ReleaseNotesManager { @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IExtensionService private readonly _extensionService: IExtensionService, - @IProductService private readonly _productService: IProductService + @IProductService private readonly _productService: IProductService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - TokenizationRegistry.onDidChange(async () => { - if (!this._currentReleaseNotes || !this._lastText) { - return; - } - const html = await this.renderBody(this._lastText); - if (this._currentReleaseNotes) { - this._currentReleaseNotes.webview.setHtml(html); - } + TokenizationRegistry.onDidChange(() => { + return this.updateHtml(); }); _configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); _webviewWorkbenchService.onDidChangeActiveWebviewEditor(this.onDidChangeActiveWebviewEditor, this, this.disposables); + this._simpleSettingRenderer = this._instantiationService.createInstance(SimpleSettingRenderer); + } + + private async updateHtml() { + 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 } }); + } } public async show(version: string): Promise { @@ -102,6 +114,8 @@ 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; } })); @@ -204,10 +218,15 @@ export class ReleaseNotesManager { return this._releaseNotesCache.get(version)!; } - private onDidClickLink(uri: URI) { - this.addGAParameters(uri, 'ReleaseNotes') - .then(updated => this._openerService.open(updated)) - .then(undefined, onUnexpectedError); + private async onDidClickLink(uri: URI) { + if (uri.scheme === Schemas.codeSetting) { + await this._simpleSettingRenderer.updateSettingValue(uri); + this.updateHtml(); + } else { + this.addGAParameters(uri, 'ReleaseNotes') + .then(updated => this._openerService.open(updated, { allowCommands: ['workbench.action.openSettings'] })) + .then(undefined, onUnexpectedError); + } } private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise { @@ -221,7 +240,7 @@ export class ReleaseNotesManager { private async renderBody(text: string) { const nonce = generateUuid(); - const content = await renderMarkdownDocument(text, this._extensionService, this._languageService, false); + const content = await renderMarkdownDocument(text, this._extensionService, this._languageService, false, undefined, undefined, this._simpleSettingRenderer); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; const showReleaseNotes = Boolean(this._configurationService.getValue('update.showReleaseNotes')); @@ -267,9 +286,23 @@ 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); } }); + window.onscroll = () => { + vscode.postMessage({ + type: 'scroll', + value: { + scrollPosition: { + x: window.scrollX, + y: window.scrollY + } + } + }); + }; + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); From 66713a49cb6b8c044dfa7a7c64a4b86e41c6ec8f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 20:08:34 +0100 Subject: [PATCH 0187/1863] zoom - do not localise codicons (#205024) --- src/vs/workbench/electron-sandbox/window.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 35136a73c5d5f..6880e360e0274 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -1076,9 +1076,9 @@ export class NativeWindow extends BaseWindow { let text: string | undefined = undefined; if (currentZoomLevel < this.configuredWindowZoomLevel) { - text = localize('zoomedOut', "$(zoom-out)"); + text = '$(zoom-out)'; } else if (currentZoomLevel > this.configuredWindowZoomLevel) { - text = localize('zoomedIn', "$(zoom-in)"); + text = '$(zoom-in)'; } entry.updateZoomEntry(text ?? false, targetWindowId); From 1dd2c349f234a6befc1bd5fa1d104e324090a371 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 12 Feb 2024 20:28:47 +0100 Subject: [PATCH 0188/1863] voice - fix leading whitespace in transcription (#205026) --- .../contrib/chat/electron-sandbox/actions/voiceChatActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 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 3d826bcaba8f5..41083a4bb9b06 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -295,7 +295,7 @@ class VoiceChatSessions { break; case SpeechToTextStatus.Recognizing: if (text) { - session.controller.updateInput([inputValue, text].join(' ')); + session.controller.updateInput(inputValue ? [inputValue, text].join(' ') : text); if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { acceptTranscriptionScheduler.cancel(); } @@ -303,7 +303,7 @@ class VoiceChatSessions { break; case SpeechToTextStatus.Recognized: if (text) { - inputValue = [inputValue, text].join(' '); + inputValue = inputValue ? [inputValue, text].join(' ') : text; session.controller.updateInput(inputValue); if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { acceptTranscriptionScheduler.schedule(); From fcb468f4aef585fefd3de735c38566f1c0476e33 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Mon, 12 Feb 2024 11:33:31 -0800 Subject: [PATCH 0189/1863] add class to re-use style from the rest of the debug views (#205030) --- .../browser/contrib/notebookVariables/notebookVariablesView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 3e4cf3cf7fc8c..82360512546e1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -79,6 +79,7 @@ export class NotebookVariablesView extends ViewPane { protected override renderBody(container: HTMLElement): void { super.renderBody(container); + this.element.classList.add('debug-pane'); this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, From 5451a4e69a305a8146808e1957f67252195dcd81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:49:24 -0800 Subject: [PATCH 0190/1863] Try use shell integration in debug terminals Fixes #204694 --- src/vs/platform/terminal/common/terminal.ts | 6 ++++++ src/vs/platform/terminal/node/terminalEnvironment.ts | 2 +- src/vs/workbench/api/browser/mainThreadTerminalService.ts | 1 + src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostTerminalService.ts | 2 ++ src/vs/workbench/api/node/extHostDebugService.ts | 3 +++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a1890a..4dea050e336d1 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -608,6 +608,12 @@ export interface IShellLaunchConfig { */ isTransient?: boolean; + /** + * Attempt to force shell integration to be enabled by bypassing the {@link isFeatureTerminal} + * equals false requirement. + */ + forceShellIntegration?: boolean; + /** * Create a terminal without shell integration even when it's enabled */ diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 066ca8f6bc131..703e0fec62322 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -117,7 +117,7 @@ export function getShellIntegrationInjection( // - There is no executable (not sure what script to run) // - The terminal is used by a feature like tasks or debugging const useWinpty = isWindows && (!options.windowsEnableConpty || getWindowsBuildNumber() < 18309); - if (!options.shellIntegration.enabled || !shellLaunchConfig.executable || shellLaunchConfig.isFeatureTerminal || shellLaunchConfig.hideFromUser || shellLaunchConfig.ignoreShellIntegration || useWinpty) { + if (!options.shellIntegration.enabled || !shellLaunchConfig.executable || (shellLaunchConfig.isFeatureTerminal && !shellLaunchConfig.forceShellIntegration) || shellLaunchConfig.hideFromUser || shellLaunchConfig.ignoreShellIntegration || useWinpty) { return undefined; } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 2293708c63b83..28bc27cbb1278 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -152,6 +152,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape ? (id, cols, rows) => new TerminalProcessExtHostProxy(id, cols, rows, this._terminalService) : undefined, extHostTerminalId, + forceShellIntegration: launchConfig.forceShellIntegration, isFeatureTerminal: launchConfig.isFeatureTerminal, isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal, useShellEnvironment: launchConfig.useShellEnvironment, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 964afc507dc85..4ed54e7b0e36d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -495,6 +495,7 @@ export interface TerminalLaunchConfig { strictEnv?: boolean; hideFromUser?: boolean; isExtensionCustomPtyTerminal?: boolean; + forceShellIntegration?: boolean; isFeatureTerminal?: boolean; isExtensionOwnedTerminal?: boolean; useShellEnvironment?: boolean; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index e004acba809ce..cbe28de19ca8d 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -63,6 +63,7 @@ interface IEnvironmentVariableCollection extends vscode.EnvironmentVariableColle export interface ITerminalInternalOptions { cwd?: string | URI; isFeatureTerminal?: boolean; + forceShellIntegration?: boolean; useShellEnvironment?: boolean; resolvedExtHostIdentifier?: ExtHostTerminalIdentifier; /** @@ -165,6 +166,7 @@ export class ExtHostTerminal { initialText: options.message ?? undefined, strictEnv: options.strictEnv ?? undefined, hideFromUser: options.hideFromUser ?? undefined, + forceShellIntegration: internalOptions?.forceShellIntegration ?? undefined, isFeatureTerminal: internalOptions?.isFeatureTerminal ?? undefined, isExtensionOwnedTerminal: true, useShellEnvironment: internalOptions?.useShellEnvironment ?? undefined, diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 1ccfd2101daad..90b977aa644bc 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -106,6 +106,9 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { giveShellTimeToInitialize = true; terminal = this._terminalService.createTerminalFromOptions(options, { isFeatureTerminal: true, + // Since debug termnials are REPLs, we want shell integration to be enabled. + // Ignore isFeatureTerminal when evaluating shell integration enablement. + forceShellIntegration: true, useShellEnvironment: true }); this._integratedTerminalInstances.insert(terminal, shellConfig); From b79d48c296a18057450205fe65029dabebb08ddc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:52:59 -0600 Subject: [PATCH 0191/1863] 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 6c44b62619c12..c2bbe8aa74ade 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 fc85c9a5a50ab..1467a9a86efe5 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 f8818faa97a22fd0aa213c5fa3027f7b51b13916 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:55:23 -0800 Subject: [PATCH 0192/1863] Add new shell language IDs to chatCodeblockActions.ts --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 592ee91e25658..cee0785180404 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -387,7 +387,10 @@ export function registerChatCodeBlockActions() { const shellLangIds = [ 'fish', + 'ps1', + 'pwsh', 'powershell', + 'sh', 'shellscript', 'zsh' ]; From d06c669a8daceb7169e5083b53ade71b1e81c401 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:57:18 -0600 Subject: [PATCH 0193/1863] Fix more issues --- .../inlineCompletions/browser/commands.ts | 2 +- .../browser/inlineCompletionsController.ts | 12 +- .../audioCues/browser/audioCueService.ts | 593 ------------------ .../files/test/browser/editorAutoSave.test.ts | 5 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 6 +- .../test/browser/bufferContentTracker.test.ts | 5 +- .../test/browser/workbenchTestServices.ts | 5 +- 7 files changed, 22 insertions(+), 606 deletions(-) delete mode 100644 src/vs/platform/audioCues/browser/audioCueService.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts index 9727bec235901..ddfde90862d25 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/commands.ts @@ -76,7 +76,7 @@ export class TriggerInlineSuggestionAction extends EditorAction { await asyncTransaction(async tx => { /** @description triggerExplicitly from command */ await controller?.model.get()?.triggerExplicitly(tx); - controller?.playAudioCue(tx); + controller?.playAccessibilitySignal(tx); }); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 14f427b5c7059..320a8f1726482 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -78,7 +78,7 @@ export class InlineCompletionsController extends Disposable { { min: 50, max: 50 } ); - private readonly _playAudioCueSignal = observableSignal(this); + private readonly _playAccessibilitySignal = observableSignal(this); private readonly _isReadonly = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.readOnly)); private readonly _textModel = observableFromEvent(this.editor.onDidChangeModel, () => this.editor.getModel()); @@ -213,14 +213,14 @@ export class InlineCompletionsController extends Disposable { let lastInlineCompletionId: string | undefined = undefined; this._register(autorunHandleChanges({ handleChange: (context, changeSummary) => { - if (context.didChange(this._playAudioCueSignal)) { + if (context.didChange(this._playAccessibilitySignal)) { lastInlineCompletionId = undefined; } return true; }, }, async reader => { - /** @description InlineCompletionsController.playAudioCueAndReadSuggestion */ - this._playAudioCueSignal.read(reader); + /** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */ + this._playAccessibilitySignal.read(reader); const model = this.model.read(reader); const state = model?.state.read(reader); @@ -249,8 +249,8 @@ export class InlineCompletionsController extends Disposable { this.editor.updateOptions({ inlineCompletionsAccessibilityVerbose: this._configurationService.getValue('accessibility.verbosity.inlineCompletions') }); } - public playAudioCue(tx: ITransaction) { - this._playAudioCueSignal.trigger(tx); + public playAccessibilitySignal(tx: ITransaction) { + this._playAccessibilitySignal.trigger(tx); } private provideScreenReaderUpdate(content: string): void { diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts deleted file mode 100644 index 7c7dfbe056719..0000000000000 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ /dev/null @@ -1,593 +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, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { FileAccess } from 'vs/base/common/network'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { localize } from 'vs/nls'; -import { observableFromEvent, derived } from 'vs/base/common/observable'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - -export const IAudioCueService = createDecorator('audioCue'); - -export interface IAudioCueService { - readonly _serviceBrand: undefined; - playAudioCue(cue: AudioCue, options?: IAudioCueOptions): Promise; - playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise; - isCueEnabled(cue: AudioCue): boolean; - isAlertEnabled(cue: AudioCue): boolean; - onEnabledChanged(cue: AudioCue): Event; - onAlertEnabledChanged(cue: AudioCue): Event; - - playSound(cue: Sound, allowManyInParallel?: boolean): Promise; - playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable; -} - -export interface IAudioCueOptions { - allowManyInParallel?: boolean; - source?: string; - /** - * For actions like save or format, depending on the - * configured value, we will only - * play the sound if the user triggered the action. - */ - userGesture?: boolean; -} - -export class AudioCueService extends Disposable implements IAudioCueService { - readonly _serviceBrand: undefined; - private readonly sounds: Map = new Map(); - private readonly screenReaderAttached = observableFromEvent( - this.accessibilityService.onDidChangeScreenReaderOptimized, - () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() - ); - private readonly sentTelemetry = new Set(); - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - ) { - super(); - } - - public async playAudioCue(cue: AudioCue, options: IAudioCueOptions = {}): Promise { - const alertMessage = cue.alertMessage; - if (this.isAlertEnabled(cue, options.userGesture) && alertMessage) { - this.accessibilityService.status(alertMessage); - } - - if (this.isCueEnabled(cue, options.userGesture)) { - this.sendAudioCueTelemetry(cue, options.source); - await this.playSound(cue.sound.getSound(), options.allowManyInParallel); - } - } - - public async playAudioCues(cues: (AudioCue | { cue: AudioCue; source: string })[]): Promise { - for (const cue of cues) { - this.sendAudioCueTelemetry('cue' in cue ? cue.cue : cue, 'source' in cue ? cue.source : undefined); - } - const cueArray = cues.map(c => 'cue' in c ? c.cue : c); - const alerts = cueArray.filter(cue => this.isAlertEnabled(cue)).map(c => c.alertMessage); - if (alerts.length) { - this.accessibilityService.status(alerts.join(', ')); - } - - // Some audio cues might reuse sounds. Don't play the same sound twice. - const sounds = new Set(cueArray.filter(cue => this.isCueEnabled(cue)).map(cue => cue.sound.getSound())); - await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true))); - - } - - - private sendAudioCueTelemetry(cue: AudioCue, source: string | undefined): void { - const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized(); - const key = cue.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : ''); - // Only send once per user session - if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) { - return; - } - this.sentTelemetry.add(key); - - this.telemetryService.publicLog2<{ - audioCue: string; - source: string; - isScreenReaderOptimized: boolean; - }, { - owner: 'hediet'; - - audioCue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The audio cue that was played.' }; - source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the audio cue (e.g. "diffEditorNavigation").' }; - isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' }; - - comment: 'This data is collected to understand how audio cues are used and if more audio cues should be added.'; - }>('audioCue.played', { - audioCue: cue.name, - source: source ?? '', - isScreenReaderOptimized, - }); - } - - private getVolumeInPercent(): number { - const volume = this.configurationService.getValue('audioCues.volume'); - if (typeof volume !== 'number') { - return 50; - } - - return Math.max(Math.min(volume, 100), 0); - } - - private readonly playingSounds = new Set(); - - public async playSound(sound: Sound, allowManyInParallel = false): Promise { - if (!allowManyInParallel && this.playingSounds.has(sound)) { - return; - } - this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/audioCues/browser/media/${sound.fileName}`).toString(true); - - try { - const sound = this.sounds.get(url); - if (sound) { - sound.volume = this.getVolumeInPercent() / 100; - sound.currentTime = 0; - await sound.play(); - } else { - const playedSound = await playAudio(url, this.getVolumeInPercent() / 100); - this.sounds.set(url, playedSound); - } - } catch (e) { - if (!e.message.includes('play() can only be initiated by a user gesture')) { - // tracking this issue in #178642, no need to spam the console - console.error('Error while playing sound', e); - } - } finally { - this.playingSounds.delete(sound); - } - } - - public playAudioCueLoop(cue: AudioCue, milliseconds: number): IDisposable { - let playing = true; - const playSound = () => { - if (playing) { - this.playAudioCue(cue, { allowManyInParallel: true }).finally(() => { - setTimeout(() => { - if (playing) { - playSound(); - } - }, milliseconds); - }); - } - }; - playSound(); - return toDisposable(() => playing = false); - } - - private readonly obsoleteAudioCuesEnabled = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration('audioCues.enabled') - ), - () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>('audioCues.enabled') - ); - - private readonly isCueEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.settingsKey) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => this.configurationService.getValue<'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never'>(event.cue.signalSettingsKey + '.audioCue') - ); - return derived(reader => { - /** @description audio cue enabled */ - const setting = settingObservable.read(reader); - if ( - setting === 'on' || - (setting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } else if (setting === 'always' || setting === 'userGesture' && event.userGesture) { - return true; - } - - const obsoleteSetting = this.obsoleteAudioCuesEnabled.read(reader); - if ( - obsoleteSetting === 'on' || - (obsoleteSetting === 'auto' && this.screenReaderAttached.read(reader)) - ) { - return true; - } - - return false; - }); - }, JSON.stringify); - - private readonly isAlertEnabledCache = new Cache((event: { readonly cue: AudioCue; readonly userGesture?: boolean }) => { - const settingObservable = observableFromEvent( - Event.filter(this.configurationService.onDidChangeConfiguration, (e) => - e.affectsConfiguration(event.cue.alertSettingsKey!) || e.affectsConfiguration(event.cue.signalSettingsKey) - ), - () => event.cue.alertSettingsKey ? this.configurationService.getValue(event.cue.signalSettingsKey + '.alert') : false - ); - return derived(reader => { - /** @description alert enabled */ - const setting = settingObservable.read(reader); - if ( - !this.screenReaderAttached.read(reader) - ) { - return false; - } - return setting === true || setting === 'always' || setting === 'userGesture' && event.userGesture; - }); - }, JSON.stringify); - - public isAlertEnabled(cue: AudioCue, userGesture?: boolean): boolean { - if (!cue.alertSettingsKey) { - return false; - } - return this.isAlertEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public isCueEnabled(cue: AudioCue, userGesture?: boolean): boolean { - return this.isCueEnabledCache.get({ cue, userGesture }).get() ?? false; - } - - public onEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isCueEnabledCache.get({ cue })); - } - - public onAlertEnabledChanged(cue: AudioCue): Event { - return Event.fromObservableLight(this.isAlertEnabledCache.get({ cue })); - } -} - - -/** - * Play the given audio url. - * @volume value between 0 and 1 - */ -function playAudio(url: string, volume: number): Promise { - return new Promise((resolve, reject) => { - const audio = new Audio(url); - audio.volume = volume; - audio.addEventListener('ended', () => { - resolve(audio); - }); - audio.addEventListener('error', (e) => { - // When the error event fires, ended might not be called - reject(e.error); - }); - audio.play().catch(e => { - // When play fails, the error event is not fired. - reject(e); - }); - }); -} - -class Cache { - private readonly map = new Map(); - constructor(private readonly getValue: (value: TArg) => TValue, private readonly getKey: (value: TArg) => unknown) { - } - - public get(arg: TArg): TValue { - if (this.map.has(arg)) { - return this.map.get(arg)!; - } - - const value = this.getValue(arg); - const key = this.getKey(arg); - this.map.set(key, value); - return value; - } -} - -/** - * Corresponds to the audio files in ./media. -*/ -export class Sound { - private static register(options: { fileName: string }): Sound { - const sound = new Sound(options.fileName); - return sound; - } - - public static readonly error = Sound.register({ fileName: 'error.mp3' }); - public static readonly warning = Sound.register({ fileName: 'warning.mp3' }); - public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' }); - public static readonly break = Sound.register({ fileName: 'break.mp3' }); - public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' }); - public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' }); - public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' }); - public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' }); - public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); - public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); - public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); - public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); - public static readonly chatResponsePending = Sound.register({ fileName: 'chatResponsePending.mp3' }); - public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); - public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); - public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); - public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); - public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); - public static readonly save = Sound.register({ fileName: 'save.mp3' }); - public static readonly format = Sound.register({ fileName: 'format.mp3' }); - - private constructor(public readonly fileName: string) { } -} - -export class SoundSource { - constructor( - public readonly randomOneOf: Sound[] - ) { } - - public getSound(deterministic = false): Sound { - if (deterministic || this.randomOneOf.length === 1) { - return this.randomOneOf[0]; - } else { - const index = Math.floor(Math.random() * this.randomOneOf.length); - return this.randomOneOf[index]; - } - } -} - -export const enum AccessibilityAlertSettingId { - Save = 'accessibility.alert.save', - Format = 'accessibility.alert.format', - Clear = 'accessibility.alert.clear', - Breakpoint = 'accessibility.alert.breakpoint', - Error = 'accessibility.alert.error', - Warning = 'accessibility.alert.warning', - FoldedArea = 'accessibility.alert.foldedArea', - TerminalQuickFix = 'accessibility.alert.terminalQuickFix', - TerminalBell = 'accessibility.alert.terminalBell', - TerminalCommandFailed = 'accessibility.alert.terminalCommandFailed', - TaskCompleted = 'accessibility.alert.taskCompleted', - TaskFailed = 'accessibility.alert.taskFailed', - ChatRequestSent = 'accessibility.alert.chatRequestSent', - NotebookCellCompleted = 'accessibility.alert.notebookCellCompleted', - NotebookCellFailed = 'accessibility.alert.notebookCellFailed', - OnDebugBreak = 'accessibility.alert.onDebugBreak', - NoInlayHints = 'accessibility.alert.noInlayHints', - LineHasBreakpoint = 'accessibility.alert.lineHasBreakpoint', - ChatResponsePending = 'accessibility.alert.chatResponsePending' -} - - -export class AudioCue { - private static _audioCues = new Set(); - private static register(options: { - name: string; - sound: Sound | { - /** - * Gaming and other apps often play a sound variant when the same event happens again - * for an improved experience. This option enables audio cues to play a random sound. - */ - randomOneOf: Sound[]; - }; - settingsKey: string; - signalSettingsKey: string; - alertSettingsKey?: AccessibilityAlertSettingId; - alertMessage?: string; - }): AudioCue { - const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]); - const audioCue = new AudioCue(soundSource, options.name, options.settingsKey, options.signalSettingsKey, options.alertSettingsKey, options.alertMessage); - AudioCue._audioCues.add(audioCue); - return audioCue; - } - - public static get allAudioCues() { - return [...this._audioCues]; - } - - public static readonly error = AudioCue.register({ - name: localize('audioCues.lineHasError.name', 'Error on Line'), - sound: Sound.error, - settingsKey: 'audioCues.lineHasError', - alertSettingsKey: AccessibilityAlertSettingId.Error, - alertMessage: localize('accessibility.signals.lineHasError', 'Error'), - signalSettingsKey: 'accessibility.signals.lineHasError' - }); - public static readonly warning = AudioCue.register({ - name: localize('audioCues.lineHasWarning.name', 'Warning on Line'), - sound: Sound.warning, - settingsKey: 'audioCues.lineHasWarning', - alertSettingsKey: AccessibilityAlertSettingId.Warning, - alertMessage: localize('accessibility.signals.lineHasWarning', 'Warning'), - signalSettingsKey: 'accessibility.signals.lineHasWarning' - }); - public static readonly foldedArea = AudioCue.register({ - name: localize('audioCues.lineHasFoldedArea.name', 'Folded Area on Line'), - sound: Sound.foldedArea, - settingsKey: 'audioCues.lineHasFoldedArea', - alertSettingsKey: AccessibilityAlertSettingId.FoldedArea, - alertMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'), - signalSettingsKey: 'accessibility.signals.lineHasFoldedArea' - }); - public static readonly break = AudioCue.register({ - name: localize('audioCues.lineHasBreakpoint.name', 'Breakpoint on Line'), - sound: Sound.break, - settingsKey: 'audioCues.lineHasBreakpoint', - alertSettingsKey: AccessibilityAlertSettingId.Breakpoint, - alertMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.lineHasBreakpoint' - }); - public static readonly inlineSuggestion = AudioCue.register({ - name: localize('audioCues.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.lineHasInlineSuggestion', - signalSettingsKey: 'accessibility.signals.lineHasInlineSuggestion' - }); - - public static readonly terminalQuickFix = AudioCue.register({ - name: localize('audioCues.terminalQuickFix.name', 'Terminal Quick Fix'), - sound: Sound.quickFixes, - settingsKey: 'audioCues.terminalQuickFix', - alertSettingsKey: AccessibilityAlertSettingId.TerminalQuickFix, - alertMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'), - signalSettingsKey: 'accessibility.signals.terminalQuickFix' - }); - - public static readonly onDebugBreak = AudioCue.register({ - name: localize('audioCues.onDebugBreak.name', 'Debugger Stopped on Breakpoint'), - sound: Sound.break, - settingsKey: 'audioCues.onDebugBreak', - alertSettingsKey: AccessibilityAlertSettingId.OnDebugBreak, - alertMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'), - signalSettingsKey: 'accessibility.signals.onDebugBreak' - }); - - public static readonly noInlayHints = AudioCue.register({ - name: localize('audioCues.noInlayHints', 'No Inlay Hints on Line'), - sound: Sound.error, - settingsKey: 'audioCues.noInlayHints', - alertSettingsKey: AccessibilityAlertSettingId.NoInlayHints, - alertMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'), - signalSettingsKey: 'accessibility.signals.noInlayHints' - }); - - public static readonly taskCompleted = AudioCue.register({ - name: localize('audioCues.taskCompleted', 'Task Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.taskCompleted', - alertSettingsKey: AccessibilityAlertSettingId.TaskCompleted, - alertMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'), - signalSettingsKey: 'accessibility.signals.taskCompleted' - }); - - public static readonly taskFailed = AudioCue.register({ - name: localize('audioCues.taskFailed', 'Task Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.taskFailed', - alertSettingsKey: AccessibilityAlertSettingId.TaskFailed, - alertMessage: localize('accessibility.signals.taskFailed', 'Task Failed'), - signalSettingsKey: 'accessibility.signals.taskFailed' - }); - - public static readonly terminalCommandFailed = AudioCue.register({ - name: localize('audioCues.terminalCommandFailed', 'Terminal Command Failed'), - sound: Sound.error, - settingsKey: 'audioCues.terminalCommandFailed', - alertSettingsKey: AccessibilityAlertSettingId.TerminalCommandFailed, - alertMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'), - signalSettingsKey: 'accessibility.signals.terminalCommandFailed' - }); - - public static readonly terminalBell = AudioCue.register({ - name: localize('audioCues.terminalBell', 'Terminal Bell'), - sound: Sound.terminalBell, - settingsKey: 'audioCues.terminalBell', - alertSettingsKey: AccessibilityAlertSettingId.TerminalBell, - alertMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'), - signalSettingsKey: 'accessibility.signals.terminalBell' - }); - - public static readonly notebookCellCompleted = AudioCue.register({ - name: localize('audioCues.notebookCellCompleted', 'Notebook Cell Completed'), - sound: Sound.taskCompleted, - settingsKey: 'audioCues.notebookCellCompleted', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellCompleted, - alertMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'), - signalSettingsKey: 'accessibility.signals.notebookCellCompleted' - }); - - public static readonly notebookCellFailed = AudioCue.register({ - name: localize('audioCues.notebookCellFailed', 'Notebook Cell Failed'), - sound: Sound.taskFailed, - settingsKey: 'audioCues.notebookCellFailed', - alertSettingsKey: AccessibilityAlertSettingId.NotebookCellFailed, - alertMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'), - signalSettingsKey: 'accessibility.signals.notebookCellFailed' - }); - - public static readonly diffLineInserted = AudioCue.register({ - name: localize('audioCues.diffLineInserted', 'Diff Line Inserted'), - sound: Sound.diffLineInserted, - settingsKey: 'audioCues.diffLineInserted', - signalSettingsKey: 'accessibility.signals.diffLineInserted' - }); - - public static readonly diffLineDeleted = AudioCue.register({ - name: localize('audioCues.diffLineDeleted', 'Diff Line Deleted'), - sound: Sound.diffLineDeleted, - settingsKey: 'audioCues.diffLineDeleted', - signalSettingsKey: 'accessibility.signals.diffLineDeleted' - }); - - public static readonly diffLineModified = AudioCue.register({ - name: localize('audioCues.diffLineModified', 'Diff Line Modified'), - sound: Sound.diffLineModified, - settingsKey: 'audioCues.diffLineModified', - signalSettingsKey: 'accessibility.signals.diffLineModified' - }); - - public static readonly chatRequestSent = AudioCue.register({ - name: localize('audioCues.chatRequestSent', 'Chat Request Sent'), - sound: Sound.chatRequestSent, - settingsKey: 'audioCues.chatRequestSent', - alertSettingsKey: AccessibilityAlertSettingId.ChatRequestSent, - alertMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), - signalSettingsKey: 'accessibility.signals.chatRequestSent' - }); - - public static readonly chatResponseReceived = AudioCue.register({ - name: localize('audioCues.chatResponseReceived', 'Chat Response Received'), - settingsKey: 'audioCues.chatResponseReceived', - sound: { - randomOneOf: [ - Sound.chatResponseReceived1, - Sound.chatResponseReceived2, - Sound.chatResponseReceived3, - Sound.chatResponseReceived4 - ] - }, - signalSettingsKey: 'accessibility.signals.chatResponseReceived' - }); - - public static readonly chatResponsePending = AudioCue.register({ - name: localize('audioCues.chatResponsePending', 'Chat Response Pending'), - sound: Sound.chatResponsePending, - settingsKey: 'audioCues.chatResponsePending', - alertSettingsKey: AccessibilityAlertSettingId.ChatResponsePending, - alertMessage: localize('accessibility.signals.chatResponsePending', 'Chat Response Pending'), - signalSettingsKey: 'accessibility.signals.chatResponsePending' - }); - - public static readonly clear = AudioCue.register({ - name: localize('audioCues.clear', 'Clear'), - sound: Sound.clear, - settingsKey: 'audioCues.clear', - alertSettingsKey: AccessibilityAlertSettingId.Clear, - alertMessage: localize('accessibility.signals.clear', 'Clear'), - signalSettingsKey: 'accessibility.signals.clear' - }); - - public static readonly save = AudioCue.register({ - name: localize('audioCues.save', 'Save'), - sound: Sound.save, - settingsKey: 'audioCues.save', - alertSettingsKey: AccessibilityAlertSettingId.Save, - alertMessage: localize('accessibility.signals.save', 'Save'), - signalSettingsKey: 'accessibility.signals.save' - }); - - public static readonly format = AudioCue.register({ - name: localize('audioCues.format', 'Format'), - sound: Sound.format, - settingsKey: 'audioCues.format', - alertSettingsKey: AccessibilityAlertSettingId.Format, - alertMessage: localize('accessibility.signals.format', 'Format'), - signalSettingsKey: 'accessibility.signals.format' - }); - - private constructor( - public readonly sound: SoundSource, - public readonly name: string, - public readonly settingsKey: string, - public readonly signalSettingsKey: string, - public readonly alertSettingsKey?: string, - public readonly alertMessage?: string, - ) { } -} diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 86fbc23d97833..e1ecd72a64909 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -43,7 +43,10 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index f9963ff03406c..d2fd3da2e0330 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -600,7 +600,7 @@ export class GotoPreviousChangeAction extends EditorAction { const index = model.findPreviousClosestChange(lineNumber, false); const change = model.changes[index]; - await playAudioCueForChange(change.change, accessibilitySignalService); + await playAccessibilitySymbolForChange(change.change, accessibilitySignalService); setPositionAndSelection(change.change, outerEditor, accessibilityService, codeEditorService); } } @@ -643,7 +643,7 @@ export class GotoNextChangeAction extends EditorAction { const index = model.findNextClosestChange(lineNumber, false); const change = model.changes[index].change; - await playAudioCueForChange(change, accessibilitySignalService); + await playAccessibilitySymbolForChange(change, accessibilitySignalService); setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService); } } @@ -658,7 +658,7 @@ function setPositionAndSelection(change: IChange, editor: ICodeEditor, accessibi } } -async function playAudioCueForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { +async function playAccessibilitySymbolForChange(change: IChange, accessibilitySignalService: IAccessibilitySignalService) { const changeType = getChangeType(change); switch (changeType) { case ChangeType.Add: diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 51fb47c8c6eb7..c71c20dd40663 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -67,7 +67,10 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(ILayoutService, new TestLayoutService()); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index be911f2f4c5e7..f6febaf3d2f41 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -282,7 +282,10 @@ export function workbenchInstantiationService( instantiationService.stub(IDialogService, new TestDialogService()); const accessibilityService = new TestAccessibilityService(); instantiationService.stub(IAccessibilityService, accessibilityService); - instantiationService.stub(IAccessibilitySignalService, { playAudioCue: async () => { }, isEnabled(cue: unknown) { return false; } } as any); + instantiationService.stub(IAccessibilitySignalService, { + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, + } as any); instantiationService.stub(IFileDialogService, instantiationService.createInstance(TestFileDialogService)); instantiationService.stub(ILanguageService, disposables.add(instantiationService.createInstance(LanguageService))); instantiationService.stub(ILanguageFeaturesService, new LanguageFeaturesService()); From d7d2bbbc2eeea3b4ae44d8199c35a4d193a90210 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 13:59:25 -0600 Subject: [PATCH 0194/1863] one more --- .../inlineCompletions/test/browser/suggestWidgetModel.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts index 969ab2fec427f..354fa36b5af81 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts @@ -161,8 +161,8 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel( [ILabelService, new class extends mock() { }], [IWorkspaceContextService, new class extends mock() { }], [IAccessibilitySignalService, { - playAudioCue: async () => { }, - isEnabled(cue: unknown) { return false; }, + playSignal: async () => { }, + isSoundEnabled(signal: unknown) { return false; }, } as any] ); From 05842e7e63c8550b0d256d93b710fcd23259df2f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 17:17:13 -0300 Subject: [PATCH 0195/1863] Support sticky agents (#204910) * Allow sticky chat agents Agents can be sticky and customize the repopulated placeholder. And normalize the API for sub commands See #199908 * Move code around to fix race condition and simplify * Fix build * Agents are sticky by default. Command sticky 'placeholder' is moved to chat agent additions * Rename repopulate to 'isSticky' * Rename --- .../api/common/extHostChatAgents2.ts | 20 +++-- src/vs/workbench/contrib/chat/browser/chat.ts | 2 + .../contrib/chat/browser/chatWidget.ts | 6 +- .../browser/contrib/chatInputEditorContrib.ts | 78 +++++++------------ .../contrib/chat/common/chatAgents.ts | 1 + .../contrib/chat/common/chatService.ts | 9 ++- .../contrib/chat/common/chatServiceImpl.ts | 30 +++---- .../vscode.proposed.chatAgents2.d.ts | 17 +--- .../vscode.proposed.chatAgents2Additions.d.ts | 14 ++++ 9 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index a807fca2d00f5..56518bd78e4b8 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -407,13 +407,19 @@ class ExtHostChatAgent { return []; } return result - .map(c => ({ - name: c.name, - description: c.description, - followupPlaceholder: c.followupPlaceholder, - shouldRepopulate: c.shouldRepopulate, - sampleRequest: c.sampleRequest - })); + .map(c => { + if ('repopulate2' in c) { + checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); + } + + return { + name: c.name, + description: c.description, + followupPlaceholder: c.isSticky2?.placeholder, + shouldRepopulate: c.isSticky2?.isSticky ?? c.isSticky, + sampleRequest: c.sampleRequest + }; + }); } async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ff95527a9828c..daa5ac41eb1ba 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -9,6 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -101,6 +102,7 @@ export type IChatWidgetViewContext = IChatViewViewContext | IChatResourceViewCon export interface IChatWidget { readonly onDidChangeViewModel: Event; readonly onDidAcceptInput: Event; + readonly onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>; readonly viewContext: IChatWidgetViewContext; readonly viewModel: IChatViewModel | undefined; readonly inputEditor: ICodeEditor; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index e243033f71330..ab61b6bde4f3f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -35,7 +35,7 @@ import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWel 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 { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; const $ = dom.$; @@ -73,6 +73,9 @@ export interface IChatWidgetContrib extends IDisposable { export class ChatWidget extends Disposable implements IChatWidget { public static readonly CONTRIBS: { new(...args: [IChatWidget, ...any]): IChatWidgetContrib }[] = []; + private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand?: IChatAgentCommand }>()); + public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; + private _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; @@ -586,6 +589,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (result) { const inputState = this.collectInputState(); this.inputPart.acceptInput(isUserQuery ? input : undefined, isUserQuery ? inputState : undefined); + this._onDidSubmitAgent.fire({ agent: result.agent, slashCommand: result.slashCommand }); result.responseCompletePromise.then(async () => { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index e5e44863dfd53..a6cf114ed3302 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -29,7 +29,6 @@ import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workben import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -39,8 +38,8 @@ const placeholderDecorationType = 'chat-session-detail'; const slashCommandTextDecorationType = 'chat-session-text'; const variableTextDecorationType = 'chat-variable-text'; -function agentAndCommandToKey(agent: string, subcommand: string): string { - return `${agent}__${subcommand}`; +function agentAndCommandToKey(agent: string, subcommand: string | undefined): string { + return subcommand ? `${agent}__${subcommand}` : agent; } class InputEditorDecorations extends Disposable { @@ -55,7 +54,6 @@ class InputEditorDecorations extends Disposable { private readonly widget: IChatWidget, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, - @IChatService private readonly chatService: IChatService, @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -72,10 +70,8 @@ class InputEditorDecorations extends Disposable { this.previouslyUsedAgents.clear(); this.updateInputEditorDecorations(); })); - this._register(this.chatService.onDidSubmitAgent((e) => { - if (e.sessionId === this.widget.viewModel?.sessionId) { - this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); - } + this._register(this.widget.onDidSubmitAgent((e) => { + this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand?.name)); })); this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations())); @@ -168,20 +164,24 @@ class InputEditorDecorations extends Disposable { return nextPart && nextPart instanceof ChatRequestTextPart && nextPart.text === ' '; }; + const getRangeForPlaceholder = (part: IParsedChatRequestPart) => ({ + startLineNumber: part.editorRange.startLineNumber, + endLineNumber: part.editorRange.endLineNumber, + startColumn: part.editorRange.endColumn + 1, + endColumn: 1000 + }); + const onlyAgentAndWhitespace = agentPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart); if (onlyAgentAndWhitespace) { // Agent reference with no other text - show the placeholder + const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, undefined)); + const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentPart.agent.metadata.followupPlaceholder; if (agentPart.agent.metadata.description && exactlyOneSpaceAfterPart(agentPart)) { placeholderDecoration = [{ - range: { - startLineNumber: agentPart.editorRange.startLineNumber, - endLineNumber: agentPart.editorRange.endLineNumber, - startColumn: agentPart.editorRange.endColumn + 1, - endColumn: 1000 - }, + range: getRangeForPlaceholder(agentPart), renderOptions: { after: { - contentText: agentPart.agent.metadata.description, + contentText: shouldRenderFollowupPlaceholder ? agentPart.agent.metadata.followupPlaceholder : agentPart.agent.metadata.description, color: this.getPlaceholderColor(), } } @@ -196,12 +196,7 @@ class InputEditorDecorations extends Disposable { const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder; if (agentSubcommandPart?.command.description && exactlyOneSpaceAfterPart(agentSubcommandPart)) { placeholderDecoration = [{ - range: { - startLineNumber: agentSubcommandPart.editorRange.startLineNumber, - endLineNumber: agentSubcommandPart.editorRange.endLineNumber, - startColumn: agentSubcommandPart.editorRange.endColumn + 1, - endColumn: 1000 - }, + range: getRangeForPlaceholder(agentSubcommandPart), renderOptions: { after: { contentText: shouldRenderFollowupPlaceholder ? agentSubcommandPart.command.followupPlaceholder : agentSubcommandPart.command.description, @@ -212,27 +207,6 @@ class InputEditorDecorations extends Disposable { } } - const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart); - if (onlySlashCommandAndWhitespace) { - // Command reference with no other text - show the placeholder - if (slashCommandPart.slashCommand.detail && exactlyOneSpaceAfterPart(slashCommandPart)) { - placeholderDecoration = [{ - range: { - startLineNumber: slashCommandPart.editorRange.startLineNumber, - endLineNumber: slashCommandPart.editorRange.endLineNumber, - startColumn: slashCommandPart.editorRange.endColumn + 1, - endColumn: 1000 - }, - renderOptions: { - after: { - contentText: slashCommandPart.slashCommand.detail, - color: this.getPlaceholderColor(), - } - } - }]; - } - } - this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); const textDecorations: IDecorationOptions[] | undefined = []; @@ -264,21 +238,23 @@ class InputEditorSlashCommandMode extends Disposable { constructor( private readonly widget: IChatWidget, - @IChatService private readonly chatService: IChatService ) { super(); - this._register(this.chatService.onDidSubmitAgent(e => { - if (this.widget.viewModel?.sessionId !== e.sessionId) { - return; - } - + this._register(this.widget.onDidSubmitAgent(e => { this.repopulateAgentCommand(e.agent, e.slashCommand); })); } - private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand) { - if (slashCommand.shouldRepopulate) { - const value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; + private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) { + let value: string | undefined; + if (slashCommand && slashCommand.shouldRepopulate) { + value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; + } else { + // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate + value = `${chatAgentLeader}${agent.id} `; + } + + if (value) { this.widget.inputEditor.setValue(value); this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 8647b65ad4310..289d3cb15b436 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -77,6 +77,7 @@ export interface IChatAgentMetadata { themeIcon?: ThemeIcon; sampleRequest?: string; supportIssueReporting?: boolean; + followupPlaceholder?: string; } diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 4764e9f23bd4b..769476c60a78e 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -262,13 +262,18 @@ export interface IChatTransferredSessionData { inputValue: string; } +export interface IChatSendRequestData { + responseCompletePromise: Promise; + agent: IChatAgentData; + slashCommand?: IChatAgentCommand; +} + export const IChatService = createDecorator('IChatService'); export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; onDidRegisterProvider: Event<{ providerId: string }>; onDidUnregisterProvider: Event<{ providerId: string }>; registerProvider(provider: IChatProvider): IDisposable; @@ -283,7 +288,7 @@ export interface IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string): Promise<{ responseCompletePromise: Promise } | undefined>; + sendRequest(sessionId: string, message: string): Promise; removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; clearSession(sessionId: string): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1191afb209a07..ebe25ec1b3ab8 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,13 +20,13 @@ 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 { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentHistoryEntry, 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, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -146,9 +146,6 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); - public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; - private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; @@ -438,7 +435,7 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data.providerId, data, CancellationToken.None); } - async sendRequest(sessionId: string, request: string): Promise<{ responseCompletePromise: Promise } | undefined> { + async sendRequest(sessionId: string, request: string): Promise { this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); @@ -461,8 +458,16 @@ export class ChatService extends Disposable implements IChatService { return; } + const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, request); + const agent = parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart)?.agent ?? this.chatAgentService.getDefaultAgent()!; + const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); + // This method is only returning whether the request was accepted - don't block on the actual request - return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; + return { + responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, parsedRequest), + agent, + slashCommand: agentSlashCommandPart?.command, + }; } private refreshFollowupsCancellationToken(sessionId: string): CancellationToken { @@ -473,10 +478,8 @@ export class ChatService extends Disposable implements IChatService { return newTokenSource.token; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, parsedRequest: IParsedChatRequest): Promise { const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); - const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); - let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); @@ -523,10 +526,6 @@ export class ChatService extends Disposable implements IChatService { }); try { - if (agentPart && agentSlashCommandPart?.command) { - this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); - } - let rawResult: IChatAgentResult | null | undefined; let agentOrCommandFollowups: Promise | undefined = undefined; @@ -570,7 +569,7 @@ export class ChatService extends Disposable implements IChatService { rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { - request = model.addRequest(parsedRequest, { message, variables: {} }); + request = model.addRequest(parsedRequest, { message: parsedRequest.text, variables: {} }); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -581,6 +580,7 @@ export class ChatService extends Disposable implements IChatService { history.push({ role: ChatMessageRole.User, content: request.message.text }); history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); } + const message = parsedRequest.text; const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { progressCallback(p); }), history, token); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 4ec0f8a92fc2d..d2a76eb8736c6 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -180,19 +180,10 @@ declare module 'vscode' { readonly sampleRequest?: string; /** - * Whether executing the command puts the - * chat into a persistent mode, where the - * command is prepended to the chat input. + * Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message. + * If this is not set, the chat input will fall back to the agent after submitting this command. */ - readonly shouldRepopulate?: boolean; - - /** - * Placeholder text to render in the chat input - * when the command has been repopulated. - * Has no effect if `shouldRepopulate` is `false`. - */ - // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? - readonly followupPlaceholder?: string; + readonly isSticky?: boolean; } export interface ChatAgentCommandProvider { @@ -221,7 +212,7 @@ declare module 'vscode' { prompt: string; /** - * By default, the followup goes to the same agent/subCommand. But these properties can be set to override that. + * By default, the followup goes to the same agent/command. But these properties can be set to override that. */ agentId?: string; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index f42f874d0dd3b..b934eeea9952c 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -230,4 +230,18 @@ declare module 'vscode' { */ kind?: string; } + + export interface ChatAgentCommand { + readonly isSticky2?: { + /** + * Indicates that the command should be automatically repopulated. + */ + isSticky: true; + + /** + * This can be set to a string to use a different placeholder message in the input box when the command has been repopulated. + */ + placeholder?: string; + }; + } } From 71770417094a265b1ab1dd5aaefada0662fde544 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 17:34:53 -0300 Subject: [PATCH 0196/1863] Fix title in chat history picker (#205029) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 14 ++++++++++---- .../contrib/chat/common/chatServiceImpl.ts | 5 ++--- .../workbench/contrib/chat/common/chatViewModel.ts | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 81bcad7a26c22..aac4dc60c4fe5 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -38,7 +38,7 @@ export interface IChatRequestModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; - readonly message: IParsedChatRequest | IChatFollowup; + readonly message: IParsedChatRequest; readonly variableData: IChatRequestVariableData; readonly response?: IChatResponseModel; } @@ -458,6 +458,14 @@ export enum ChatModelInitState { } export class ChatModel extends Disposable implements IChatModel { + static getDefaultTitle(requests: (ISerializableChatRequestData | IChatRequestModel)[]): string { + const firstRequestMessage = firstOrDefault(requests)?.message ?? ''; + const message = typeof firstRequestMessage === 'string' ? + firstRequestMessage : + firstRequestMessage.text; + return message.split('\n')[0].substring(0, 50); + } + private readonly _onDidDispose = this._register(new Emitter()); readonly onDidDispose = this._onDidDispose.event; @@ -527,9 +535,7 @@ export class ChatModel extends Disposable implements IChatModel { } get title(): string { - const firstRequestMessage = firstOrDefault(this._requests)?.message; - const message = firstRequestMessage?.text ?? ''; - return message.split('\n')[0].substring(0, 50); + return ChatModel.getDefaultTitle(this._requests); } constructor( diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index ebe25ec1b3ab8..cdbcda1853720 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -322,11 +322,10 @@ export class ChatService extends Disposable implements IChatService { .filter(session => !this._sessionModels.has(session.sessionId)) .filter(session => !session.isImported) .map(item => { - const firstRequestMessage = item.requests[0]?.message; + const title = ChatModel.getDefaultTitle(item.requests); return { sessionId: item.sessionId, - title: (typeof firstRequestMessage === 'string' ? firstRequestMessage : - firstRequestMessage?.text) ?? '', + title }; }); } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index bd2c325d728f9..1a95949446422 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -261,7 +261,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { } get messageText() { - return 'kind' in this.message ? this.message.message : this.message.text; + return this.message.text; } currentRenderedHeight: number | undefined; From a4611af71759f8a29ea71229c25e15927e650fc1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:43:25 +0100 Subject: [PATCH 0197/1863] SCM - add menu to configure incoming/outgoing (#205037) SCM - add menu to easily configure incoming/outgoing settings --- src/vs/platform/actions/common/actions.ts | 3 + .../contrib/scm/browser/media/scm.css | 9 + .../contrib/scm/browser/scmViewPane.ts | 154 +++++++++++++++++- 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 376627398acaa..0b3015ddf756a 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -108,10 +108,13 @@ export class MenuId { static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMInputBox = new MenuId('SCMInputBox'); + static readonly SCMChangesSeparator = new MenuId('SCMChangesSeparator'); static readonly SCMIncomingChanges = new MenuId('SCMIncomingChanges'); static readonly SCMIncomingChangesContext = new MenuId('SCMIncomingChangesContext'); + static readonly SCMIncomingChangesSetting = new MenuId('SCMIncomingChangesSetting'); static readonly SCMOutgoingChanges = new MenuId('SCMOutgoingChanges'); static readonly SCMOutgoingChangesContext = new MenuId('SCMOutgoingChangesContext'); + static readonly SCMOutgoingChangesSetting = new MenuId('SCMOutgoingChangesSetting'); static readonly SCMIncomingChangesAllChangesContext = new MenuId('SCMIncomingChangesAllChangesContext'); static readonly SCMIncomingChangesHistoryItemContext = new MenuId('SCMIncomingChangesHistoryItemContext'); static readonly SCMOutgoingChangesAllChangesContext = new MenuId('SCMOutgoingChangesAllChangesContext'); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 13eb48d7df4fc..c1c3290d585a5 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -210,6 +210,10 @@ border-top: 1px solid var(--vscode-sideBar-border); } +.scm-view .monaco-list-row .separator-container .actions { + padding-left: 6px; +} + .scm-view .monaco-list-row .history > .name, .scm-view .monaco-list-row .history-item-group > .name, .scm-view .monaco-list-row .resource-group > .name { @@ -255,6 +259,7 @@ .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view .monaco-list .monaco-list-row .separator-container > .actions, .scm-view .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row .history-item > .actions { display: none; @@ -268,6 +273,9 @@ .scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, +.scm-view .monaco-list .monaco-list-row:hover .separator-container > .actions, +.scm-view .monaco-list .monaco-list-row.selected .separator-container > .actions, +.scm-view .monaco-list .monaco-list-row.focused .separator-container > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.selected .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item-group > .actions, @@ -292,6 +300,7 @@ .scm-view.show-actions > .monaco-list .monaco-list-row .scm-input > .scm-editor > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view.show-actions > .monaco-list .monaco-list-row .separator-container > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .history-item-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .history-item > .actions { display: block; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 57f3428b3dcba..ac5b68e12b27b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -100,7 +100,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { stripIcons } from 'vs/base/common/iconLabels'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { foreground, listActiveSelectionForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; -import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IMenuWorkbenchToolBarOptions, MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { clamp } from 'vs/base/common/numbers'; @@ -1047,7 +1047,7 @@ class HistoryItemChangeRenderer implements ICompressibleTreeRenderer { @@ -1055,6 +1055,14 @@ class SeparatorRenderer implements ICompressibleTreeRenderer, index: number, templateData: SeparatorTemplate, height: number | undefined): void { templateData.label.setLabel(element.element.label, undefined, { title: element.element.ariaLabel }); @@ -1077,7 +1090,7 @@ class SeparatorRenderer implements ICompressibleTreeRenderer) { + super({ + ...desc, + f1: false, + toggled: ContextKeyExpr.equals(`config.${settingKey}`, settingValue), + }); + } + + override run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(this.settingKey, this.settingValue); + } +} + +MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { + title: localize('incomingChanges', "Show Incoming Changes"), + submenu: MenuId.SCMIncomingChangesSetting, + group: '1_incoming&outgoing', + order: 1 +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'always', + { + id: 'workbench.scm.action.showIncomingChanges.always', + title: localize('always', "Always"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'auto', + { + id: 'workbench.scm.action.showIncomingChanges.auto', + title: localize('auto', "Auto"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showIncomingChanges', 'never', + { + id: 'workbench.scm.action.showIncomingChanges.never', + title: localize('never', "Never"), + menu: { + id: MenuId.SCMIncomingChangesSetting, + } + }); + } +}); + +MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { + title: localize('outgoingChanges', "Show Outgoing Changes"), + submenu: MenuId.SCMOutgoingChangesSetting, + group: '1_incoming&outgoing', + order: 2 +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'always', + { + id: 'workbench.scm.action.showOutgoingChanges.always', + title: localize('always', "Always"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'auto', + { + id: 'workbench.scm.action.showOutgoingChanges.auto', + title: localize('auto', "Auto"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + } + }); + } +}); + +registerAction2(class extends SCMChangesSettingAction { + constructor() { + super('scm.showOutgoingChanges', 'never', + { + id: 'workbench.scm.action.showOutgoingChanges.never', + title: localize('never', "Never"), + menu: { + id: MenuId.SCMOutgoingChangesSetting, + } + }); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.scm.action.scm.showChangesSummary', + title: localize('showChangesSummary', "Show Changes Summary"), + f1: false, + toggled: ContextKeyExpr.equals('config.scm.showChangesSummary', true), + menu: { + id: MenuId.SCMChangesSeparator, + order: 3 + } + }); + } + + override run(accessor: ServicesAccessor) { + const configurationService = accessor.get(IConfigurationService); + const configValue = configurationService.getValue('scm.showChangesSummary') === true; + configurationService.updateValue('scm.showChangesSummary', !configValue); + } +}); + class RepositoryVisibilityAction extends Action2 { private repository: ISCMRepository; From a0d97ae9449d8665a531d8dd1dd58c4fe24332fe Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 15:49:32 -0600 Subject: [PATCH 0198/1863] 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 1de53d6193085..78fd7df671213 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 c2bbe8aa74ade..1d909e6fc300a 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 0000000000000..ae9ded44aecac --- /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 1467a9a86efe5..14a6ca567b48e 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 adfe67dca6f7901e714e69242c4f8fcae3a1bcd1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 12 Feb 2024 19:19:44 -0300 Subject: [PATCH 0199/1863] A few chat Followups fixes (#205038) * A few chat Followups fixes * Fix build --- .../workbench/api/common/extHostTypeConverters.ts | 4 ++-- .../contrib/chat/browser/chatFollowups.ts | 6 ++++-- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 14 ++++++++------ .../chat/browser/contrib/chatInputEditorContrib.ts | 3 ++- .../contrib/inlineChat/browser/inlineChatWidget.ts | 3 ++- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 2 +- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2a3a71e9029a6..62f12e9aae7a6 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2198,7 +2198,7 @@ export namespace ChatFollowup { return { kind: 'reply', agentId: followup.agentId ?? request?.agentId ?? '', - subCommand: followup.subCommand ?? request?.command, + subCommand: followup.command ?? request?.command, message: followup.prompt, title: followup.title, tooltip: followup.tooltip, @@ -2210,7 +2210,7 @@ export namespace ChatFollowup { prompt: followup.message, title: followup.title, agentId: followup.agentId, - subCommand: followup.subCommand, + command: followup.subCommand, tooltip: followup.tooltip, }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 827de16655c5d..87ab89782893f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -9,6 +9,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatFollowup } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -21,7 +22,8 @@ export class ChatFollowups extend followups: T[], private readonly options: IButtonStyles | undefined, private readonly clickHandler: (followup: T) => void, - private readonly contextService: IContextKeyService, + @IContextKeyService private readonly contextService: IContextKeyService, + @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); @@ -44,7 +46,7 @@ export class ChatFollowups extend } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); let prefix = ''; - if ('agentId' in followup && followup.agentId) { + if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { prefix += `${chatAgentLeader}${followup.agentId} `; if ('subCommand' in followup && followup.subCommand) { prefix += `${chatSubcommandLeader}${followup.subCommand} `; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index b69f086eb58bd..04b6ffc381065 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -347,7 +347,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(this.followupsContainer); if (items && items.length > 0) { - this.followupsDisposables.add(new ChatFollowups(this.followupsContainer, items, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }), this.contextKeyService)); + this.followupsDisposables.add(this.instantiationService.createInstance, ChatFollowups>(ChatFollowups, this.followupsContainer, items, undefined, followup => this._onDidAcceptFollowup.fire({ followup, response }))); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 972fe2d9aeb3d..194e012133d6a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -500,12 +500,14 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._onDidClickFollowup.fire(followup), - templateData.contextKeyService)); + const scopedInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, templateData.contextKeyService])); + templateData.elementDisposables.add( + scopedInstaService.createInstance, ChatFollowups>( + ChatFollowups, + templateData.value, + item, + undefined, + followup => this._onDidClickFollowup.fire(followup))); } else { const result = this.renderMarkdown(item as IMarkdownString, element, templateData); templateData.value.appendChild(result.element); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index a6cf114ed3302..0b1a1793ea234 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -238,6 +238,7 @@ class InputEditorSlashCommandMode extends Disposable { constructor( private readonly widget: IChatWidget, + @IChatAgentService private readonly _chatAgentService: IChatAgentService ) { super(); this._register(this.widget.onDidSubmitAgent(e => { @@ -249,7 +250,7 @@ class InputEditorSlashCommandMode extends Disposable { let value: string | undefined; if (slashCommand && slashCommand.shouldRepopulate) { value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; - } else { + } else if (agent.id !== this._chatAgentService.getDefaultAgent()?.id) { // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate value = `${chatAgentLeader}${agent.id} `; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index aee5dace743a1..ca909ba98e0e9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -638,7 +638,8 @@ export class InlineChatWidget { this._elements.followUps.classList.toggle('hidden', !items || items.length === 0); reset(this._elements.followUps); if (items && items.length > 0 && onFollowup) { - this._followUpDisposables.add(new ChatFollowups(this._elements.followUps, items, undefined, onFollowup, this._contextKeyService)); + this._followUpDisposables.add( + this._instantiationService.createInstance(ChatFollowups, this._elements.followUps, items, undefined, onFollowup)); } this._onDidChangeHeight.fire(); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index d2a76eb8736c6..5a667140993ad 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -216,7 +216,7 @@ declare module 'vscode' { */ agentId?: string; - subCommand?: string; + command?: string; /** * A tooltip to show when hovering over the followup. From fd0784486582c2cff44a8c4bb28fa01b38904473 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 12 Feb 2024 16:27:38 -0600 Subject: [PATCH 0200/1863] 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 ae9ded44aecac..5392e1787dd91 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 14a6ca567b48e..ed78f6292e778 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 ba50d3b212caf4ed5504442493d1fd7b3e5dc140 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 12 Feb 2024 18:18:04 -0600 Subject: [PATCH 0201/1863] Error in search input for spawn ripgrep ENOENT (#205045) * Error in search input for spawn ripgrep ENOENT Fixes #204011 * add message for ignoring notebook results. --- src/vs/workbench/api/common/extHostNotebook.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index f4a80653baa3f..6af6206b7fc11 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -434,6 +434,17 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } finalMatchedTargets.add(uri); }); + }).catch(err => { + // temporary fix for https://github.com/microsoft/vscode/issues/205044: don't show notebook results for remotehub repos. + if (err.code === 'ENOENT') { + console.warn(`Could not find notebook search results, ignoring notebook results.`); + return { + limitHit: false, + messages: [], + }; + } else { + throw err; + } }); })) )); From b2598b92d18ea1132e9cd35df776c8c22a7f9730 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 00:04:24 -0300 Subject: [PATCH 0202/1863] Fix variables2 for #file (#205054) --- src/vs/workbench/contrib/chat/common/chatParserTypes.ts | 5 ++--- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index f65c12199b8f0..fd9630ab51542 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -138,12 +138,11 @@ export class ChatRequestDynamicVariablePart implements IParsedChatRequestPart { constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string, readonly data: IChatRequestVariableValue[]) { } get referenceText(): string { - return this.text; + return this.text.replace(chatVariableLeader, ''); } get promptText(): string { - // This needs to be dynamically generated for de-duping - return ``; + return this.text; } } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index cdbcda1853720..5ec45665e6d52 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,7 +23,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IChatAgentHistoryEntry, 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, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -776,6 +776,9 @@ function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChat if (part instanceof ChatRequestVariablePart) { const values = variableData.variables[part.variableName]; res.variables.push({ name: part.variableName, range: part.range, values }); + } else if (part instanceof ChatRequestDynamicVariablePart) { + // Need variable without `#` + res.variables.push({ name: part.referenceText, range: part.range, values: part.data }); } } From 6061711e35083e2114ba765bdabb39575b933b20 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 20:40:25 -0800 Subject: [PATCH 0203/1863] Allow focus to be between cells --- .../controller/chat/cellChatActions.ts | 4 ++-- .../controller/chat/notebookChatController.ts | 20 ++++++++++++++++--- .../notebook/browser/view/notebookCellList.ts | 9 +++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 5959e33fcd715..b4c797e0c36e7 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -22,7 +22,7 @@ import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/w import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -43,7 +43,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { NotebookChatController.get(context.notebookEditor)?.acceptInput(); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index a2fde377709a3..33ad97dc0b6ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -272,7 +272,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); - this._widget.focus(); + this.focusWidget(); } if (this._widget && input) { @@ -287,6 +287,20 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } + private focusWidget() { + if (!this._widget) { + return; + } + + this._notebookEditor.focusContainer(); + this._notebookEditor.setSelections([{ + start: this._widget.afterModelPosition, + end: this._widget.afterModelPosition + }]); + + this._widget.focus(); + } + async acceptInput() { assertType(this._activeSession); assertType(this._widget); @@ -522,12 +536,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito switch (direction) { case 'above': if (this._widget?.afterModelPosition === index) { - this._widget.focus(); + this.focusWidget(); } break; case 'below': if (this._widget?.afterModelPosition === index + 1) { - this._widget.focus(); + this.focusWidget(); } break; default: diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index e613e8af3e619..9f57fec538546 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1282,6 +1282,15 @@ export class NotebookCellList extends WorkbenchList implements ID } focusContainer() { + // allow focus to be between cells + this._viewModel?.updateSelectionsState({ + kind: SelectionStateType.Handle, + primary: null, + selections: [] + }, 'view'); + this.setFocus([], undefined, true); + this.setSelection([], undefined, true); + super.domFocus(); } From aca3aec01a9d517a61eca3e68be479867139b254 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 20:54:23 -0800 Subject: [PATCH 0204/1863] Update notebook selections when focus change --- .../controller/chat/notebookChatController.ts | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 33ad97dc0b6ec..b2c6309f9ba8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { Dimension, IFocusTracker, WindowIntervalTimer, getWindow, scheduleAtNextAnimationFrame, trackFocus } from 'vs/base/browser/dom'; import { CancelablePromise, Queue, createCancelablePromise, disposableTimeout, raceCancellationError } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -162,6 +162,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; + private _widgetDisposableStore = this._register(new DisposableStore()); + private _focusTracker: IFocusTracker | undefined; constructor( private readonly _notebookEditor: INotebookEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -189,6 +191,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const window = getWindow(this._widget.domNode); this._widget.dispose(); this._widget = undefined; + this._widgetDisposableStore.clear(); scheduleAtNextAnimationFrame(window, () => { this._createWidget(index, input, autoSend); @@ -203,28 +206,36 @@ export class NotebookChatController extends Disposable implements INotebookEdito } private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + // Clear the widget if it's already there + this._widgetDisposableStore.clear(); + const viewZoneContainer = document.createElement('div'); viewZoneContainer.classList.add('monaco-editor'); const widgetContainer = document.createElement('div'); widgetContainer.style.position = 'absolute'; viewZoneContainer.appendChild(widgetContainer); + this._focusTracker = this._widgetDisposableStore.add(trackFocus(viewZoneContainer)); + this._widgetDisposableStore.add(this._focusTracker.onDidFocus(() => { + this._updateNotebookEditorFocusNSelections(); + })); + const fakeParentEditorElement = document.createElement('div'); - const fakeParentEditor = this._instantiationService.createInstance( + const fakeParentEditor = this._widgetDisposableStore.add(this._instantiationService.createInstance( CodeEditorWidget, fakeParentEditorElement, { }, { isSimpleWidget: true } - ); + )); const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); - const inlineChatWidget = this._instantiationService.createInstance( + const inlineChatWidget = this._widgetDisposableStore.add(this._instantiationService.createInstance( InlineChatWidget, fakeParentEditor, { @@ -233,7 +244,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito statusMenuId: MENU_CELL_CHAT_WIDGET_STATUS, feedbackMenuId: MENU_CELL_CHAT_WIDGET_FEEDBACK } - ); + )); inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); widgetContainer.appendChild(inlineChatWidget.domNode); @@ -260,7 +271,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito disposableTimeout(() => { this._ctxCellWidgetFocused.set(true); - this._widget?.focus(); + this._focusWidget(); }, 0, this._store); this._sessionCtor = createCancelablePromise(async token => { @@ -272,7 +283,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); this._widget.inlineChatWidget.updateInfo(this._activeSession?.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._widget.inlineChatWidget.updateSlashCommands(this._activeSession?.session.slashCommands ?? []); - this.focusWidget(); + this._focusWidget(); } if (this._widget && input) { @@ -287,7 +298,16 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } - private focusWidget() { + private _focusWidget() { + if (!this._widget) { + return; + } + + this._updateNotebookEditorFocusNSelections(); + this._widget.focus(); + } + + private _updateNotebookEditorFocusNSelections() { if (!this._widget) { return; } @@ -297,8 +317,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }]); - - this._widget.focus(); } async acceptInput() { @@ -536,12 +554,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito switch (direction) { case 'above': if (this._widget?.afterModelPosition === index) { - this.focusWidget(); + this._focusWidget(); } break; case 'below': if (this._widget?.afterModelPosition === index + 1) { - this.focusWidget(); + this._focusWidget(); } break; default: @@ -581,6 +599,13 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._sessionCtor = undefined; this._widget?.dispose(); this._widget = undefined; + this._widgetDisposableStore.clear(); + } + + public override dispose(): void { + this.dismiss(); + + super.dispose(); } } From 590d1fde9d211677754d517e7b92690f6b033214 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:06:46 -0800 Subject: [PATCH 0205/1863] Update markdown fragment from requests --- .../controller/chat/notebookChatController.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b2c6309f9ba8b..b686115da753a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -36,7 +36,7 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; -import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; +import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -354,7 +354,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const request: IInlineChatRequest = { requestId: generateUuid(), prompt: value, - attempt: 0, + attempt: this._activeSession.lastInput.attempt, selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, live: true, @@ -366,10 +366,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito const requestCts = new CancellationTokenSource(); const progressEdits: TextEdit[][] = []; + const progressiveEditsQueue = new Queue(); const progressiveEditsClock = StopWatch.create(); const progressiveEditsAvgDuration = new MovingAverage(); const progressiveEditsCts = new CancellationTokenSource(requestCts.token); + let progressiveChatResponse: IInlineChatMessageAppender | undefined; const progress = new AsyncProgress(async data => { // console.log('received chunk', data, request); @@ -400,6 +402,19 @@ export class NotebookChatController extends Disposable implements INotebookEdito ); }); } + + if (data.markdownFragment) { + if (!progressiveChatResponse) { + const message = { + message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }), + providerId: this._activeSession!.provider.debugName, + requestId: request.requestId, + }; + progressiveChatResponse = this._widget?.inlineChatWidget.updateChatMessage(message, true); + } else { + progressiveChatResponse.appendContent(data.markdownFragment); + } + } }); const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); From 27211bf19c3563a9dc9d6dd04ca6ed6e7d244c07 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:10:51 -0800 Subject: [PATCH 0206/1863] ensure init slash commands are registered --- .../notebook/browser/controller/chat/notebookChatController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b686115da753a..5eccad3da4826 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -277,7 +277,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._sessionCtor = createCancelablePromise(async token => { if (fakeParentEditor.hasModel()) { - this._startSession(fakeParentEditor, token); + await this._startSession(fakeParentEditor, token); if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); From cd89ea1ee64a0ef28618b9c97dd0a52b86c2fbe5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 21:34:39 -0800 Subject: [PATCH 0207/1863] support multiple whitespaces at the same position --- .../browser/view/notebookCellListView.ts | 47 +++++---- .../test/browser/notebookViewZones.test.ts | 95 +++++++++++++++++++ 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index 8ff1afea0c167..bafaac004e091 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -16,6 +16,7 @@ export interface IWhitespace { */ afterPosition: number; size: number; + priority: number; } export class NotebookCellsLayout implements IRangeMap { private _items: IItem[] = []; @@ -72,10 +73,11 @@ export class NotebookCellsLayout implements IRangeMap { const newSizes = []; for (let i = 0; i < inserts.length; i++) { const insertIndex = i + index; - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === insertIndex + 1); + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === insertIndex + 1); - if (existingWhitespace) { - newSizes.push(inserts[i].size + existingWhitespace.size); + + if (existingWhitespaces.length > 0) { + newSizes.push(inserts[i].size + existingWhitespaces.reduce((acc, ws) => acc + ws.size, 0)); } else { newSizes.push(inserts[i].size); } @@ -85,9 +87,9 @@ export class NotebookCellsLayout implements IRangeMap { // Now that the items array has been updated, and the whitespaces are updated elsewhere, if an item is removed/inserted, the accumlated size of the items are all updated. // Loop through all items from the index where the splice started, to the end for (let i = index; i < this._items.length; i++) { - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === i + 1); - if (existingWhitespace) { - this._prefixSumComputer.setValue(i, this._items[i].size + existingWhitespace.size); + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === i + 1); + if (existingWhitespaces.length > 0) { + this._prefixSumComputer.setValue(i, this._items[i].size + existingWhitespaces.reduce((acc, ws) => acc + ws.size, 0)); } else { this._prefixSumComputer.setValue(i, this._items[i].size); } @@ -95,14 +97,20 @@ export class NotebookCellsLayout implements IRangeMap { } insertWhitespace(id: string, afterPosition: number, size: number): void { - const existingWhitespace = this._whitespace.find(ws => ws.afterPosition === afterPosition); - if (existingWhitespace) { - throw new Error('Whitespace already exists at the specified position'); + let priority = 0; + const existingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === afterPosition); + if (existingWhitespaces.length > 0) { + priority = Math.max(...existingWhitespaces.map(ws => ws.priority)) + 1; } - this._whitespace.push({ id, afterPosition: afterPosition, size }); + this._whitespace.push({ id, afterPosition: afterPosition, size, priority }); this._size += size; // Update the total size to include the whitespace - this._whitespace.sort((a, b) => a.afterPosition - b.afterPosition); // Keep the whitespace sorted by index + this._whitespace.sort((a, b) => { + if (a.afterPosition === b.afterPosition) { + return a.priority - b.priority; + } + return a.afterPosition - b.afterPosition; + }); // find item size of index if (afterPosition > 0) { @@ -150,7 +158,8 @@ export class NotebookCellsLayout implements IRangeMap { if (whitespace.afterPosition > 0) { const index = whitespace.afterPosition - 1; const itemSize = this._items[index].size; - const accSize = itemSize; + const remainingWhitespaces = this._whitespace.filter(ws => ws.afterPosition === whitespace.afterPosition); + const accSize = itemSize + remainingWhitespaces.reduce((acc, ws) => acc + ws.size, 0); this._prefixSumComputer.setValue(index, accSize); } } @@ -169,16 +178,20 @@ export class NotebookCellsLayout implements IRangeMap { const afterPosition = whitespace.afterPosition; if (afterPosition === 0) { - return this.paddingTop; + // find all whitespaces at the same position but with higher priority (smaller number) + const whitespaces = this._whitespace.filter(ws => ws.afterPosition === afterPosition && ws.priority < whitespace.priority); + return whitespaces.reduce((acc, ws) => acc + ws.size, 0) + this.paddingTop; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); // previous item index const index = afterPosition - 1; const previousItemPosition = this._prefixSumComputer.getPrefixSum(index); const previousItemSize = this._items[index].size; - return previousItemPosition + previousItemSize + whitespaceBeforeFirstItem + this.paddingTop; + const previousWhitespace = this._whitespace.filter(ws => ws.afterPosition === afterPosition - 1); + const whitespaceBefore = previousWhitespace.reduce((acc, ws) => acc + ws.size, 0); + return previousItemPosition + previousItemSize + whitespaceBeforeFirstItem + this.paddingTop + whitespaceBefore; } indexAt(position: number): number { @@ -186,7 +199,7 @@ export class NotebookCellsLayout implements IRangeMap { return -1; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); const offset = position - (this._paddingTop + whitespaceBeforeFirstItem); if (offset <= 0) { @@ -219,7 +232,7 @@ export class NotebookCellsLayout implements IRangeMap { return -1; } - const whitespaceBeforeFirstItem = this._whitespace.length > 0 && this._whitespace[0].afterPosition === 0 ? this._whitespace[0].size : 0; + const whitespaceBeforeFirstItem = this._whitespace.filter(ws => ws.afterPosition === 0).reduce((acc, ws) => acc + ws.size, 0); return this._prefixSumComputer.getPrefixSum(index/** count */) + this._paddingTop + whitespaceBeforeFirstItem; } } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index fb8562f0a1e29..a858c9d786603 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -346,6 +346,41 @@ suite('NotebookRangeMap with whitesspaces', () => { instantiationService.stub(IConfigurationService, config); }); + test('Whitespace CRUD', async function () { + const twenty = { size: 20 }; + + const rangeMap = new NotebookCellsLayout(0); + rangeMap.splice(0, 0, [twenty, twenty, twenty]); + rangeMap.insertWhitespace('0', 0, 5); + rangeMap.insertWhitespace('1', 0, 5); + assert.strictEqual(rangeMap.indexAt(0), 0); + assert.strictEqual(rangeMap.indexAt(1), 0); + assert.strictEqual(rangeMap.indexAt(10), 0); + assert.strictEqual(rangeMap.indexAt(11), 0); + assert.strictEqual(rangeMap.indexAt(21), 0); + assert.strictEqual(rangeMap.indexAt(31), 1); + assert.strictEqual(rangeMap.positionAt(0), 10); + + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + assert.strictEqual(rangeMap.getWhitespacePosition('1'), 5); + + assert.strictEqual(rangeMap.positionAt(0), 10); + assert.strictEqual(rangeMap.positionAt(1), 30); + + rangeMap.changeOneWhitespace('0', 0, 10); + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + assert.strictEqual(rangeMap.getWhitespacePosition('1'), 10); + + assert.strictEqual(rangeMap.positionAt(0), 15); + assert.strictEqual(rangeMap.positionAt(1), 35); + + rangeMap.removeWhitespace('1'); + assert.strictEqual(rangeMap.getWhitespacePosition('0'), 0); + + assert.strictEqual(rangeMap.positionAt(0), 10); + assert.strictEqual(rangeMap.positionAt(1), 30); + }); + test('Whitespace with editing', async function () { await withTestNotebook( [ @@ -630,4 +665,64 @@ suite('NotebookRangeMap with whitesspaces', () => { }); }); }); + + test('Whitespace with multiple viewzones at same position', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + cellLineNumberStates: {}, + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + + const cellList = createNotebookCellList(instantiationService, disposables); + disposables.add(cellList); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + assert.strictEqual(cellList.scrollHeight, 350); + + cellList.changeViewZones(accessor => { + const first = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + + accessor.layoutZone(first); + assert.strictEqual(cellList.scrollHeight, 370); + + const second = accessor.addZone({ + afterModelPosition: 0, + heightInPx: 20, + domNode: document.createElement('div') + }); + accessor.layoutZone(second); + assert.strictEqual(cellList.scrollHeight, 390); + + assert.strictEqual(cellList.getElementTop(0), 40); + assert.strictEqual(cellList.getElementTop(1), 90); + assert.strictEqual(cellList.getElementTop(2), 190); + assert.strictEqual(cellList.getElementTop(3), 240); + assert.strictEqual(cellList.getElementTop(4), 340); + + + accessor.removeZone(first); + assert.strictEqual(cellList.scrollHeight, 370); + accessor.removeZone(second); + assert.strictEqual(cellList.scrollHeight, 350); + }); + }); + }); }); From a16faa30835e1185bf3d6017ad186650dc2fcc98 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:05:55 -0800 Subject: [PATCH 0208/1863] Reveal chat widget into view on creation. --- .../controller/chat/cellChatActions.ts | 4 ++-- .../controller/chat/notebookChatController.ts | 20 +++++++++++++++++++ .../notebook/browser/notebookBrowser.ts | 6 ++++++ .../notebook/browser/notebookEditorWidget.ts | 8 ++++++++ .../notebook/browser/view/notebookCellList.ts | 9 +++++++++ .../browser/view/notebookRenderingCommon.ts | 1 + 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index b4c797e0c36e7..3829e6e67c27c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -428,7 +428,7 @@ registerAction2(class extends NotebookAction { } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends NotebookAction { constructor() { super( { @@ -453,7 +453,7 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { context.notebookEditor.focusContainer(); NotebookChatController.get(context.notebookEditor)?.run(0, '', false); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 5eccad3da4826..dd6529fe241f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -257,6 +257,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito }; const id = accessor.addZone(notebookViewZone); + this._scrollWidgetIntoView(index); this._widget = new NotebookChatWidget( this._notebookEditor, @@ -298,6 +299,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } + private _scrollWidgetIntoView(index: number) { + if (index === 0 || this._notebookEditor.getLength() === 0) { + // the cell is at the beginning of the notebook + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0); + } else if (index >= this._notebookEditor.getLength()) { + // the cell is at the end of the notebook + const cell = this._notebookEditor.cellAt(this._notebookEditor.getLength() - 1)!; + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); + const cellHeight = this._notebookEditor.getHeightOfElement(cell); + + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); + } else { + const cell = this._notebookEditor.cellAt(index); + if (cell) { + this._notebookEditor.revealInCenterIfOutsideViewport(cell); + } + } + } + private _focusWidget() { if (!this._widget) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 6d0331e96d0a2..f54e31ebf9650 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -663,6 +663,11 @@ export interface INotebookEditor { */ revealCellOffsetInCenter(cell: ICellViewModel, offset: number): void; + /** + * Reveal `offset` in the list view into viewport center if it is outside of the viewport. + */ + revealOffsetInCenterIfOutsideViewport(offset: number): void; + /** * Convert the view range to model range * @param startIndex Inclusive @@ -720,6 +725,7 @@ export interface INotebookEditor { hideProgress(): void; getAbsoluteTopOfElement(cell: ICellViewModel): number; + getHeightOfElement(cell: ICellViewModel): number; } export interface IActiveNotebookEditor extends INotebookEditor { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0719ee2ca4fa1..64d26f612cec0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2086,6 +2086,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._list.getCellViewScrollTop(cell); } + getHeightOfElement(cell: ICellViewModel) { + return this._list.elementHeight(cell); + } + scrollToBottom() { this._list.scrollToBottom(); } @@ -2146,6 +2150,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._list.revealCellOffsetInCenter(cell, offset); } + revealOffsetInCenterIfOutsideViewport(offset: number) { + return this._list.revealOffsetInCenterIfOutsideViewport(offset); + } + getViewIndexByModelIndex(index: number): number { if (!this._listViewInfoAccessor) { return -1; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 9f57fec538546..af4c103561b26 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1171,6 +1171,15 @@ export class NotebookCellList extends WorkbenchList implements ID } } + revealOffsetInCenterIfOutsideViewport(offset: number) { + const scrollTop = this.getViewScrollTop(); + const wrapperBottom = this.getViewScrollBottom(); + + if (offset < scrollTop || offset > wrapperBottom) { + this.view.setScrollTop(offset - this.view.renderHeight / 2); + } + } + private _revealInCenterIfOutsideViewport(viewIndex: number) { this._revealInternal(viewIndex, true, CellRevealPosition.Center); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 01e4f3434586a..fd10069c2b0a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -63,6 +63,7 @@ export interface INotebookCellList extends ICoordinatesConverter { revealCells(range: ICellRange): void; revealRangeInCell(cell: ICellViewModel, range: Selection | Range, revealType: CellRevealRangeType): Promise; revealCellOffsetInCenter(element: ICellViewModel, offset: number): void; + revealOffsetInCenterIfOutsideViewport(offset: number): void; setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean; changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void; domElementOfElement(element: ICellViewModel): HTMLElement | null; From 7db840ad973637b422ebabe7b54bbaef1769ce15 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 07:07:19 +0100 Subject: [PATCH 0209/1863] eng - prefix error to hunt down shared process error (#205060) --- .../electron-main/userDataProfileStorageIpc.ts | 2 +- src/vs/platform/userDataSync/common/userDataSyncIpc.ts | 4 ++-- src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts index e3ac3124a1083..457dac8e1d4d8 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts @@ -97,7 +97,7 @@ export class ProfileStorageChangesListenerChannel extends Disposable implements switch (event) { case 'onDidChange': return this._onDidChange.event; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[ProfileStorageChangesListenerChannel] Event not found: ${event}`); } async call(_: unknown, command: string): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 74725542b1093..ede978060d963 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -22,7 +22,7 @@ export class UserDataSyncAccountServiceChannel implements IServerChannel { case 'onDidChangeAccount': return this.service.onDidChangeAccount; case 'onTokenFailed': return this.service.onTokenFailed; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncAccountServiceChannel] Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { @@ -70,7 +70,7 @@ export class UserDataSyncStoreManagementServiceChannel implements IServerChannel switch (event) { case 'onDidChangeUserDataSyncStore': return this.service.onDidChangeUserDataSyncStore; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncStoreManagementServiceChannel] Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 9619fae438e26..b052fe458c5f3 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -51,7 +51,7 @@ export class UserDataSyncServiceChannel implements IServerChannel { case 'manualSync/onSynchronizeResources': return this.onManualSynchronizeResources.event; } - throw new Error(`Event not found: ${event}`); + throw new Error(`[UserDataSyncServiceChannel] Event not found: ${event}`); } async call(context: any, command: string, args?: any): Promise { From 4ebc4f8b6447feb02e235cad8bd24c254e84dfc5 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:14:37 -0800 Subject: [PATCH 0210/1863] Small tweaks to widget reveal --- .../browser/controller/chat/notebookChatController.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index dd6529fe241f6..9fdd1bbf9fd47 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -311,9 +311,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } else { - const cell = this._notebookEditor.cellAt(index); + const cell = this._notebookEditor.cellAt(index - 1); if (cell) { - this._notebookEditor.revealInCenterIfOutsideViewport(cell); + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); + const cellHeight = this._notebookEditor.getHeightOfElement(cell); + + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } } } From e2d8d6273f0d038ebd4183c5bcd1017fa988956f Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 22:22:56 -0800 Subject: [PATCH 0211/1863] :lipstick: --- .../controller/chat/notebookChatController.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 9fdd1bbf9fd47..53e513ca6ee25 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -303,18 +303,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (index === 0 || this._notebookEditor.getLength() === 0) { // the cell is at the beginning of the notebook this._notebookEditor.revealOffsetInCenterIfOutsideViewport(0); - } else if (index >= this._notebookEditor.getLength()) { - // the cell is at the end of the notebook - const cell = this._notebookEditor.cellAt(this._notebookEditor.getLength() - 1)!; - const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); - const cellHeight = this._notebookEditor.getHeightOfElement(cell); - - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } else { - const cell = this._notebookEditor.cellAt(index - 1); - if (cell) { - const cellTop = this._notebookEditor.getAbsoluteTopOfElement(cell); - const cellHeight = this._notebookEditor.getHeightOfElement(cell); + // the cell is at the end of the notebook + const previousCell = this._notebookEditor.cellAt(Math.min(index - 1, this._notebookEditor.getLength() - 1)); + if (previousCell) { + const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell); + const cellHeight = this._notebookEditor.getHeightOfElement(previousCell); this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); } From b1784433fc8eaf93fe230ca33de25bb531d682ad Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 12 Feb 2024 23:11:18 -0800 Subject: [PATCH 0212/1863] Fix failing integration tests caused by notebook focus/selection change --- .../controller/chat/notebookChatController.ts | 2 +- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 4 ++-- .../notebook/browser/view/notebookCellList.ts | 20 ++++++++++--------- .../browser/view/notebookRenderingCommon.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 53e513ca6ee25..6f0a0ad584a9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -329,7 +329,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } - this._notebookEditor.focusContainer(); + this._notebookEditor.focusContainer(true); this._notebookEditor.setSelections([{ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index f54e31ebf9650..ae7113667a3e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -521,7 +521,7 @@ export interface INotebookEditor { /** * Focus the notebook cell list container */ - focusContainer(): void; + focusContainer(clearSelection?: boolean): void; hasEditorFocus(): boolean; hasWebviewFocus(): boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 64d26f612cec0..60ad41b8d1868 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1968,11 +1968,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } - focusContainer() { + focusContainer(clearSelection: boolean = false) { if (this._webviewFocused) { this._webview?.focusWebview(); } else { - this._list.focusContainer(); + this._list.focusContainer(clearSelection); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index af4c103561b26..d9d1dab798d4b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1290,15 +1290,17 @@ export class NotebookCellList extends WorkbenchList implements ID super.domFocus(); } - focusContainer() { - // allow focus to be between cells - this._viewModel?.updateSelectionsState({ - kind: SelectionStateType.Handle, - primary: null, - selections: [] - }, 'view'); - this.setFocus([], undefined, true); - this.setSelection([], undefined, true); + focusContainer(clearSelection: boolean) { + if (clearSelection) { + // allow focus to be between cells + this._viewModel?.updateSelectionsState({ + kind: SelectionStateType.Handle, + primary: null, + selections: [] + }, 'view'); + this.setFocus([], undefined, true); + this.setSelection([], undefined, true); + } super.domFocus(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index fd10069c2b0a7..f2700c72a213a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -71,7 +71,7 @@ export interface INotebookCellList extends ICoordinatesConverter { triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; updateElementHeight2(element: ICellViewModel, size: number, anchorElementIndex?: number | null): void; domFocus(): void; - focusContainer(): void; + focusContainer(clearSelection: boolean): void; setCellEditorSelection(element: ICellViewModel, range: Range): void; style(styles: IListStyles): void; getRenderHeight(): number; 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 0213/1863] 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 7e944aa9753db..30a95315f3363 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 656e26c80f38ed670804c88457cea5cc7ac80c76 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 13 Feb 2024 00:27:11 -0800 Subject: [PATCH 0214/1863] One auth provider per-extension (#205049) This way we don't complicate the user's experience who doesn't need to know anything about models. --- .../api/browser/mainThreadChatProvider.ts | 55 +++++++++------- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostChatProvider.ts | 66 +++++++++++++++---- .../contrib/chat/common/chatProvider.ts | 4 ++ .../vscode.proposed.chatProvider.d.ts | 7 ++ 6 files changed, 99 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 1a1d046357afa..801b0139627c4 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -59,7 +59,9 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } } })); - dipsosables.add(this._registerAuthenticationProvider(identifier)); + if (metadata.auth) { + dipsosables.add(this._registerAuthenticationProvider(metadata.extension, metadata.auth)); + } dipsosables.add(Registry.as(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({ id: `lm-${identifier}`, label: localize('languageModels', "Language Model ({0})", `${identifier}-${metadata.model}`), @@ -100,48 +102,54 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return task; } - private _registerAuthenticationProvider(identifier: string): IDisposable { - const disposables = new DisposableStore(); + private _registerAuthenticationProvider(extension: ExtensionIdentifier, auth: { providerLabel: string; accountLabel?: string | undefined }): IDisposable { // This needs to be done in both MainThread & ExtHost ChatProvider - const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + identifier; - // This is what will be displayed in the UI and the account used for managing access via Auth UI - const authAccountId = identifier; - this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, authAccountId)); + const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + extension.value; + + // Only register one auth provider per extension + if (this._authenticationService.getProviderIds().includes(authProviderId)) { + return Disposable.None; + } + + const disposables = new DisposableStore(); + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, auth.accountLabel)); disposables.add(toDisposable(() => { this._authenticationService.unregisterAuthenticationProvider(authProviderId); })); disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { if (e.providerId === authProviderId) { if (e.event.removed?.length) { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); const extensionsToUpdateAccess = []; for (const allowed of allowedExtensions) { - const extension = await this._extensionService.getExtension(allowed.id); - this._authenticationService.updateAllowedExtension(authProviderId, authAccountId, allowed.id, allowed.name, false); - if (extension) { + const from = await this._extensionService.getExtension(allowed.id); + this._authenticationService.updateAllowedExtension(authProviderId, authProviderId, allowed.id, allowed.name, false); + if (from) { extensionsToUpdateAccess.push({ - extension: extension.identifier, + from: from.identifier, + to: extension, enabled: false }); } } - this._proxy.$updateAccesslist(extensionsToUpdateAccess); + this._proxy.$updateModelAccesslist(extensionsToUpdateAccess); } } })); disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authAccountId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); const accessList = []; for (const allowedExtension of allowedExtensions) { - const extension = await this._extensionService.getExtension(allowedExtension.id); - if (extension) { + const from = await this._extensionService.getExtension(allowedExtension.id); + if (from) { accessList.push({ - extension: extension.identifier, + from: from.identifier, + to: extension, enabled: allowedExtension.allowed ?? true }); } } - this._proxy.$updateAccesslist(accessList); + this._proxy.$updateModelAccesslist(accessList); })); return disposables; } @@ -150,7 +158,6 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { // The fake AuthenticationProvider that will be used to gate access to the Language Model. There will be one per provider. class LanguageModelAccessAuthProvider implements IAuthenticationProvider { supportsMultipleAccounts = false; - label = 'Language Model'; // Important for updating the UI private _onDidChangeSessions: Emitter = new Emitter(); @@ -158,7 +165,11 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { private _session: AuthenticationSession | undefined; - constructor(readonly id: string, private readonly accountName: string) { } + constructor( + readonly id: string, + readonly label: string, + private readonly _accountLabel: string = localize('languageModelsAccountId', 'Language Models') + ) { } async getSessions(scopes?: string[] | undefined): Promise { // If there are no scopes and no session that means no extension has requested a session yet @@ -189,7 +200,7 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { id: 'fake-session', account: { id: this.id, - label: this.accountName, + label: this._accountLabel, }, accessToken: 'fake-access-token', scopes, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 071e935c05128..2b4bfec98a643 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1395,7 +1395,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); - return extHostChatProvider.registerLanguageModel(extension.identifier, id, provider, metadata); + return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, requestLanguageModelAccess(id, options) { checkProposedApiEnabled(extension, 'chatRequestAccess'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 964afc507dc85..2f931bb83ef8d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1184,6 +1184,7 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; + $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index eae0f221ffa65..108a893fb4d62 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -11,7 +11,7 @@ import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +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 { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; @@ -91,12 +91,14 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _proxy: MainThreadChatProviderShape; private readonly _onDidChangeAccess = new Emitter(); + private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH private readonly _accesslist = new ExtensionIdentifierMap(); + private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); @@ -113,11 +115,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeProviders.dispose(); } - registerLanguageModel(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { + registerLanguageModel(extension: IExtensionDescription, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { const handle = ExtHostChatProvider._idPool++; - this._languageModels.set(handle, { extension, provider }); - this._proxy.$registerProvider(handle, identifier, { extension, model: metadata.name ?? '' }); + this._languageModels.set(handle, { extension: extension.identifier, provider }); + let auth; + if (metadata.auth) { + auth = { + providerLabel: extension.displayName || extension.name, + accountLabel: typeof metadata.auth === 'object' ? metadata.auth.label : undefined + }; + } + this._proxy.$registerProvider(handle, identifier, { + extension: extension.identifier, + model: metadata.name ?? '', + auth + }); return toDisposable(() => { this._languageModels.delete(handle); @@ -190,7 +203,26 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._onDidChangeAccess.fire(updated); } - async requestLanguageModelAccess(extension: Readonly, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { + $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void { + const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); + for (const { from, to, enabled } of data) { + const set = this._modelAccessList.get(from) ?? new ExtensionIdentifierSet(); + const oldValue = set.has(to); + if (oldValue !== enabled) { + if (enabled) { + set.add(to); + } else { + set.delete(to); + } + this._modelAccessList.set(from, set); + const newItem = { from, to }; + updated.push(newItem); + this._onDidChangeModelAccess.fire(newItem); + } + } + } + + async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; // check if the extension is in the access list and allowed to make chat requests if (this._accesslist.get(from) === false) { @@ -206,7 +238,10 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } throw new Error(`Language model '${languageModelId}' NOT found`); } - await this._checkAuthAccess(extension, languageModelId, justification); + + if (metadata.auth) { + await this._checkAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth?.providerLabel }, justification); + } const that = this; @@ -215,15 +250,18 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accesslist.get(from) || !that._languageModelIds.has(languageModelId); + return !that._accesslist.get(from) + || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension)) + || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); const onDidRemoveLM = Event.filter(that._onDidChangeProviders.event, e => e.removed.includes(languageModelId)); - return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM)); + const onDidChangeModelAccess = Event.filter(that._onDidChangeModelAccess.event, e => ExtensionIdentifier.equals(e.from, from) && ExtensionIdentifier.equals(e.to, metadata.extension)); + return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { - if (!that._accesslist.get(from)) { + if (!that._accesslist.get(from) || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension))) { throw new Error('Access to chat has been revoked'); } if (!that._languageModelIds.has(languageModelId)) { @@ -253,20 +291,22 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _checkAuthAccess(from: Readonly, languageModelId: string, detail?: string): Promise { + private async _checkAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { // This needs to be done in both MainThread & ExtHost ChatProvider - const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + languageModelId; + const providerId = INTERNAL_AUTH_PROVIDER_PREFIX + to.identifier.value; const session = await this._extHostAuthentication.getSession(from, providerId, [], { silent: true }); if (!session) { try { await this._extHostAuthentication.getSession(from, providerId, [], { forceNewSession: { - detail: detail ?? localize('chatAccess', "To allow access to the '{0}' language model", languageModelId), + detail: detail ?? localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName), } }); } catch (err) { - throw new Error('Access to language model has not been granted'); + throw new Error('Access to language models has not been granted'); } } + + this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } } diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/chatProvider.ts index c4655e3566564..c393a73de98c4 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/chatProvider.ts @@ -30,6 +30,10 @@ export interface IChatResponseProviderMetadata { readonly extension: ExtensionIdentifier; readonly model: string; readonly description?: string; + readonly auth?: { + readonly providerLabel: string; + readonly accountLabel?: string; + }; } export interface IChatResponseProvider { diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 226416480c052..e35fd5547d14d 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -26,6 +26,13 @@ declare module 'vscode' { */ // TODO@API rename to model name: string; + + /** + * When present, this gates the use of `requestLanguageModelAccess` behind an authorization flow where + * the user must approve of another extension accessing the models contributed by this extension. + * Additionally, the extension can provide a label that will be shown in the UI. + */ + auth?: true | { label: string }; } export namespace chat { From 7b0e8858b37d05e44907378e5e61fe5f524a198d Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 09:35:47 +0100 Subject: [PATCH 0215/1863] polishing code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 49cef9c8c8ae9..3a9b9ebab13ae 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -98,6 +98,11 @@ export class BulkEditPane extends ViewPane { this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); + this._disposables.add(this._editorService.onDidCloseEditor((e) => { + if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input) { + this._multiDiffEditor = undefined; + } + })); } override dispose(): void { @@ -268,7 +273,6 @@ export class BulkEditPane extends ViewPane { this._dialogService.warn(message).finally(() => this._done(false)); } - // Going through here to discard discard() { this._done(false); } @@ -278,6 +282,10 @@ export class BulkEditPane extends ViewPane { this._currentInput = undefined; this._setState(State.Message); this._sessionDisposables.clear(); + if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { + this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); + } + this._multiDiffEditor = undefined; } toggleChecked() { @@ -375,14 +383,14 @@ export class BulkEditPane extends ViewPane { } const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); const label = 'Refactor Preview'; - this._multiDiffEditor = this._sessionDisposables.add( + this._multiDiffEditor = await this._editorService.openEditor({ - multiDiffSource: multiDiffSource ? URI.revive(multiDiffSource) : undefined, + multiDiffSource: URI.revive(multiDiffSource), resources, label: label, description: label, options: options, - }) as MultiDiffEditor); + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { From 45e2e2072fac7eb15804477b725cf3081ecd7bb3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:53:50 +0100 Subject: [PATCH 0216/1863] Git - fix upstream state management check (#205078) --- extensions/git/src/historyProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 80fbbe52799f5..db9873440286c 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -81,8 +81,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Check if Upstream has changed if (force || this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || - this._HEAD?.upstream?.remote === this.repository.HEAD?.upstream?.remote || - this._HEAD?.upstream?.commit === this.repository.HEAD?.upstream?.commit) { + this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || + this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); this._onDidChangeCurrentHistoryItemGroupBase.fire(); } From c58252ba7e7cb82f869df43df2c765a8655053c5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:54:33 +0100 Subject: [PATCH 0217/1863] SCM - Add Incoming/Outgoing menu to the title menu (#205069) SCM Add Incoming/Outgoing menu to the title menu --- .../contrib/scm/browser/scmViewPane.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ac5b68e12b27b..3a92ee61bdc50 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1430,6 +1430,7 @@ const enum ViewSortKey { const Menus = { ViewSort: new MenuId('SCMViewSort'), Repositories: new MenuId('SCMRepositories'), + ChangesSettings: new MenuId('SCMChangesSettings'), }; const ContextKeys = { @@ -1451,7 +1452,16 @@ MenuRegistry.appendMenuItem(MenuId.SCMTitle, { title: localize('sortAction', "View & Sort"), submenu: Menus.ViewSort, when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)), - group: '0_view&sort' + group: '0_view&sort', + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.SCMTitle, { + title: localize('scmChanges', "Incoming & Outgoing"), + submenu: Menus.ChangesSettings, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)), + group: '0_view&sort', + order: 2 }); MenuRegistry.appendMenuItem(Menus.ViewSort, { @@ -1486,16 +1496,20 @@ MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { order: 1 }); +MenuRegistry.appendMenuItem(Menus.ChangesSettings, { + title: localize('incomingChanges', "Show Incoming Changes"), + submenu: MenuId.SCMIncomingChangesSetting, + group: '1_incoming&outgoing', + order: 1 +}); + registerAction2(class extends SCMChangesSettingAction { constructor() { super('scm.showIncomingChanges', 'always', { id: 'workbench.scm.action.showIncomingChanges.always', title: localize('always', "Always"), - menu: { - id: MenuId.SCMIncomingChangesSetting, - - } + menu: { id: MenuId.SCMIncomingChangesSetting }, }); } }); @@ -1533,6 +1547,13 @@ MenuRegistry.appendMenuItem(MenuId.SCMChangesSeparator, { order: 2 }); +MenuRegistry.appendMenuItem(Menus.ChangesSettings, { + title: localize('outgoingChanges', "Show Outgoing Changes"), + submenu: MenuId.SCMOutgoingChangesSetting, + group: '1_incoming&outgoing', + order: 2 +}); + registerAction2(class extends SCMChangesSettingAction { constructor() { super('scm.showOutgoingChanges', 'always', @@ -1580,10 +1601,10 @@ registerAction2(class extends Action2 { title: localize('showChangesSummary', "Show Changes Summary"), f1: false, toggled: ContextKeyExpr.equals('config.scm.showChangesSummary', true), - menu: { - id: MenuId.SCMChangesSeparator, - order: 3 - } + menu: [ + { id: MenuId.SCMChangesSeparator, order: 3 }, + { id: Menus.ChangesSettings, order: 3 }, + ] }); } From 038226e88363e69f59023c491ce40dfbcbffad55 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:02:41 +0100 Subject: [PATCH 0218/1863] Support holding `Cmd+I` in panel chat to dictate (fix #203208) (#205065) --- .../actions/voiceChatActions.ts | 324 +++++++++--------- .../electron-sandbox/chat.contribution.ts | 7 +- .../extensions/browser/extensionEditor.ts | 4 +- .../electron-sandbox/inlineChatActions.ts | 6 +- 4 files changed, 169 insertions(+), 172 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 41083a4bb9b06..fecd9449db4c2 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -11,25 +11,25 @@ import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize, localize2 } from 'vs/nls'; -import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; -import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, MENU_INLINE_CHAT_INPUT } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -51,6 +51,9 @@ import { ProgressLocation } from 'vs/platform/progress/common/progress'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; 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'; 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.") }); @@ -60,6 +63,8 @@ const CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS = new RawContextKey('inline 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'; interface IVoiceChatSessionController { @@ -84,6 +89,7 @@ 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 { const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); @@ -129,7 +135,7 @@ class VoiceChatSessionControllerFactory { } // View Chat - if (context === 'view') { + if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { const provider = firstOrDefault(chatService.getProviderInfos()); if (provider) { const chatView = await chatWidgetService.revealViewForProvider(provider.id); @@ -220,7 +226,14 @@ class VoiceChatSessionControllerFactory { } } -interface ActiveVoiceChatSession { +interface IVoiceChatSession { + setTimeoutDisabled(disabled: boolean): void; + + accept(): void; + stop(): void; +} + +interface IActiveVoiceChatSession extends IVoiceChatSession { readonly id: number; readonly controller: IVoiceChatSessionController; readonly disposables: DisposableStore; @@ -245,7 +258,7 @@ class VoiceChatSessions { private voiceChatInViewInProgressKey = CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.bindTo(this.contextKeyService); private voiceChatInEditorInProgressKey = CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.bindTo(this.contextKeyService); - private currentVoiceChatSession: ActiveVoiceChatSession | undefined = undefined; + private currentVoiceChatSession: IActiveVoiceChatSession | undefined = undefined; private voiceChatSessionIds = 0; constructor( @@ -254,14 +267,19 @@ class VoiceChatSessions { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - async start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): Promise { + start(controller: IVoiceChatSessionController, context?: IChatExecuteActionContext): IVoiceChatSession { this.stop(); + let disableTimeout = false; + const sessionId = ++this.voiceChatSessionIds; - const session = this.currentVoiceChatSession = { + const session: IActiveVoiceChatSession = this.currentVoiceChatSession = { id: sessionId, controller, - disposables: new DisposableStore() + disposables: new DisposableStore(), + setTimeoutDisabled: (disabled: boolean) => { disableTimeout = disabled; }, + accept: () => session.controller.acceptInput(), + stop: () => this.stop(sessionId, controller.context) }; const cts = new CancellationTokenSource(); @@ -296,7 +314,7 @@ class VoiceChatSessions { case SpeechToTextStatus.Recognizing: if (text) { session.controller.updateInput(inputValue ? [inputValue, text].join(' ') : text); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !disableTimeout) { acceptTranscriptionScheduler.cancel(); } } @@ -305,7 +323,7 @@ class VoiceChatSessions { if (text) { inputValue = inputValue ? [inputValue, text].join(' ') : text; session.controller.updateInput(inputValue); - if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput) { + if (voiceChatTimeout > 0 && context?.voice?.disableTimeout !== true && !waitingForInput && !disableTimeout) { acceptTranscriptionScheduler.schedule(); } } @@ -315,6 +333,8 @@ class VoiceChatSessions { break; } })); + + return session; } private onDidSpeechToTextSessionStart(controller: IVoiceChatSessionController, disposables: DisposableStore): void { @@ -383,55 +403,81 @@ class VoiceChatSessions { } } -export class VoiceChatInChatViewAction extends Action2 { +export const VOICE_KEY_HOLD_THRESHOLD = 500; - static readonly ID = 'workbench.action.chat.voiceChatInChatView'; +async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise): Promise { + if (!holdMode) { + return; + } - constructor() { - super({ - id: VoiceChatInChatViewAction.ID, - title: localize2('workbench.action.chat.voiceChatInView.label', "Voice Chat in Chat View"), - category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), - f1: true - }); + let acceptVoice = false; + const handle = disposableTimeout(() => { + acceptVoice = true; + session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms + }, VOICE_KEY_HOLD_THRESHOLD); + + await holdMode; + handle.dispose(); + + if (acceptVoice) { + session.accept(); + } +} + +class VoiceChatWithHoldModeAction extends Action2 { + + constructor(desc: Readonly, private readonly target: 'inline' | 'quick' | 'view') { + super(desc); } async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + + const controller = await VoiceChatSessionControllerFactory.create(accessor, this.target); + if (!controller) { + return; } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + + awaitHoldAndAccept(session, holdMode); } } -export class InlineVoiceChatAction extends Action2 { +export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { - static readonly ID = 'workbench.action.chat.inlineVoiceChat'; + static readonly ID = 'workbench.action.chat.voiceChatInChatView'; constructor() { super({ - id: InlineVoiceChatAction.ID, - title: localize2('workbench.action.chat.inlineVoiceChat', "Inline Voice Chat"), + id: VoiceChatInChatViewAction.ID, + title: localize2('workbench.action.chat.voiceChatInView.label', "Voice Chat in View"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, ActiveEditorContext, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true - }); + }, 'view'); } +} - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); +export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'inline'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } + static readonly ID = 'workbench.action.chat.inlineVoiceChat'; + + constructor() { + super({ + id: InlineVoiceChatAction.ID, + title: localize2('workbench.action.chat.inlineVoiceChat', "Inline Voice Chat"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(CanVoiceChat, ActiveEditorContext, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + f1: true + }, 'inline'); } } -export class QuickVoiceChatAction extends Action2 { +export class QuickVoiceChatAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.quickVoiceChat'; @@ -440,18 +486,9 @@ export class QuickVoiceChatAction extends Action2 { id: QuickVoiceChatAction.ID, title: localize2('workbench.action.chat.quickVoiceChat.label', "Quick Voice Chat"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_PROVIDER_EXISTS, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), f1: true - }); - } - - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'quick'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } + }, 'quick'); } } @@ -462,10 +499,26 @@ export class StartVoiceChatAction extends Action2 { constructor() { super({ id: StartVoiceChatAction.ID, - title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), + title: localize2('workbench.action.chat.startVoiceChat.label', "Start Voice Chat"), category: CHAT_CATEGORY, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + EditorContextKeys.focus.toNegated(), // do not steal the inline-chat keybinding + CONTEXT_VOICE_CHAT_GETTING_READY.negate(), + CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), + CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate(), + 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() + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + }, icon: Codicon.mic, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_GETTING_READY.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate()), + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_GETTING_READY.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate()), menu: [{ id: MenuId.ChatExecute, when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate()), @@ -482,7 +535,9 @@ export class StartVoiceChatAction extends Action2 { async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { const instantiationService = accessor.get(IInstantiationService); - const commandService = accessor.get(ICommandService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); const widget = context?.widget; if (widget) { @@ -497,12 +552,13 @@ export class StartVoiceChatAction extends Action2 { } const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); - if (controller) { - VoiceChatSessions.getInstance(instantiationService).start(controller, context); - } else { - // fallback to Quick Voice Chat command - commandService.executeCommand(QuickVoiceChatAction.ID, context); + if (!controller) { + return; } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + + awaitHoldAndAccept(session, holdMode); } } @@ -517,8 +573,7 @@ export class InstallVoiceChatAction extends Action2 { constructor() { super({ id: InstallVoiceChatAction.ID, - title: localize2('workbench.action.chat.startVoiceChat.label', "Use Microphone"), - f1: false, + title: localize2('workbench.action.chat.startVoiceChat.label', "Start Voice Chat"), category: CHAT_CATEGORY, icon: Codicon.mic, precondition: InstallingSpeechProvider.negate(), @@ -574,147 +629,81 @@ export class InstallVoiceChatAction extends Action2 { } } -export class StopListeningAction extends Action2 { - - static readonly ID = 'workbench.action.chat.stopListening'; +class BaseStopListeningAction extends Action2 { - constructor() { + constructor( + desc: { id: string; icon?: ThemeIcon; f1?: boolean }, + private readonly target: 'inline' | 'quick' | 'view' | 'editor' | undefined, + context: RawContextKey, + menu: MenuId | undefined, + group: 'navigation' | 'main' = 'navigation' + ) { super({ - id: StopListeningAction.ID, + ...desc, title: localize2('workbench.action.chat.stopListening.label', "Stop Listening"), category: CHAT_CATEGORY, - f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS), + when: ContextKeyExpr.and(CanVoiceChat, context), primary: KeyCode.Escape }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS) + precondition: ContextKeyExpr.and(CanVoiceChat, context), + menu: menu ? [{ + id: menu, + when: ContextKeyExpr.and(CanVoiceChat, context), + group, + order: -1 + }] : undefined }); } - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(); + async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, this.target); } } -export class StopListeningInChatViewAction extends Action2 { +export class StopListeningAction extends BaseStopListeningAction { - static readonly ID = 'workbench.action.chat.stopListeningInChatView'; + static readonly ID = 'workbench.action.chat.stopListening'; constructor() { - super({ - id: StopListeningInChatViewAction.ID, - title: localize2('workbench.action.chat.stopListeningInChatView.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); + super({ id: StopListeningAction.ID, f1: true }, undefined, CONTEXT_VOICE_CHAT_IN_PROGRESS, undefined); } +} - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'view'); +export class StopListeningInChatViewAction extends BaseStopListeningAction { + + static readonly ID = 'workbench.action.chat.stopListeningInChatView'; + + constructor() { + super({ id: StopListeningInChatViewAction.ID, icon: spinningLoading }, 'view', CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInChatEditorAction extends Action2 { +export class StopListeningInChatEditorAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInChatEditor'; constructor() { - super({ - id: StopListeningInChatEditorAction.ID, - title: localize2('workbench.action.chat.stopListeningInChatEditor.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'editor'); + super({ id: StopListeningInChatEditorAction.ID, icon: spinningLoading }, 'editor', CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInQuickChatAction extends Action2 { +export class StopListeningInQuickChatAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInQuickChat'; constructor() { - super({ - id: StopListeningInQuickChatAction.ID, - title: localize2('workbench.action.chat.stopListeningInQuickChat.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS), - group: 'navigation', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'quick'); + super({ id: StopListeningInQuickChatAction.ID, icon: spinningLoading }, 'quick', CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS, MenuId.ChatExecute); } } -export class StopListeningInInlineChatAction extends Action2 { +export class StopListeningInInlineChatAction extends BaseStopListeningAction { static readonly ID = 'workbench.action.chat.stopListeningInInlineChat'; constructor() { - super({ - id: StopListeningInInlineChatAction.ID, - title: localize2('workbench.action.chat.stopListeningInInlineChat.label', "Stop Listening"), - category: CHAT_CATEGORY, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - primary: KeyCode.Escape - }, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - icon: spinningLoading, - menu: [{ - id: MENU_INLINE_CHAT_INPUT, - when: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS), - group: 'main', - order: -1 - }] - }); - } - - run(accessor: ServicesAccessor): void { - VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).stop(undefined, 'inline'); + super({ id: StopListeningInInlineChatAction.ID, icon: spinningLoading }, 'inline', CONTEXT_INLINE_VOICE_CHAT_IN_PROGRESS, MENU_INLINE_CHAT_INPUT, 'main'); } } @@ -728,7 +717,16 @@ export class StopListeningAndSubmitAction extends Action2 { title: localize2('workbench.action.chat.stopListeningAndSubmit.label', "Stop Listening and Submit"), category: CHAT_CATEGORY, f1: true, - precondition: ContextKeyExpr.and(HasSpeechProvider, CONTEXT_VOICE_CHAT_IN_PROGRESS) + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT), + CONTEXT_VOICE_CHAT_IN_PROGRESS + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + }, + precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_IN_PROGRESS) }); } @@ -808,6 +806,8 @@ function supportsKeywordActivation(configurationService: IConfigurationService, export class KeywordActivationContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.keywordActivation'; + static SETTINGS_VALUE = { OFF: 'off', INLINE_CHAT: 'inlineChat', 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 127d13be18bb0..2f69e1b13f87e 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -5,9 +5,7 @@ import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopListeningInInlineChatAction, StopListeningInQuickChatAction, StopListeningInChatEditorAction, StopListeningInChatViewAction, VoiceChatInChatViewAction, StopListeningAction, StopListeningAndSubmitAction, KeywordActivationContribution, InstallVoiceChatAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; registerAction2(StartVoiceChatAction); registerAction2(InstallVoiceChatAction); @@ -24,5 +22,4 @@ registerAction2(StopListeningInChatEditorAction); registerAction2(StopListeningInQuickChatAction); registerAction2(StopListeningInInlineChatAction); -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(KeywordActivationContribution, LifecyclePhase.Restored); +registerWorkbenchContribution2(KeywordActivationContribution.ID, KeywordActivationContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 45f5217c9f589..53f55fa804c67 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -580,7 +580,7 @@ export class ExtensionEditor extends EditorPane { template.navbar.push(ExtensionEditorTab.ExtensionPack, localize('extensionpack', "Extension Pack"), localize('extensionpacktooltip', "Lists extensions those will be installed together with this extension")); } - if ((this.options).tab) { + if ((this.options)?.tab) { template.navbar.switch((this.options).tab!); } if (template.navbar.currentId) { @@ -966,7 +966,7 @@ export class ExtensionEditor extends EditorPane { return null; } - const extensionFeaturesTab = this.contentDisposables.add(this.instantiationService.createInstance(ExtensionFeaturesTab, manifest, (this.options).feature)); + const extensionFeaturesTab = this.contentDisposables.add(this.instantiationService.createInstance(ExtensionFeaturesTab, manifest, (this.options)?.feature)); const layout = () => extensionFeaturesTab.layout(template.content.clientHeight, template.content.clientWidth); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout }); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index d30d230a2b71e..99862b01d7bb8 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -13,7 +13,7 @@ import { disposableTimeout } from 'vs/base/common/async'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StartVoiceChatAction, StopListeningAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { StartVoiceChatAction, StopListeningAction, VOICE_KEY_HOLD_THRESHOLD } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HasSpeechProvider, ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; @@ -62,12 +62,12 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController, a // start VOICE input commandService.executeCommand(StartVoiceChatAction.ID, { voice: { disableTimeout: true } } satisfies IChatExecuteActionContext); listening = true; - }, 250); + }, VOICE_KEY_HOLD_THRESHOLD); holdMode.finally(() => { if (listening) { commandService.executeCommand(StopListeningAction.ID).finally(() => { - ctrl!.acceptInput(); + ctrl.acceptInput(); }); } handle.dispose(); From f6c0c5e5e26e76939eb9ec79f964073f8dc9da84 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 13 Feb 2024 12:24:29 +0100 Subject: [PATCH 0219/1863] Less text-y settings in release notes (#205081) --- .../browser/markdownSettingRenderer.ts | 184 ++++++++++++------ .../browser/markdownSettingRenderer.test.ts | 117 ++++------- .../update/browser/releaseNotesEditor.ts | 61 +++++- 3 files changed, 220 insertions(+), 142 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index b593321746b60..89101b7134b7c 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -4,24 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { IPreferencesService, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IAction } from 'vs/base/common/actions'; const codeSettingRegex = /^/; export class SimpleSettingRenderer { - private defaultSettings: DefaultSettings; - private updatedSettings = new Map(); // setting ID to user's original setting value - private encounteredSettings = new Map(); // setting ID to setting + private _defaultSettings: DefaultSettings; + private _updatedSettings = new Map(); // setting ID to user's original setting value + private _encounteredSettings = new Map(); // setting ID to setting constructor( - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IPreferencesService private readonly _preferencesService: IPreferencesService ) { - this.defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } getHtmlRenderer(): (html: string) => string { @@ -38,19 +43,23 @@ export class SimpleSettingRenderer { }; } + settingToUriString(settingId: string, value?: any): string { + return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; + } + private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { if (!this.settingsGroups) { - this.settingsGroups = this.defaultSettings.getSettingsGroups(); + this.settingsGroups = this._defaultSettings.getSettingsGroups(); } - if (this.encounteredSettings.has(settingId)) { - return this.encounteredSettings.get(settingId); + if (this._encounteredSettings.has(settingId)) { + return this._encounteredSettings.get(settingId); } for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { if (setting.key === settingId) { - this.encounteredSettings.set(settingId, setting); + this._encounteredSettings.set(settingId, setting); return setting; } } @@ -88,100 +97,151 @@ export class SimpleSettingRenderer { return this.renderSetting(setting, newValue); } - private viewInSettings(settingId: string, alreadySet: boolean): string { - let message: string; - if (alreadySet) { - const displayName = settingKeyToDisplayFormat(settingId); - message = nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); + private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { + if (alreadyDisplayed) { + return nls.localize('viewInSettings', "View in Settings"); } else { - message = nls.localize('viewInSettings', "View in Settings"); + const displayName = settingKeyToDisplayFormat(settingId); + return nls.localize('viewInSettingsDetailed', "View \"{0}: {1}\" in Settings", displayName.category, displayName.label); } - return `${message}`; } - private renderRestorePreviousSetting(settingId: string): string { + private restorePreviousSettingMessage(settingId: string): string { const displayName = settingKeyToDisplayFormat(settingId); - const value = this.updatedSettings.get(settingId); - const message = nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); - return `${message}`; + return nls.localize('restorePreviousValue', "Restore value of \"{0}: {1}\"", displayName.category, displayName.label); } - private renderBooleanSetting(setting: ISetting, value: string): string | undefined { - const booleanValue: boolean = value === 'true' ? true : false; - const currentValue = this.configurationService.getValue(setting.key); + private booleanSettingMessage(setting: ISetting, booleanValue: boolean): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); if (currentValue === booleanValue || (currentValue === undefined && setting.value === booleanValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - let message: string; if (booleanValue) { - message = nls.localize('trueMessage', "Enable \"{0}: {1}\" now", displayName.category, displayName.label); + return nls.localize('trueMessage', "Enable \"{0}: {1}\"", displayName.category, displayName.label); } else { - message = nls.localize('falseMessage', "Disable \"{0}: {1}\" now", displayName.category, displayName.label); + return nls.localize('falseMessage', "Disable \"{0}: {1}\"", displayName.category, displayName.label); } - return `${message}`; } - private renderStringSetting(setting: ISetting, value: string): string | undefined { - const currentValue = this.configurationService.getValue(setting.key); - if (currentValue === value || (currentValue === undefined && setting.value === value)) { + private stringSettingMessage(setting: ISetting, stringValue: string): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); + if (currentValue === stringValue || (currentValue === undefined && setting.value === stringValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - const message = nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\" now", displayName.category, displayName.label, value); - return `${message}`; + return nls.localize('stringValue', "Set \"{0}: {1}\" to \"{2}\"", displayName.category, displayName.label, stringValue); } - private renderNumberSetting(setting: ISetting, value: string): string | undefined { - const numberValue: number = parseInt(value, 10); - const currentValue = this.configurationService.getValue(setting.key); + private numberSettingMessage(setting: ISetting, numberValue: number): string | undefined { + const currentValue = this._configurationService.getValue(setting.key); if (currentValue === numberValue || (currentValue === undefined && setting.value === numberValue)) { return undefined; } const displayName = settingKeyToDisplayFormat(setting.key); - const message = nls.localize('numberValue', "Set \"{0}: {1}\" to {2} now", displayName.category, displayName.label, numberValue); - return `${message}`; + return nls.localize('numberValue', "Set \"{0}: {1}\" to {2}", displayName.category, displayName.label, numberValue); } private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { - let renderedSetting: string | undefined; - - if (newValue !== undefined) { - if (this.updatedSettings.has(setting.key)) { - renderedSetting = this.renderRestorePreviousSetting(setting.key); - } else if (setting.type === 'boolean') { - renderedSetting = this.renderBooleanSetting(setting, newValue); - } else if (setting.type === 'string') { - renderedSetting = this.renderStringSetting(setting, newValue); - } else if (setting.type === 'number') { - renderedSetting = this.renderNumberSetting(setting, newValue); - } - } + const href = this.settingToUriString(setting.key, newValue); + const title = nls.localize('changeSettingTitle', "Try feature"); + return ``; + } - if (!renderedSetting) { - return `(${this.viewInSettings(setting.key, true)})`; + private getSettingMessage(setting: ISetting, newValue: boolean | string | number): string | undefined { + if (setting.type === 'boolean') { + return this.booleanSettingMessage(setting, newValue as boolean); + } else if (setting.type === 'string') { + return this.stringSettingMessage(setting, newValue as string); + } else if (setting.type === 'number') { + return this.numberSettingMessage(setting, newValue as number); } + return undefined; + } + + async restoreSetting(settingId: string): Promise { + const userOriginalSettingValue = this._updatedSettings.get(settingId); + this._updatedSettings.delete(settingId); + return this._configurationService.updateValue(settingId, userOriginalSettingValue, ConfigurationTarget.USER); + } - return nls.localize({ key: 'fullRenderedSetting', comment: ['A pair of already localized links. The first argument is a link to change a setting, the second is a link to view the setting.'] }, - "({0} | {1})", renderedSetting, this.viewInSettings(setting.key, false),); + async setSetting(settingId: string, currentSettingValue: any, newSettingValue: any): Promise { + this._updatedSettings.set(settingId, currentSettingValue); + return this._configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); } - async updateSettingValue(uri: URI) { + getActions(uri: URI) { if (uri.scheme !== Schemas.codeSetting) { return; } + + const actions: IAction[] = []; + const settingId = uri.authority; const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); - const oldSettingValue = this.configurationService.inspect(settingId).userValue; - if (newSettingValue === this.updatedSettings.get(settingId)) { - this.updatedSettings.delete(settingId); - } else { - this.updatedSettings.set(settingId, oldSettingValue); + const currentSettingValue = this._configurationService.inspect(settingId).userValue; + + if (newSettingValue && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { + const restoreMessage = this.restorePreviousSettingMessage(settingId); + actions.push({ + class: undefined, + id: 'restoreSetting', + enabled: true, + tooltip: restoreMessage, + label: restoreMessage, + run: () => { + return this.restoreSetting(settingId); + } + }); + } else if (newSettingValue) { + const setting = this.getSetting(settingId); + const trySettingMessage = setting ? this.getSettingMessage(setting, newSettingValue) : undefined; + + if (setting && trySettingMessage) { + actions.push({ + class: undefined, + id: 'trySetting', + enabled: currentSettingValue !== newSettingValue, + tooltip: trySettingMessage, + label: trySettingMessage, + run: () => { + this.setSetting(settingId, currentSettingValue, newSettingValue); + } + }); + } + } + + const viewInSettingsMessage = this.viewInSettingsMessage(settingId, actions.length > 0); + actions.push({ + class: undefined, + enabled: true, + id: 'viewInSettings', + tooltip: viewInSettingsMessage, + label: viewInSettingsMessage, + run: () => { + return this._preferencesService.openApplicationSettings({ query: `@id:${settingId}` }); + } + }); + + return actions; + } + + async updateSetting(uri: URI, x: number, y: number) { + const actions = this.getActions(uri); + if (!actions) { + return; } - await this.configurationService.updateValue(settingId, newSettingValue, ConfigurationTarget.USER); + + this._contextMenuService.showContextMenu({ + getAnchor: () => ({ x, y }), + getActions: () => actions, + getActionViewItem: (action) => { + return new ActionViewItem(action, action, { label: true }); + }, + }); } } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index bc215847e4911..15db5eac97994 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Schemas } from 'vs/base/common/network'; +import { IAction } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Registry } from 'vs/platform/registry/common/platform'; import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const configuration: IConfigurationNode = { 'id': 'examples', @@ -50,98 +52,63 @@ suite('Markdown Setting Renderer Test', () => { ensureNoDisposablesAreLeakedInTestSuite(); let configurationService: TestConfigurationService; + let preferencesService: IPreferencesService; + let contextMenuService: IContextMenuService; let settingRenderer: SimpleSettingRenderer; suiteSetup(() => { configurationService = new MarkdownConfigurationService(); + preferencesService = {}; + contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService); }); suiteTeardown(() => { Registry.as(Extensions.Configuration).deregisterConfigurations([configuration]); }); - test('render boolean setting', () => { + test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: Boolean Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Enable "Example: Boolean Setting" now | View in Settings)`); - - const htmlWithValueSetToFalse = ''; - const renderedHtmlWithValueSetToFalse = htmlRenderer(htmlWithValueSetToFalse); - assert.equal(renderedHtmlWithValueSetToFalse, - `(Disable "Example: Boolean Setting2" now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: Boolean Setting" in Settings)`); + assert.strictEqual(renderedHtmlNoValue, + ``); }); - test('render string setting', () => { - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; - const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: String Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: String Setting" to "two" now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: String Setting" in Settings)`); + test('actions with no value', () => { + const uri = URI.parse(settingRenderer.settingToUriString('example.booleanSetting')); + const actions = settingRenderer.getActions(uri); + assert.strictEqual(actions?.length, 1); + assert.strictEqual(actions[0].label, 'View "Example: Boolean Setting" in Settings'); }); - test('render number setting', () => { - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; - const renderedHtmlNoValue = htmlRenderer(htmlNoValue); - assert.equal(renderedHtmlNoValue, - `(View "Example: Number Setting" in Settings)`); - - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: Number Setting" to 2 now | View in Settings)`); - - const htmlSameValue = ''; - const renderedHtmlSameValue = htmlRenderer(htmlSameValue); - assert.equal(renderedHtmlSameValue, - `(View "Example: Number Setting" in Settings)`); - }); - - test('updating and restoring the setting through the renderer changes what is rendered', async () => { + test('actions with value + updating and restoring', async () => { await configurationService.setUserConfiguration('example', { stringSetting: 'two' }); - const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlWithValue = ''; - const renderedHtmlWithValue = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValue, - `(Set "Example: String Setting" to "three" now | View in Settings)`); - assert.equal(configurationService.getValue('example.stringSetting'), 'two'); - - // Update the value - await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/three`)); - assert.equal(configurationService.getValue('example.stringSetting'), 'three'); - const renderedHtmlWithValueAfterUpdate = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValueAfterUpdate, - `(Restore value of "Example: String Setting" | View in Settings)`); - - // Restore the value - await settingRenderer.updateSettingValue(URI.parse(`${Schemas.codeSetting}://example.stringSetting/two`)); - assert.equal(configurationService.getValue('example.stringSetting'), 'two'); - const renderedHtmlWithValueAfterRestore = htmlRenderer(htmlWithValue); - assert.equal(renderedHtmlWithValueAfterRestore, - `(Set "Example: String Setting" to "three" now | View in Settings)`); + const uri = URI.parse(settingRenderer.settingToUriString('example.stringSetting', 'three')); + + const verifyOriginalState = (actions: IAction[] | undefined): actions is IAction[] => { + assert.strictEqual(actions?.length, 2); + assert.strictEqual(actions[0].label, 'Set "Example: String Setting" to "three"'); + assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'two'); + return true; + }; + + const actions = settingRenderer.getActions(uri); + if (verifyOriginalState(actions)) { + // Update the value + await actions[0].run(); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); + const actionsUpdated = settingRenderer.getActions(uri); + assert.strictEqual(actionsUpdated?.length, 2); + assert.strictEqual(actionsUpdated[0].label, 'Restore value of "Example: String Setting"'); + assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); + + // Restore the value + await actionsUpdated[0].run(); + verifyOriginalState(settingRenderer.getActions(uri)); + } }); }); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 27846ded77bae..f03e00087d372 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -116,6 +116,8 @@ export class ReleaseNotesManager { 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') { + this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), e.message.value.x, e.message.value.y); } })); @@ -220,8 +222,7 @@ export class ReleaseNotesManager { private async onDidClickLink(uri: URI) { if (uri.scheme === Schemas.codeSetting) { - await this._simpleSettingRenderer.updateSettingValue(uri); - this.updateHtml(); + // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') .then(updated => this._openerService.open(updated, { allowCommands: ['workbench.action.openSettings'] })) @@ -254,6 +255,49 @@ export class ReleaseNotesManager { @@ -303,6 +347,13 @@ export class ReleaseNotesManager { }); }; + window.addEventListener('click', event => { + const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; + if (href && href.startsWith('${Schemas.codeSetting}')) { + vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.screenX, y: event.screenY }}); + } + }); + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); @@ -313,17 +364,17 @@ export class ReleaseNotesManager { private onDidChangeConfiguration(e: IConfigurationChangeEvent): void { if (e.affectsConfiguration('update.showReleaseNotes')) { - this.updateWebview(); + this.updateCheckboxWebview(); } } private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { - this.updateWebview(); + this.updateCheckboxWebview(); } } - private updateWebview() { + private updateCheckboxWebview() { if (this._currentReleaseNotes) { this._currentReleaseNotes.webview.postMessage({ type: 'showReleaseNotes', From f1b761fea526ae882891710a3c73bb3852f36ced Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:18:54 +0100 Subject: [PATCH 0220/1863] voice - reuse `startVoiceChatWithHoldMode` --- .../actions/voiceChatActions.ts | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 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 fecd9449db4c2..75867e30b4b51 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -405,10 +405,11 @@ class VoiceChatSessions { export const VOICE_KEY_HOLD_THRESHOLD = 500; -async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise): Promise { - if (!holdMode) { - return; - } +async function startVoiceChatWithHoldMode(id: string, accessor: ServicesAccessor, target: 'inline' | 'quick' | 'view' | 'focused', context?: IChatExecuteActionContext): Promise { + const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(id); let acceptVoice = false; const handle = disposableTimeout(() => { @@ -416,6 +417,14 @@ async function awaitHoldAndAccept(session: IVoiceChatSession, holdMode?: Promise session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms }, VOICE_KEY_HOLD_THRESHOLD); + const controller = await VoiceChatSessionControllerFactory.create(accessor, target); + if (!controller) { + handle.dispose(); + return; + } + + const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + await holdMode; handle.dispose(); @@ -430,20 +439,8 @@ class VoiceChatWithHoldModeAction extends Action2 { super(desc); } - async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - const keybindingService = accessor.get(IKeybindingService); - - const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); - - const controller = await VoiceChatSessionControllerFactory.create(accessor, this.target); - if (!controller) { - return; - } - - const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); - - awaitHoldAndAccept(session, holdMode); + run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + return startVoiceChatWithHoldMode(this.desc.id, accessor, this.target, context); } } @@ -534,11 +531,6 @@ export class StartVoiceChatAction extends Action2 { } async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { - const instantiationService = accessor.get(IInstantiationService); - const keybindingService = accessor.get(IKeybindingService); - - const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); - const widget = context?.widget; if (widget) { // if we already get a context when the action is executed @@ -551,14 +543,7 @@ export class StartVoiceChatAction extends Action2 { widget.focusInput(); } - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); - if (!controller) { - return; - } - - const session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); - - awaitHoldAndAccept(session, holdMode); + return startVoiceChatWithHoldMode(this.desc.id, accessor, 'focused', context); } } From f28c9e1cb6c8fe8e6bece5e6519c1a3e804925d9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 13 Feb 2024 12:27:34 +0100 Subject: [PATCH 0221/1863] voice - allow pause between agent and slash command --- .../contrib/chat/common/voiceChat.ts | 15 ++++---- .../chat/test/common/voiceChat.test.ts | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index ff8679fa3ea56..b1d1d468ea1fa 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -118,11 +118,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { return phrases; } - private toText(value: IPhraseValue, type: PhraseTextType, options: IVoiceChatSessionOptions): string { - if (type === PhraseTextType.COMMAND && options.usesAgents) { - type = PhraseTextType.AGENT_AND_COMMAND; // rewrite `/fix` to `@workspace /foo` in this case - } - + private toText(value: IPhraseValue, type: PhraseTextType): string { switch (type) { case PhraseTextType.AGENT: return `${VoiceChatService.AGENT_PREFIX}${value.agent}`; @@ -158,7 +154,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND, options), ...originalWords.slice(4)]; + transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND), ...originalWords.slice(4)]; waitingForInput = originalWords.length === 4; @@ -173,7 +169,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.AGENT, options), ...originalWords.slice(2)]; + transformedWords = [this.toText(phrase, PhraseTextType.AGENT), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; @@ -187,7 +183,10 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { - transformedWords = [this.toText(phrase, PhraseTextType.COMMAND, options), ...originalWords.slice(2)]; + transformedWords = [this.toText(phrase, options.usesAgents && !detectedAgent ? + PhraseTextType.AGENT_AND_COMMAND : // rewrite `/fix` to `@workspace /foo` in this case + PhraseTextType.COMMAND // when we have not yet detected an agent before + ), ...originalWords.slice(2)]; waitingForInput = originalWords.length === 2; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index bfa4baf30e936..6751d47ede433 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -250,6 +250,43 @@ suite('VoiceChat', () => { assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); assert.strictEqual(event?.text, options.usesAgents ? '@workspace for at workspace' : 'At workspace, for at workspace'); assert.strictEqual(event?.waitingForInput, false); + + // Slash command detected after agent recognized + if (options.usesAgents) { + createSession(options); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'slash' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, 'slash'); + assert.strictEqual(event?.waitingForInput, false); + + emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + + createSession(options); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'At workspace' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '@workspace'); + assert.strictEqual(event?.waitingForInput, true); + + emitter.fire({ status: SpeechToTextStatus.Recognized, text: 'slash fix' }); + assert.strictEqual(event?.status, SpeechToTextStatus.Recognized); + assert.strictEqual(event?.text, '/fix'); + assert.strictEqual(event?.waitingForInput, true); + } } test('waiting for input', async () => { From c5d0386f54cc012c76c914214aa02592d0899958 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 12:54:37 +0100 Subject: [PATCH 0222/1863] some API todos (#205095) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 5a667140993ad..1a48b38195250 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -41,6 +41,8 @@ declare module 'vscode' { /** * The ID of the chat agent to which this request was directed. */ + // TODO@API NAME agent + // TODO@API TYPE {agent:string, extension:string} readonly agentId: string; /** @@ -286,7 +288,7 @@ declare module 'vscode' { // TODO@API // notify(request: ChatResponsePart, reference: string): boolean; // BETTER - // requestResponseStream(callback: (stream: ChatAgentResponseStream) => void, why?: string): void; + // requestResponseStream(result: ChatAgentResult, callback: (stream: ChatAgentResponseStream) => void, why?: string): void; // TODO@API // clear NEVER happens @@ -367,6 +369,7 @@ declare module 'vscode' { * @param value A plain text value. * @returns This stream. */ + // TODO@API remove? text(value: string): ChatAgentResponseStream; /** @@ -377,6 +380,7 @@ declare module 'vscode' { * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ + // TODO@API NAME: content markdown(value: string | MarkdownString): ChatAgentResponseStream; /** From 4d542ef5d144449f1c399dc5c06cbf296380b7fe Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Tue, 13 Feb 2024 13:55:49 +0000 Subject: [PATCH 0223/1863] 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 19257411641b5..2b95a4341ce6b 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 519fab7f93a67..f295d14a03c45 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 794a8e60d5710..6ef9f9764ea56 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 26fed6ffc4ebe..e004aaa86bc3b 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 012855aaaee61..716bb3bcfd689 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 4194cbfdf8b5b5b83a7a525b5470bdddb39da2ea Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:56:22 +0100 Subject: [PATCH 0224/1863] =?UTF-8?q?Git=20-=20=F0=9F=92=84=20more=20histo?= =?UTF-8?q?ry=20provider=20logging=20(#205101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - 💄 more history provider logging * Fix up more logging --- extensions/git/src/historyProvider.ts | 10 +++++++--- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index db9873440286c..64230ecf46bfb 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -83,7 +83,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - Upstream has changed'); + this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - Upstream has changed (${force})`); this._onDidChangeCurrentHistoryItemGroupBase.fire(); } @@ -176,6 +176,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec if (!historyItemId2) { const upstreamRef = await this.resolveHistoryItemGroupBase(historyItemId1); if (!upstreamRef) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve history item group base for '${historyItemId1}'`); return undefined; } @@ -184,14 +185,16 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const ancestor = await this.repository.getMergeBase(historyItemId1, historyItemId2); if (!ancestor) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to resolve common ancestor for '${historyItemId1}' and '${historyItemId2}'`); return undefined; } try { const commitCount = await this.repository.getCommitCount(`${historyItemId1}...${historyItemId2}`); + this.logger.trace(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Resolved common ancestor for '${historyItemId1}' and '${historyItemId2}': ${JSON.stringify({ id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind })}`); return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; } catch (err) { - this.logger.error(`Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); + this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupCommonAncestor - Failed to get ahead/behind for '${historyItemId1}...${historyItemId2}': ${err.message}`); } return undefined; @@ -212,6 +215,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // Base (config -> reflog -> default) const remoteBranch = await this.repository.getBranchBase(historyItemId); if (!remoteBranch?.remote || !remoteBranch?.name || !remoteBranch?.commit || remoteBranch?.type !== RefType.RemoteHead) { + this.logger.info(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to resolve history item group base for '${historyItemId}'`); return undefined; } @@ -222,7 +226,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec }; } catch (err) { - this.logger.error(`Failed to get branch base for '${historyItemId}': ${err.message}`); + this.logger.error(`GitHistoryProvider:resolveHistoryItemGroupBase - Failed to get branch base for '${historyItemId}': ${err.message}`); } return undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 3a92ee61bdc50..73ce611ad2116 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3559,7 +3559,7 @@ class SCMTreeDataSource implements IAsyncDataSource Date: Tue, 13 Feb 2024 14:50:06 +0100 Subject: [PATCH 0225/1863] activitybar: Use orange active highlight in dark HC theme --- .../browser/parts/activitybar/media/activityaction.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 99df125f3a8e1..a47bbe794a0f4 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -107,6 +107,11 @@ border-left: 2px solid; } +.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench.hc-black .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { + border-color: var(--vscode-focusBorder); +} + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { top: 0; height: 100%; From 0776d4f78f4ec9b974990d5414e332ebd82232ba Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 15:38:07 +0100 Subject: [PATCH 0226/1863] polishing the code --- .../bulkEdit/browser/preview/bulkEditPane.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 3a9b9ebab13ae..2d8031cbe778d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -99,7 +99,7 @@ export class BulkEditPane extends ViewPane { this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); this._disposables.add(this._editorService.onDidCloseEditor((e) => { - if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input) { + if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input && e.groupId === this._multiDiffEditor.group?.id) { this._multiDiffEditor = undefined; } })); @@ -108,7 +108,6 @@ export class BulkEditPane extends ViewPane { override dispose(): void { this._tree.dispose(); this._disposables.dispose(); - this._multiDiffEditor = undefined; super.dispose(); } @@ -284,8 +283,8 @@ export class BulkEditPane extends ViewPane { this._sessionDisposables.clear(); if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); + this._multiDiffEditor = undefined; } - this._multiDiffEditor = undefined; } toggleChecked() { @@ -333,7 +332,6 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } - const options: Mutable = { ...e.editorOptions }; let fileElement: FileElement; if (e.element instanceof TextEditElement) { @@ -381,16 +379,15 @@ export class BulkEditPane extends ViewPane { }); } } - const multiDiffSource = URI.from({ scheme: 'refactor-preview', path: JSON.stringify(fileElement.edit.uri) }); + const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; - this._multiDiffEditor = - await this._editorService.openEditor({ - multiDiffSource: URI.revive(multiDiffSource), - resources, - label: label, - description: label, - options: options, - }) as MultiDiffEditor; + this._multiDiffEditor = await this._editorService.openEditor({ + multiDiffSource, + resources, + label, + description: label, + options, + }) as MultiDiffEditor; } private _onContextMenu(e: ITreeContextMenuEvent): void { From 72d1ad1be81ab1dbce885786dba4f5b21800c7a0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 13 Feb 2024 15:42:19 +0100 Subject: [PATCH 0227/1863] remove ChatMessage and add types LanguageModelMessage types so that they can evolve at their own pace https://github.com/microsoft/vscode/issues/199908 --- .../workbench/api/common/extHost.api.impl.ts | 3 +++ .../api/common/extHostChatProvider.ts | 10 +++++-- .../api/common/extHostTypeConverters.ts | 26 +++++++++++++++---- src/vs/workbench/api/common/extHostTypes.ts | 26 +++++++++++++++++++ .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.proposed.chat.d.ts | 24 ----------------- .../vscode.proposed.chatProvider.d.ts | 22 ++++++++++++++++ .../vscode.proposed.chatRequestAccess.d.ts | 23 +++++++++++++++- 8 files changed, 102 insertions(+), 33 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.chat.d.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2b4bfec98a643..881bd742f468c 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1665,6 +1665,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, + LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, + LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, + LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, }; }; } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 108a893fb4d62..923a2db9edaa8 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -151,7 +151,13 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + if (data.provider.provideLanguageModelResponse2) { + return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } else { + // TODO@jrieken remove + return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); + } + } //#region --- making request @@ -269,7 +275,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - const requestPromise = that._proxy.$fetchResponse(from, languageModelId, requestId, messages.map(typeConvert.ChatMessage.from), options ?? {}, cts.token); + 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 }); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 62f12e9aae7a6..2c4bba8099f89 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2242,12 +2242,28 @@ export namespace ChatMessage { export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); } +} - export function from(message: vscode.ChatMessage): chatProvider.IChatMessage { - return { - role: ChatMessageRole.from(message.role), - content: message.content, - }; +export namespace LanguageModelMessage { + + export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { + 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); + } + } + + export function from(message: vscode.LanguageModelMessage): chatProvider.IChatMessage { + if (message instanceof types.LanguageModelSystemMessage) { + return { role: chatProvider.ChatMessageRole.System, content: message.content }; + } else if (message instanceof types.LanguageModelUserMessage) { + return { role: chatProvider.ChatMessageRole.User, content: message.content }; + } else if (message instanceof types.LanguageModelAssistantMessage) { + 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 492cf921558a2..09327af7d1b61 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4274,6 +4274,32 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { ) { } } +export class LanguageModelSystemMessage { + content: string; + + constructor(content: string) { + this.content = content; + } +} + +export class LanguageModelUserMessage { + content: string; + name: string | undefined; + + constructor(content: string, name?: string) { + this.content = content; + this.name = name; + } +} + +export class LanguageModelAssistantMessage { + content: string; + + constructor(content: string) { + this.content = content; + } +} + //#endregion //#region ai diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0edc713a74c07..879db06731102 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,7 +11,6 @@ export const allApiProposals = Object.freeze({ 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', - chat: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chat.d.ts', chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chat.d.ts b/src/vscode-dts/vscode.proposed.chat.d.ts deleted file mode 100644 index 5dc2704b1767d..0000000000000 --- a/src/vscode-dts/vscode.proposed.chat.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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' { - - // ChatML - export enum ChatMessageRole { - System = 0, - User = 1, - // TODO@API name: align with ChatAgent (or whatever we'll rename that to) - Assistant = 2, - } - - // ChatML - export class ChatMessage { - role: ChatMessageRole; - content: string; - - constructor(role: ChatMessageRole, content: string); - } - -} diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index e35fd5547d14d..7ac3ddba1b9cb 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,6 +5,27 @@ declare module 'vscode' { + // TODO@API NAME: ChatMessageKind? + export enum ChatMessageRole { + System = 0, + User = 1, + // TODO@API name: align with ChatAgent (or whatever we'll rename that to) + Assistant = 2, + } + + /** + * A chat message that is used to make chat request against a language model. + */ + export class ChatMessage { + + readonly role: ChatMessageRole; + + readonly content: string; + + constructor(role: ChatMessageRole, content: string); + } + + export interface ChatResponseFragment { index: number; part: string; @@ -17,6 +38,7 @@ declare module 'vscode' { */ export interface ChatResponseProvider { provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2?(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index e1a151e77f43f..431f3c2d708da 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -17,6 +17,27 @@ declare module 'vscode' { stream: AsyncIterable; } + //TODO@API see https://learn.microsoft.com/en-us/dotnet/api/azure.ai.openai.chatrequestmessage?view=azure-dotnet-preview + // this allows to grow message by type, e.g add more content types to User message to support multimodal language models + + export class LanguageModelSystemMessage { + content: string; + constructor(content: string); + } + + export class LanguageModelUserMessage { + content: string; + name: string | undefined; + constructor(content: string, name?: string); + } + + export class LanguageModelAssistantMessage { + content: string; + constructor(content: string); + } + + export type LanguageModelMessage = LanguageModelSystemMessage | LanguageModelUserMessage | LanguageModelAssistantMessage; + /** * 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. @@ -49,7 +70,7 @@ declare module 'vscode' { * @param messages * @param options */ - makeChatRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; + makeChatRequest(messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse; } export interface LanguageModelAccessOptions { From 4531f1a2be1795084994f133b0dda300d189d274 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 13 Feb 2024 15:47:56 +0100 Subject: [PATCH 0228/1863] Fix cursor position after splitting window (#205107) The cursor is not in the correct position after splitting the window Fixes #203969 --- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index d2fd3da2e0330..742c76b7d3710 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -280,9 +280,9 @@ class DirtyDiffWidget extends PeekViewWidget { this._actionbarWidget!.context = [diffEditorModel.modified.uri, providerSpecificChanges, contextIndex]; if (usePosition) { this.show(position, height); + this.editor.setPosition(position); + this.editor.focus(); } - this.editor.setPosition(position); - this.editor.focus(); } private renderTitle(label: string): void { From 2b7bef020bc73d8f9dac84eccda619e232c8c8f4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 16:05:15 +0100 Subject: [PATCH 0229/1863] Chat Agent API updates (#205104) * update `ChatAgentContext#history` to be an array of turns https://github.com/microsoft/vscode/issues/199908 * add agent info (agent and extension id) to `ChatAgentResponseTurn` and `ChatAgentRequestTurn` https://github.com/microsoft/vscode/issues/199908 * update chat test snapshots --- .../api/browser/mainThreadChatAgents2.ts | 4 ++- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 26 ++++--------------- src/vs/workbench/api/common/extHostTypes.ts | 16 +++++++++--- .../contrib/chat/common/chatAgents.ts | 2 ++ .../contrib/chat/common/chatModel.ts | 2 +- .../__snapshots__/Chat_can_deserialize.0.snap | 4 +++ .../__snapshots__/Chat_can_serialize.1.snap | 4 +++ .../chat/test/common/chatService.test.ts | 4 ++- .../chat/test/common/voiceChat.test.ts | 5 ++++ .../vscode.proposed.chatAgents2.d.ts | 25 ++++++++++-------- 11 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 3229725e96a8b..fa796bde94873 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -13,6 +13,7 @@ import { getWordAtText } from 'vs/editor/common/core/wordHelper'; import { CompletionContext, CompletionItem, CompletionItemKind, CompletionList } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -74,10 +75,11 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._agents.deleteAndDispose(handle); } - $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void { + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void { let lastSlashCommands: IChatAgentCommand[] | undefined; const d = this._chatAgentService.registerAgent({ id: name, + extensionId: extension, metadata: revive(metadata), invoke: async (request, progress, history, token) => { this._pendingProgress.set(request.requestId, progress); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2f931bb83ef8d..c17e879971a8e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1195,7 +1195,7 @@ export interface IExtensionChatAgentMetadata extends Dto { } export interface MainThreadChatAgentsShape2 extends IDisposable { - $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void; + $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void; $registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void; $unregisterAgentCompletionsProvider(handle: number): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 56518bd78e4b8..e32d418c24a2f 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -176,7 +176,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); - this._proxy.$registerAgent(handle, name, {}); + this._proxy.$registerAgent(handle, extension.identifier, name, {}); return agent.apiAgent; } @@ -197,11 +197,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistory(request, context); - const convertedHistory2 = await this.prepareHistoryTurns(request, context); + const convertedHistory = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory, history2: convertedHistory2 }, + { history: convertedHistory, history2: convertedHistory }, stream.apiObject, token ); @@ -229,21 +228,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistory(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise { - return coalesce(await Promise.all(context.history - .map(async h => { - const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? - ehResult : - { ...ehResult, metadata: undefined }; - return { - request: typeConvert.ChatAgentRequest.to(h.request), - response: coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))), - result, - } satisfies vscode.ChatAgentHistoryEntry; - }))); - } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; @@ -255,11 +239,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.agentId, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to))); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, h.request.agentId)); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agentId: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 09327af7d1b61..9122ad1b88101 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4258,20 +4258,28 @@ export class ChatResponseReferencePart { export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { + agentId: string; constructor( readonly prompt: string, - readonly agentId: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - ) { } + readonly agent: { extensionId: string; agentId: string }, + ) { + this.agentId = agent.agentId; + } } export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { + + agentId: string; + constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agentId: string, - ) { } + readonly agent: { extensionId: string; agentId: string } + ) { + this.agentId = agent.agentId; + } } export class LanguageModelSystemMessage { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 289d3cb15b436..adbaa77da2a9e 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; @@ -26,6 +27,7 @@ export interface IChatAgentHistoryEntry { export interface IChatAgentData { id: string; + extensionId: ExtensionIdentifier; metadata: IChatAgentMetadata; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index aac4dc60c4fe5..56f75f06edac6 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -788,7 +788,7 @@ export class ChatModel extends Disposable implements IChatModel { followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent ? { id: r.response.agent.id, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props + agent: r.response?.agent ? { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props slashCommand: r.response?.slashCommand, usedContext: r.response?.usedContext, contentReferences: r.response?.contentReferences diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index cd6fa9b8139e9..1e34cf65b08fb 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -53,6 +53,10 @@ vote: undefined, agent: { id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, metadata: { } }, slashCommand: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 8420b35111ed4..16a53a88b077b 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -53,6 +53,10 @@ vote: undefined, agent: { id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, metadata: { } }, slashCommand: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 3358df8cd4dcd..eface4d487db3 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -29,7 +29,7 @@ import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; class SimpleTestProvider extends Disposable implements IChatProvider { @@ -57,6 +57,7 @@ class SimpleTestProvider extends Disposable implements IChatProvider { const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, + extensionId: nullExtensionDescription.identifier, metadata: {}, async provideSlashCommands(token) { return []; @@ -109,6 +110,7 @@ suite('Chat', () => { const agent = { id: 'testAgent', + extensionId: nullExtensionDescription.identifier, metadata: { isDefault: true }, async invoke(request, progress, history, token) { return {}; diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 6751d47ede433..fd4184ab8eb97 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -10,10 +10,12 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProviderResult } from 'vs/editor/common/languages'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; suite('VoiceChat', () => { @@ -22,6 +24,9 @@ suite('VoiceChat', () => { } class TestChatAgent implements IChatAgent { + + extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; + constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1a48b38195250..3ee255c32e1bf 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -38,13 +38,16 @@ declare module 'vscode' { */ readonly prompt: string; - /** - * The ID of the chat agent to which this request was directed. - */ // TODO@API NAME agent // TODO@API TYPE {agent:string, extension:string} + /** @deprecated */ readonly agentId: string; + /** + * The ID of the chat agent to which this request was directed. + */ + readonly agent: { extensionId: string; agentId: string }; + /** * The name of the {@link ChatAgentCommand command} that was selected for this request. */ @@ -56,7 +59,7 @@ declare module 'vscode' { // TODO@API is this needed? readonly variables: ChatAgentResolvedVariable[]; - private constructor(prompt: string, agentId: string, command: string | undefined, variables: ChatAgentResolvedVariable[],); + private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); } // TODO@API name: Turn? @@ -72,23 +75,23 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; + /** @deprecated */ readonly agentId: string; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: string); + readonly agent: { extensionId: string; agentId: string }; + + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } export interface ChatAgentContext { /** - * @deprecated + * All of the chat messages so far in the current chat session. */ - history: ChatAgentHistoryEntry[]; - - // location: + readonly history: ReadonlyArray; /** - * All of the chat messages so far in the current chat session. + * @deprecated, use histroy */ - // TODO@API name: histroy readonly history2: ReadonlyArray; } From 80f1ca24b43780278fd48c2136afbc87c9a53967 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 09:44:08 -0600 Subject: [PATCH 0230/1863] fix #205108 --- .../browser/accessibilitySignalService.ts | 2 +- .../browser/accessibilityConfiguration.ts | 7 +++++-- .../accessibilitySignals/browser/commands.ts | 10 ++++++++-- .../preferences/browser/settingsLayout.ts | 20 +++++-------------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index 915a61aba5bf9..d2689b002d73e 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -129,7 +129,7 @@ export class AccessibilitySignalService extends Disposable implements IAccessibi return; } this.playingSounds.add(sound); - const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignals/browser/media/${sound.fileName}`).toString(true); + const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignal/browser/media/${sound.fileName}`).toString(true); try { const sound = this.sounds.get(url); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index c322814c2bae9..afd920ac1f9a2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -731,7 +731,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi announcement = announcement ? 'auto' : 'off'; } } - configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); @@ -742,7 +742,10 @@ Registry.as(WorkbenchExtensions.ConfigurationMi migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; const sound = accessor(item.legacySoundSettingsKey); - configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement ? { announcement, sound } : { sound } }]); + if (announcement !== undefined && typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; + } + configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } }))); diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 50db5214d76b5..666516df128eb 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -21,6 +21,9 @@ export class ShowSignalSoundHelp extends Action2 { id: ShowSignalSoundHelp.ID, title: localize2('signals.sound.help', "Help: List Signal Sounds"), f1: true, + metadata: { + description: localize('accessibility.sound.help.description', "List all accessibility sounds / audio cues and configure their settings") + } }); } @@ -41,7 +44,7 @@ export class ShowSignalSoundHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); + qp.selectedItems = items.filter(i => accessibilitySignalService.isSoundEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.sound') !== 'never'); qp.onDidAccept(() => { const enabledSounds = qp.selectedItems.map(i => i.signal); const disabledSounds = qp.items.map(i => (i as any).signal).filter(i => !enabledSounds.includes(i)); @@ -82,6 +85,9 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { id: ShowAccessibilityAnnouncementHelp.ID, title: localize2('accessibility.announcement.help', "Help: List Signal Announcements"), f1: true, + metadata: { + description: localize('accessibility.announcement.help.description', "List all accessibility announcements / alerts and configure their settings") + } }); } @@ -102,7 +108,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { })); const qp = quickInputService.createQuickPick(); qp.items = items; - qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); + qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 6995c33c3024b..2edaff0b3b707 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -146,6 +146,11 @@ export const tocData: ITOCEntry = { id: 'features', label: localize('features', "Features"), children: [ + { + id: 'features/accessibilitySignals', + label: localize('accessibility.signals', 'Accessibility Signals'), + settings: ['accessibility.signals.*'] + }, { id: 'features/accessibility', label: localize('accessibility', "Accessibility"), @@ -221,21 +226,6 @@ export const tocData: ITOCEntry = { label: localize('notebook', 'Notebook'), settings: ['notebook.*', 'interactiveWindow.*'] }, - { - id: 'features/signals', - label: localize('signals', 'Signals'), - settings: ['signals.*'] - }, - { - id: 'features/accessibilitySignals', - label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] - }, - { - id: 'features/accessibilitySignals', - label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] - }, { id: 'features/mergeEditor', label: localize('mergeEditor', 'Merge Editor'), From 29a8551519b0a5eceb2979e8cb1ec1f02743f59e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 13 Feb 2024 07:52:41 -0800 Subject: [PATCH 0231/1863] a new cell editor might already be in view (#205023) account for creating a editor cell already within viewable range - edit markdown --- .../notebook/browser/view/notebookCellList.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index d9d1dab798d4b..94cbebb16b55e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1023,19 +1023,19 @@ export class NotebookCellList extends WorkbenchList implements ID const element = this.view.element(viewIndex); if (element.editorAttached) { - this._revealRangeCommon(viewIndex, range, false, false); + this._revealRangeCommon(viewIndex, range); } else { const elementHeight = this.view.elementHeight(viewIndex); - let upwards = false; + let alignHint: 'top' | 'bottom' | undefined = undefined; if (elementTop + elementHeight <= scrollTop) { - // scroll downwards + // scroll up this.view.setScrollTop(elementTop); - upwards = false; + alignHint = 'top'; } else if (elementTop >= wrapperBottom) { - // scroll upwards + // scroll down this.view.setScrollTop(elementTop - this.view.renderHeight / 2); - upwards = true; + alignHint = 'bottom'; } const editorAttachedPromise = new Promise((resolve, reject) => { @@ -1045,7 +1045,7 @@ export class NotebookCellList extends WorkbenchList implements ID }); return editorAttachedPromise.then(() => { - this._revealRangeCommon(viewIndex, range, true, upwards); + this._revealRangeCommon(viewIndex, range, alignHint); }); } } @@ -1112,7 +1112,7 @@ export class NotebookCellList extends WorkbenchList implements ID } } - private _revealRangeCommon(viewIndex: number, range: Selection | Range, newlyCreated: boolean, alignToBottom: boolean) { + private _revealRangeCommon(viewIndex: number, range: Selection | Range, alignHint?: 'top' | 'bottom' | undefined) { const element = this.view.element(viewIndex); const scrollTop = this.getViewScrollTop(); const wrapperBottom = this.getViewScrollBottom(); @@ -1134,17 +1134,15 @@ export class NotebookCellList extends WorkbenchList implements ID this.view.setScrollTop(positionTop - 30); } else if (positionTop > wrapperBottom) { this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); - } else if (newlyCreated) { - // newly scrolled into view - if (alignToBottom) { - // align to the bottom - this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); - } else { - // align to to top - this.view.setScrollTop(positionTop - 30); - } + } else if (alignHint === 'bottom') { + // Scrolled into view from below + this.view.setScrollTop(scrollTop + positionTop - wrapperBottom + 30); + } else if (alignHint === 'top') { + // Scrolled into view from above + this.view.setScrollTop(positionTop - 30); } + element.revealRangeInCenter(range); } //#endregion From 760ee5b9976bc9ee647e5aa5ecfa26c42780fab4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 10:22:14 -0600 Subject: [PATCH 0232/1863] 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 5392e1787dd91..8308105d9f5a1 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 ed78f6292e778..ee0a778cc0fc1 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 0233/1863] 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 3102938a3be18..0b3015ddf756a 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 1d909e6fc300a..fd88e4c09824c 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 0000000000000..8b74aae7aeba4 --- /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 ee0a778cc0fc1..7f30c3bca532e 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 0234/1863] 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 7f30c3bca532e..330be0b35a513 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 0235/1863] 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 5ed9e674af23b..f2167db7f1797 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 fd88e4c09824c..2972104787fc1 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 0236/1863] 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 f2167db7f1797..fc925b646bb1c 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 2972104787fc1..f8a1dd4b11145 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 8b74aae7aeba4..431a0e9c7158f 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 0237/1863] 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 f8a1dd4b11145..a3a7b55757a2b 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 0000000000000..161b85154786a --- /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 5f33bf34ce74baeebfa231dfd3dc2f15a247ae5c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 13 Feb 2024 17:51:04 +0100 Subject: [PATCH 0238/1863] wip --- .../multiDiffEditorWidget.ts | 4 +- .../multiDiffEditorWidgetImpl.ts | 32 ++++++++++-- src/vs/workbench/common/editor.ts | 5 ++ .../bulkEdit/browser/preview/bulkEditPane.ts | 49 +++++++++---------- .../browser/multiDiffEditor.ts | 4 +- .../services/editor/common/editorService.ts | 1 - 6 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 9a19e5e5669ea..e2392cc8b4a1e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -44,8 +44,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public scrollTo(uri: URI): void { - this._widgetImpl.get().scrollTo(uri); + public reveal(resource: { original: URI } | { modified: URI }, lineNumber: number): void { + this._widgetImpl.get().reveal(resource, lineNumber); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 5619b08b2cd5f..b8547c7d347b7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -25,6 +25,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -104,6 +105,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -186,10 +188,20 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public scrollTo(originalUri: URI): void { + public reveal(resource: IMultiDiffResource, lineNumber: number): void { const viewItems = this._viewItems.get(); - const index = viewItems?.findIndex(item => item.viewModel.originalUri?.toString() === originalUri.toString()); - let scrollTop = 0; + let searchCallback: (item: VirtualizedViewItem) => boolean; + if (isMultiDiffOriginalResourceUri(resource)) { + searchCallback = (item) => item.viewModel.originalUri?.toString() === resource.original.toString(); + } else { + searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); + } + const index = viewItems.findIndex(searchCallback); + const scrollTopWithinItem = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri + // following does not neccessarily correspond to the appropriate scroll top within the editor + const maxScroll = viewItems[index].template.get()?.maxScroll.get().maxScroll; + let scrollTop = (maxScroll && scrollTopWithinItem < maxScroll) ? scrollTopWithinItem : 0; for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -289,6 +301,20 @@ interface IMultiDiffDocState { selections?: ISelection[]; } +interface IMultiDiffOriginalResource { + original: URI; +} + +interface IMultiDiffModifiedResource { + modified: URI; +} + +function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalResource { + return 'original' in obj && obj.original instanceof URI; +} + +type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index bf6785d967516..322582ad33c76 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,6 +505,11 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; + + /** + * Reveal the following resource on open + */ + readonly revealResource?: IResourceDiffEditorInput; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 2d8031cbe778d..ff0cc489d2808 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,7 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -36,8 +36,7 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { @@ -69,7 +68,8 @@ export class BulkEditPane extends ViewPane { private _currentResolve?: (edit?: ResourceEdit[]) => void; private _currentInput?: BulkFileOperations; private _currentProvider?: BulkEditPreviewProvider; - private _multiDiffEditor?: MultiDiffEditor; + private _fileOperations?: BulkFileOperation[]; + private _resources?: IResourceDiffEditorInput[]; constructor( options: IViewletViewOptions, @@ -98,11 +98,6 @@ export class BulkEditPane extends ViewPane { this._ctxHasCategories = BulkEditPane.ctxHasCategories.bindTo(contextKeyService); this._ctxGroupByFile = BulkEditPane.ctxGroupByFile.bindTo(contextKeyService); this._ctxHasCheckedChanges = BulkEditPane.ctxHasCheckedChanges.bindTo(contextKeyService); - this._disposables.add(this._editorService.onDidCloseEditor((e) => { - if (this._multiDiffEditor && e.editor === this._multiDiffEditor.input && e.groupId === this._multiDiffEditor.group?.id) { - this._multiDiffEditor = undefined; - } - })); } override dispose(): void { @@ -281,10 +276,6 @@ export class BulkEditPane extends ViewPane { this._currentInput = undefined; this._setState(State.Message); this._sessionDisposables.clear(); - if (this._multiDiffEditor && this._multiDiffEditor.input && this._multiDiffEditor.group) { - this._editorService.closeEditor({ editor: this._multiDiffEditor.input, groupId: this._multiDiffEditor.group.id }); - this._multiDiffEditor = undefined; - } } toggleChecked() { @@ -347,12 +338,26 @@ export class BulkEditPane extends ViewPane { return; } - if (this._multiDiffEditor) { - // Multi diff editor already visible - this._multiDiffEditor.scrollTo(fileElement.edit.uri); - return; + let resources: IResourceDiffEditorInput[]; + if (this._fileOperations === fileOperations && this._resources) { + resources = this._resources; + } else { + resources = await this._getResources(fileOperations); } + const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); + const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); + const label = 'Refactor Preview'; + this._editorService.openEditor({ + multiDiffSource, + revealResource, + resources, + label, + description: label, + options, + }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + private async _getResources(fileOperations: BulkFileOperation[]): Promise { const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { const operationUri = operation.uri; @@ -379,15 +384,7 @@ export class BulkEditPane extends ViewPane { }); } } - const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); - const label = 'Refactor Preview'; - this._multiDiffEditor = await this._editorService.openEditor({ - multiDiffSource, - resources, - label, - description: label, - options, - }) as MultiDiffEditor; + return resources; } private _onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 21b1d64a6fac1..be2fab9317566 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -72,8 +72,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 1be2060f98c72..19b51a652c7fa 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -236,7 +236,6 @@ export interface IEditorService { * Open an editor in an editor group. * * @param editor the editor to open - * @param options the options to use for the editor * @param group the target group. If unspecified, the editor will open in the currently * active group. Use `SIDE_GROUP` to open the editor in a new editor group to the side * of the currently active group. 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 0239/1863] 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 b41804eb8cc26..74ec952ba1c44 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 0240/1863] 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 8308105d9f5a1..c8f3ec6995860 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 330be0b35a513..4a5dbb55994b5 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 1f57cd8267e146bbedc1d7a00d9c09baadb6a4f1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:19:53 +0100 Subject: [PATCH 0241/1863] SCM - fix issue related to event handler registration (#205120) --- .../contrib/scm/browser/scmViewPane.ts | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 73ce611ad2116..500967073c56c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -104,6 +104,7 @@ import { IMenuWorkbenchToolBarOptions, MenuWorkbenchToolBar, WorkbenchToolBar } import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { clamp } from 'vs/base/common/numbers'; +import { ILogService } from 'vs/platform/log/common/log'; // type SCMResourceTreeNode = IResourceNode; // type SCMHistoryItemChangeResourceTreeNode = IResourceNode; @@ -2753,6 +2754,7 @@ export class SCMViewPane extends ViewPane { options: IViewPaneOptions, @ICommandService private readonly commandService: ICommandService, @IEditorService private readonly editorService: IEditorService, + @ILogService private readonly logService: ILogService, @IMenuService private readonly menuService: IMenuService, @ISCMService private readonly scmService: ISCMService, @ISCMViewService private readonly scmViewService: ISCMViewService, @@ -3117,9 +3119,22 @@ export class SCMViewPane extends ViewPane { repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); - if (repository.provider.historyProvider) { - repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateChildren(repository))); - } + const onDidChangeHistoryProvider = () => { + if (!repository.provider.historyProvider) { + this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - no history provider present'); + return; + } + + repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + this.updateChildren(repository); + this.logService.debug('SCMViewPane:onDidChangeCurrentHistoryItemGroup - update children'); + })); + + this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); + }; + + repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); + onDidChangeHistoryProvider(); const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); @@ -3411,6 +3426,7 @@ class SCMTreeDataSource implements IAsyncDataSource ViewMode, @IConfigurationService private readonly configurationService: IConfigurationService, + @ILogService private readonly logService: ILogService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IUriIdentityService private uriIdentityService: IUriIdentityService, ) { @@ -3767,9 +3783,22 @@ class SCMTreeDataSource implements IAsyncDataSource this.historyProviderCache.delete(repository))); - } + const onDidChangeHistoryProvider = () => { + if (!repository.provider.historyProvider) { + this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - no history provider present'); + return; + } + + repositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + this.historyProviderCache.delete(repository); + this.logService.debug('SCMTreeDataSource:onDidChangeCurrentHistoryItemGroup - cache cleared'); + })); + + this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); + }; + + repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); + onDidChangeHistoryProvider(); this.repositoryDisposables.set(repository, repositoryDisposables); } From a2c91f1f288f66c15d1e5614478b21fbfa314964 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 09:22:06 -0800 Subject: [PATCH 0242/1863] debug: update visualizers tree proposal, some initial implementation (#204877) --- .../api/browser/mainThreadDebugService.ts | 15 ++ .../workbench/api/common/extHost.api.impl.ts | 4 + .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostDebugService.ts | 121 ++++++++++++--- .../api/common/extHostTypeConverters.ts | 20 ++- .../contrib/debug/browser/baseDebugView.ts | 26 +++- .../contrib/debug/browser/debugHover.ts | 26 ++-- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/debug/browser/variablesView.ts | 142 ++++++++++++++++-- .../debug/browser/watchExpressionsView.ts | 70 +++++---- .../workbench/contrib/debug/common/debug.ts | 35 ++++- .../contrib/debug/common/debugModel.ts | 32 +++- .../contrib/debug/common/debugViewModel.ts | 21 +++ .../contrib/debug/common/debugVisualizers.ts | 129 +++++++++++++++- .../vscode.proposed.debugVisualization.d.ts | 81 +++++++++- 15 files changed, 640 insertions(+), 92 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index ea668f70f3cb8..1b383b49d75fb 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -30,6 +30,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private readonly _debugAdapterDescriptorFactories: Map; private readonly _extHostKnownSessions: Set; private readonly _visualizerHandles = new Map(); + private readonly _visualizerTreeHandles = new Map(); constructor( extHostContext: IExtHostContext, @@ -112,6 +113,20 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this.sendBreakpointsAndListen(); } + $registerDebugVisualizerTree(treeId: string, canEdit: boolean): void { + this.visualizerService.registerTree(treeId, { + disposeItem: id => this._proxy.$disposeVisualizedTree(id), + getChildren: e => this._proxy.$getVisualizerTreeItemChildren(treeId, e), + getTreeItem: e => this._proxy.$getVisualizerTreeItem(treeId, e), + editItem: canEdit ? ((e, v) => this._proxy.$editVisualizerTreeItem(e, v)) : undefined + }); + } + + $unregisterDebugVisualizerTree(treeId: string): void { + this._visualizerTreeHandles.get(treeId)?.dispose(); + this._visualizerTreeHandles.delete(treeId); + } + $registerDebugVisualizer(extensionId: string, id: string): void { const handle = this.visualizerService.register({ extensionId: new ExtensionIdentifier(extensionId), diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 881bd742f468c..ac17b86e67cd5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1229,6 +1229,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'debugVisualization'); return extHostDebugService.registerDebugVisualizationProvider(extension, id, provider); }, + registerDebugVisualizationTreeProvider(id, provider) { + checkProposedApiEnabled(extension, 'debugVisualization'); + return extHostDebugService.registerDebugVisualizationTree(extension, id, provider); + }, onDidStartDebugSession(listener, thisArg?, disposables?) { return _asExtensionEvent(extHostDebugService.onDidStartDebugSession)(listener, thisArg, disposables); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c17e879971a8e..c3382650e3e9b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -55,7 +55,7 @@ import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/c import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext } from 'vs/workbench/contrib/debug/common/debug'; +import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; @@ -1565,6 +1565,8 @@ export interface MainThreadDebugServiceShape extends IDisposable { $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise; $registerDebugVisualizer(extensionId: string, id: string): void; $unregisterDebugVisualizer(extensionId: string, id: string): void; + $registerDebugVisualizerTree(treeId: string, canEdit: boolean): void; + $unregisterDebugVisualizerTree(treeId: string): void; } export interface IOpenUriOptions { @@ -2360,6 +2362,10 @@ export interface ExtHostDebugServiceShape { $resolveDebugVisualizer(id: number, token: CancellationToken): Promise; $executeDebugVisualizerCommand(id: number): Promise; $disposeDebugVisualizers(ids: number[]): void; + $getVisualizerTreeItem(treeId: string, element: IDebugVisualizationContext): Promise; + $getVisualizerTreeItemChildren(treeId: string, element: number): Promise; + $editVisualizerTreeItem(element: number, value: string): Promise; + $disposeVisualizedTree(element: number): void; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index ced402e29f88c..133ff216767ee 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -7,7 +7,7 @@ import { asPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -18,7 +18,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Breakpoint, DataBreakpoint, DebugAdapterExecutable, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer, DebugAdapterServer, DebugConsoleMode, Disposable, FunctionBreakpoint, Location, Position, setBreakpointId, SourceBreakpoint, ThreadFocus, StackFrameFocus, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType } from 'vs/workbench/contrib/debug/common/debug'; +import { MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugAdapter, IDebugAdapterExecutable, IDebugAdapterNamedPipeServer, IDebugAdapterServer, IDebugVisualization, IDebugVisualizationContext, IDebuggerContribution, DebugVisualizationType, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { convertToDAPaths, convertToVSCPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -28,6 +28,7 @@ import { IExtHostVariableResolverProvider } from './extHostVariableResolverServi import { toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon as ThemeIconUtils } from 'vs/base/common/themables'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import * as Convert from 'vs/workbench/api/common/extHostTypeConverters'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -54,6 +55,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; registerDebugVisualizationProvider(extension: IExtensionDescription, id: string, provider: vscode.DebugVisualizationProvider): vscode.Disposable; + registerDebugVisualizationTree(extension: IExtensionDescription, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable; asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; } @@ -100,11 +102,16 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E private _debugAdapters: Map; private _debugAdaptersTrackers: Map; + + private _debugVisualizationTreeItemIdsCounter = 0; private readonly _debugVisualizationProviders = new Map(); + private readonly _debugVisualizationTrees = new Map(); + private readonly _debugVisualizationTreeItemIds = new WeakMap(); + private readonly _debugVisualizationElements = new Map(); private _signService: ISignService | undefined; - private readonly _visualizers = new Map(); + private readonly _visualizers = new Map(); private _visualizerIdCounter = 0; constructor( @@ -151,6 +158,77 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E }); } + public async $getVisualizerTreeItem(treeId: string, element: IDebugVisualizationContext): Promise { + const context = this.hydrateVisualizationContext(element); + if (!context) { + return undefined; + } + + const item = await this._debugVisualizationTrees.get(treeId)?.getTreeItem?.(context); + return item ? this.convertVisualizerTreeItem(treeId, item) : undefined; + } + + public registerDebugVisualizationTree(manifest: Readonly, id: string, provider: vscode.DebugVisualizationTree): vscode.Disposable { + const extensionId = ExtensionIdentifier.toKey(manifest.identifier); + const key = this.extensionVisKey(extensionId, id); + if (this._debugVisualizationProviders.has(key)) { + throw new Error(`A debug visualization provider with id '${id}' is already registered`); + } + + this._debugVisualizationTrees.set(key, provider); + this._debugServiceProxy.$registerDebugVisualizerTree(key, !!provider.editItem); + return toDisposable(() => { + this._debugServiceProxy.$unregisterDebugVisualizerTree(key); + this._debugVisualizationTrees.delete(id); + }); + } + + public async $getVisualizerTreeItemChildren(treeId: string, element: number): Promise { + const item = this._debugVisualizationElements.get(element)?.item; + if (!item) { + return []; + } + + const children = await this._debugVisualizationTrees.get(treeId)?.getChildren?.(item); + return children?.map(i => this.convertVisualizerTreeItem(treeId, i)) || []; + } + + public async $editVisualizerTreeItem(element: number, value: string): Promise { + const e = this._debugVisualizationElements.get(element); + if (!e) { return undefined; } + + const r = await this._debugVisualizationTrees.get(e.provider)?.editItem?.(e.item, value); + return this.convertVisualizerTreeItem(e.provider, r || e.item); + } + + public $disposeVisualizedTree(element: number): void { + const root = this._debugVisualizationElements.get(element); + if (!root) { + return; + } + + const queue = [root.children]; + for (const children of queue) { + if (children) { + for (const child of children) { + queue.push(this._debugVisualizationElements.get(child)?.children); + this._debugVisualizationElements.delete(child); + } + } + } + } + + private convertVisualizerTreeItem(treeId: string, item: vscode.DebugTreeItem): IDebugVisualizationTreeItem { + let id = this._debugVisualizationTreeItemIds.get(item); + if (!id) { + id = this._debugVisualizationTreeItemIdsCounter++; + this._debugVisualizationTreeItemIds.set(item, id); + this._debugVisualizationElements.set(id, { provider: treeId, item }); + } + + return Convert.DebugTreeItem.from(item, id); + } + public asDebugSourceUri(src: vscode.DebugProtocolSource, session?: vscode.DebugSession): URI { const source = src; @@ -224,7 +302,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E throw new Error(`No debug visualizer found with id '${id}'`); } - let { v, provider } = visualizer; + let { v, provider, extensionId } = visualizer; if (!v.visualization) { v = await provider.resolveDebugVisualization?.(v, token) || v; visualizer.v = v; @@ -234,7 +312,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E throw new Error(`No visualization returned from resolveDebugVisualization in '${provider}'`); } - return this.serializeVisualization(v.visualization)!; + return this.serializeVisualization(extensionId, v.visualization)!; } public async $executeDebugVisualizerCommand(id: number): Promise { @@ -249,21 +327,26 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E } } - public async $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise { + private hydrateVisualizationContext(context: IDebugVisualizationContext): vscode.DebugVisualizationContext | undefined { const session = this._debugSessions.get(context.sessionId); - const key = this.extensionVisKey(extensionId, id); - const provider = this._debugVisualizationProviders.get(key); - if (!session || !provider) { - return []; // probably ended in the meantime - } - - const visualizations = await provider.provideDebugVisualization({ + return session && { session: session.api, variable: context.variable, containerId: context.containerId, frameId: context.frameId, threadId: context.threadId, - }, token); + }; + } + + public async $provideDebugVisualizers(extensionId: string, id: string, context: IDebugVisualizationContext, token: CancellationToken): Promise { + const contextHydrated = this.hydrateVisualizationContext(context); + const key = this.extensionVisKey(extensionId, id); + const provider = this._debugVisualizationProviders.get(key); + if (!contextHydrated || !provider) { + return []; // probably ended in the meantime + } + + const visualizations = await provider.provideDebugVisualization(contextHydrated, token); if (!visualizations) { return []; @@ -271,14 +354,14 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return visualizations.map(v => { const id = ++this._visualizerIdCounter; - this._visualizers.set(id, { v, provider }); + this._visualizers.set(id, { v, provider, extensionId }); const icon = v.iconPath ? this.getIconPathOrClass(v.iconPath) : undefined; return { id, name: v.name, iconClass: icon?.iconClass, iconPath: icon?.iconPath, - visualization: this.serializeVisualization(v.visualization), + visualization: this.serializeVisualization(extensionId, v.visualization), }; }); } @@ -962,7 +1045,7 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return `${extensionId}\0${id}`; } - private serializeVisualization(viz: vscode.DebugVisualization['visualization']): MainThreadDebugVisualization | undefined { + private serializeVisualization(extensionId: string, viz: vscode.DebugVisualization['visualization']): MainThreadDebugVisualization | undefined { if (!viz) { return undefined; } @@ -971,6 +1054,10 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E return { type: DebugVisualizationType.Command }; } + if ('treeId' in viz) { + return { type: DebugVisualizationType.Tree, id: `${extensionId}\0${viz.treeId}` }; + } + throw new Error('Unsupported debug visualization type'); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2c4bba8099f89..91f918b71cff9 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -14,10 +14,12 @@ import { marked } from 'vs/base/common/marked/marked'; import { parse, revive } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; import { cloneAndChange } from 'vs/base/common/objects'; +import { basename } from 'vs/base/common/resources'; import { isEmptyObject, isNumber, isString, isUndefinedOrNull } from 'vs/base/common/types'; import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition } from 'vs/editor/common/core/position'; import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; @@ -31,6 +33,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IMarkerData, IRelatedInformation, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; +import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateApi'; import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; @@ -38,6 +41,7 @@ import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/c import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatCommandFollowup, IInlineChatFollowup, IInlineChatReplyFollowup, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; @@ -50,9 +54,6 @@ import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; -import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; -import { basename } from 'vs/base/common/resources'; -import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; export namespace Command { @@ -2702,3 +2703,16 @@ export namespace TerminalQuickFix { return converter.toInternal(quickFix, disposables); } } + +export namespace DebugTreeItem { + export function from(item: vscode.DebugTreeItem, id: number): IDebugVisualizationTreeItem { + return { + id, + label: item.label, + description: item.description, + canEdit: item.canEdit, + collapsibleState: (item.collapsibleState || DebugTreeItemCollapsibleState.None) as DebugTreeItemCollapsibleState, + contextValue: item.contextValue, + }; + } +} diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 4399464dbc05d..d55c8cefd4d73 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -8,7 +8,7 @@ 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 { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; @@ -145,6 +145,30 @@ export interface IExpressionTemplateData { currentElement: IExpression | undefined; } +export abstract class AbstractExpressionDataSource implements IAsyncDataSource { + constructor(@IDebugService protected debugService: IDebugService) { } + + public abstract hasChildren(element: Input | Element): boolean; + + public getChildren(element: Input | Element): Promise { + const vm = this.debugService.getViewModel(); + return this.doGetChildren(element).then(r => { + let dup: Element[] | undefined; + for (let i = 0; i < r.length; i++) { + const visualized = vm.getVisualizedExpression(r[i] as IExpression); + if (visualized) { + dup ||= r.slice(); + dup[i] = visualized as Element; + } + } + + return dup || r; + }); + } + + protected abstract doGetChildren(element: Input | Element): Promise; +} + export abstract class AbstractExpressionsRenderer implements ITreeRenderer { constructor( diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 369949fb9f798..c0e1b530b3e4d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -9,7 +9,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -32,11 +32,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { asCssVariable, editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; -import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { AbstractExpressionDataSource, renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { VariablesRenderer, openContextMenuForVariableTreeElement } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { VariablesRenderer, VisualizedVariableRenderer, openContextMenuForVariableTreeElement } from 'vs/workbench/contrib/debug/browser/variablesView'; import { IDebugService, IDebugSession, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils'; const $ = dom.$; @@ -127,9 +127,12 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const tip = dom.append(this.complexValueContainer, $('.tip')); tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); - const dataSource = new DebugHoverDataSource(); + const dataSource = new DebugHoverDataSource(this.debugService); const linkeDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer, linkeDetector)], + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [ + this.instantiationService.createInstance(VariablesRenderer, linkeDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkeDetector), + ], dataSource, { accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, @@ -141,6 +144,8 @@ export class DebugHoverWidget implements IContentWidget { } }); + this.toDispose.push(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); + this.valueContainer = $('.value'); this.valueContainer.tabIndex = 0; this.valueContainer.setAttribute('role', 'tooltip'); @@ -403,13 +408,13 @@ class DebugHoverAccessibilityProvider implements IListAccessibilityProvider { +class DebugHoverDataSource extends AbstractExpressionDataSource { - hasChildren(element: IExpression): boolean { + public override hasChildren(element: IExpression): boolean { return element.hasChildren; } - getChildren(element: IExpression): Promise { + public override doGetChildren(element: IExpression): Promise { return element.getChildren(); } } @@ -420,6 +425,9 @@ class DebugHoverDelegate implements IListVirtualDelegate { } getTemplateId(element: IExpression): string { + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } return VariablesRenderer.ID; } } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 4eef489c49f65..ffcda601ff903 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -626,7 +626,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { new ReplRawObjectsRenderer(linkDetector), ], // https://github.com/microsoft/TypeScript/issues/32526 - new ReplDataSource() as IAsyncDataSource, + new ReplDataSource() satisfies IAsyncDataSource, { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index df67a66260a13..ef5e0cfb7d085 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -8,15 +8,15 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { AsyncDataTree, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ITreeContextMenuEvent, ITreeMouseEvent, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Action, IAction } from 'vs/base/common/actions'; import { coalesce } from 'vs/base/common/arrays'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -37,11 +37,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_VARIABLES_FOCUSED, DebugVisualizationType, IDataBreakpointInfoResponse, IDebugService, IExpression, IScope, IStackFrame, IViewModel, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; -import { ErrorScope, Expression, Scope, StackFrame, Variable, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; +import { ErrorScope, Expression, Scope, StackFrame, Variable, VisualizedExpression, getUriForDebugMemory } from 'vs/workbench/contrib/debug/common/debugModel'; import { DebugVisualizer, IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -118,10 +118,15 @@ export class VariablesView extends ViewPane { this.element.classList.add('debug-pane'); container.classList.add('debug-variables'); const treeContainer = renderViewTree(container); - const linkeDetector = this.instantiationService.createInstance(LinkDetector); + const linkDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), - [this.instantiationService.createInstance(VariablesRenderer, linkeDetector), new ScopesRenderer(), new ScopeErrorRenderer()], - new VariablesDataSource(), { + [ + this.instantiationService.createInstance(VariablesRenderer, linkDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), + new ScopesRenderer(), + new ScopeErrorRenderer(), + ], + new VariablesDataSource(this.debugService), { accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, @@ -130,6 +135,7 @@ export class VariablesView extends ViewPane { } }); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); this.tree.setInput(this.debugService.getViewModel().focusedStackFrame ?? null); CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); @@ -299,9 +305,9 @@ function isStackFrame(obj: any): obj is IStackFrame { return obj instanceof StackFrame; } -class VariablesDataSource implements IAsyncDataSource { +class VariablesDataSource extends AbstractExpressionDataSource { - hasChildren(element: IStackFrame | null | IExpression | IScope): boolean { + public override hasChildren(element: IStackFrame | null | IExpression | IScope): boolean { if (!element) { return false; } @@ -312,7 +318,7 @@ class VariablesDataSource implements IAsyncDataSource { + protected override doGetChildren(element: IStackFrame | IExpression | IScope): Promise<(IExpression | IScope)[]> { if (isStackFrame(element)) { return element.getScopes(); } @@ -341,6 +347,10 @@ class VariablesDelegate implements IListVirtualDelegate { return ScopesRenderer.ID; } + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } + return VariablesRenderer.ID; } } @@ -396,6 +406,102 @@ class ScopeErrorRenderer implements ITreeRenderer): IDisposable { + return model.onDidChangeVisualization(({ original }) => { + if (!tree.hasElement(original)) { + return; + } + + const parent: IExpression = tree.getParentElement(original); + tree.updateChildren(parent, false, false); + }); + + } + + constructor( + private readonly linkDetector: LinkDetector, + @IDebugService debugService: IDebugService, + @IContextViewService contextViewService: IContextViewService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(debugService, contextViewService); + } + + public override get templateId(): string { + return VisualizedVariableRenderer.ID; + } + + public override renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + super.renderExpressionElement(node.element, node, data); + } + + protected override renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { + const viz = expression as VisualizedExpression; + + let text = viz.name; + if (viz.value && typeof viz.name === 'string') { + text += ':'; + } + data.label.set(text, highlights, viz.name); + renderExpressionValue(viz, data.value, { + showChanged: false, + maxValueLength: 1024, + showHover: true, + colorize: true, + linkDetector: this.linkDetector + }); + } + + protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { + const variable = expression; + return { + initialValue: expression.value, + ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), + validationOptions: { + validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null + }, + onFinish: (value: string, success: boolean) => { + variable.errorMessage = undefined; + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + if (success && variable.value !== value && focusedStackFrame) { + variable.setVariable(value, focusedStackFrame) + // Need to force watch expressions and variables to update since a variable change can have an effect on both + .then(() => { + // Do not refresh scopes due to a node limitation #15520 + forgetScopes = false; + this.debugService.getViewModel().updateViews(); + }); + } + } + }; + } + + protected override renderActionBar(actionBar: ActionBar, expression: IExpression, _data: IExpressionTemplateData) { + const viz = expression as VisualizedExpression; + const contextKeyService = viz.original ? getContextForVariableMenuBase(this.contextKeyService, viz.original) : this.contextKeyService; + const menu = this.menuService.createMenu(MenuId.DebugVariablesContext, contextKeyService); + + const primary: IAction[] = []; + const context = viz.original ? getVariablesContext(viz.original) : undefined; + createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); + + if (viz.original) { + primary.push(new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.close), undefined, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined))); + } + actionBar.clear(); + actionBar.context = context; + actionBar.push(primary, { icon: true, label: false }); + } +} + export class VariablesRenderer extends AbstractExpressionsRenderer { static readonly ID = 'variable'; @@ -466,13 +572,14 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { this.visualization.getApplicableFor(expression, cts.token).then(result => { data.elementDisposable.add(result); - const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, cts.token))); + const originalExpression = (expression instanceof VisualizedExpression && expression.original) || expression; + const actions = result.object.map(v => new Action('debugViz', v.name, v.iconClass || 'debug-viz-icon', undefined, this.useVisualizer(v, originalExpression, cts.token))); if (actions.length === 0) { // no-op } else if (actions.length === 1) { actionBar.push(actions[0], { icon: true, label: false }); } else { - actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, expression, data)), { icon: true, label: false }); + actionBar.push(new Action('debugViz', localize('useVisualizer', 'Visualize Variable...'), ThemeIcon.asClassName(Codicon.eye), undefined, () => this.pickVisualizer(actions, originalExpression, data)), { icon: true, label: false }); } }); } @@ -484,7 +591,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { }); } - private useVisualizer(viz: DebugVisualizer, token: CancellationToken) { + private useVisualizer(viz: DebugVisualizer, expression: IExpression, token: CancellationToken) { return async () => { const resolved = await viz.resolve(token); if (token.isCancellationRequested) { @@ -494,7 +601,10 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (resolved.type === DebugVisualizationType.Command) { viz.execute(); } else { - throw new Error('not implemented, yet'); + const replacement = await this.visualization.setVisualizedNodeFor(resolved.id, expression); + if (replacement) { + this.debugService.getViewModel().setVisualizedExpression(expression, replacement); + } } }; } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 991c7712217f1..9bdf8801c54b6 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -3,38 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; +import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, WATCH_VIEW_ID, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_CAN_VIEW_MEMORY } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Codicon } from 'vs/base/common/codicons'; +import { FuzzyScore } from 'vs/base/common/filters'; +import { localize } from 'vs/nls'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAction } from 'vs/base/common/actions'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectType } from 'vs/base/browser/ui/list/list'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; -import { FuzzyScore } from 'vs/base/common/filters'; -import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; -import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { watchExpressionsRemoveAll, watchExpressionsAdd } from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { registerAction2, MenuId, Action2, IMenuService, IMenu } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; -import { Codicon } from 'vs/base/common/codicons'; -import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { AbstractExpressionDataSource, AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions, renderExpressionValue, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { watchExpressionsAdd, watchExpressionsRemoveAll } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { VariablesRenderer, VisualizedVariableRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { CONTEXT_CAN_VIEW_MEMORY, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_WATCH_EXPRESSIONS_EXIST, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_WATCH_ITEM_TYPE, IDebugService, IExpression, WATCH_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreViewUpdates = false; @@ -86,9 +86,14 @@ export class WatchExpressionsView extends ViewPane { const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - const linkeDetector = this.instantiationService.createInstance(LinkDetector); - this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer, linkeDetector)], - new WatchExpressionsDataSource(), { + const linkDetector = this.instantiationService.createInstance(LinkDetector); + this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'WatchExpressions', treeContainer, new WatchExpressionsDelegate(), + [ + expressionsRenderer, + this.instantiationService.createInstance(VariablesRenderer, linkDetector), + this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), + ], + new WatchExpressionsDataSource(this.debugService), { accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { @@ -109,6 +114,7 @@ export class WatchExpressionsView extends ViewPane { this.tree.setInput(this.debugService); CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); + this._register(VisualizedVariableRenderer.rendererOnVisualizationRange(this.debugService.getViewModel(), this.tree)); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this._register(this.debugService.getModel().onDidChangeWatchExpressions(async we => { @@ -234,6 +240,10 @@ class WatchExpressionsDelegate implements IListVirtualDelegate { return WatchExpressionsRenderer.ID; } + if (element instanceof VisualizedExpression) { + return VisualizedVariableRenderer.ID; + } + // Variable return VariablesRenderer.ID; } @@ -243,13 +253,13 @@ function isDebugService(element: any): element is IDebugService { return typeof element.getConfigurationManager === 'function'; } -class WatchExpressionsDataSource implements IAsyncDataSource { +class WatchExpressionsDataSource extends AbstractExpressionDataSource { - hasChildren(element: IExpression | IDebugService): boolean { + public override hasChildren(element: IExpression | IDebugService): boolean { return isDebugService(element) || element.hasChildren; } - getChildren(element: IDebugService | IExpression): Promise> { + public override doGetChildren(element: IDebugService | IExpression): Promise> { if (isDebugService(element)) { const debugService = element as IDebugService; const watchExpressions = debugService.getModel().getWatchExpressions(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d16c7c8e0cddd..9dcab53e24082 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -634,6 +634,8 @@ export interface IViewModel extends ITreeElement { */ readonly focusedStackFrame: IStackFrame | undefined; + setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void; + getVisualizedExpression(expression: IExpression): IExpression | undefined; getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined; setSelectedExpression(expression: IExpression | undefined, settingWatch: boolean): void; updateViews(): void; @@ -645,6 +647,11 @@ export interface IViewModel extends ITreeElement { onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; session: IDebugSession | undefined }>; onDidSelectExpression: Event<{ expression: IExpression; settingWatch: boolean } | undefined>; onDidEvaluateLazyExpression: Event; + /** + * Fired when `setVisualizedExpression`, to migrate elements currently + * rendered as `original` to the `replacement`. + */ + onDidChangeVisualization: Event<{ original: IExpression; replacement: IExpression }>; onWillUpdateViews: Event; evaluateLazyExpression(expression: IExpressionContainer): void; @@ -1269,9 +1276,31 @@ export const enum DebugVisualizationType { Tree, } -export type MainThreadDebugVisualization = { - type: DebugVisualizationType.Command; -}; // todo: tree +export type MainThreadDebugVisualization = + | { type: DebugVisualizationType.Command } + | { type: DebugVisualizationType.Tree; id: string }; + + +export const enum DebugTreeItemCollapsibleState { + None = 0, + Collapsed = 1, + Expanded = 2 +} + +export interface IDebugVisualizationTreeItem { + id: number; + label: string; + description?: string; + collapsibleState: DebugTreeItemCollapsibleState; + contextValue?: string; + canEdit?: boolean; +} + +export namespace IDebugVisualizationTreeItem { + export type Serialized = IDebugVisualizationTreeItem; + export const deserialize = (v: Serialized): IDebugVisualizationTreeItem => v; + export const serialize = (item: IDebugVisualizationTreeItem): Serialized => item; +} export interface IDebugVisualization { id: number; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 5a33aecfbebbf..373ebfa9189dd 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -22,9 +22,10 @@ import * as nls from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { DEBUG_MEMORY_SCHEME, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; +import { DEBUG_MEMORY_SCHEME, DebugTreeItemCollapsibleState, IBaseBreakpoint, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IBreakpointsChangeEvent, IDataBreakpoint, IDebugModel, IDebugSession, IDebugVisualizationTreeItem, IEnablement, IExceptionBreakpoint, IExceptionInfo, IExpression, IExpressionContainer, IFunctionBreakpoint, IInstructionBreakpoint, IMemoryInvalidationEvent, IMemoryRegion, IRawModelUpdate, IRawStoppedDetails, IScope, IStackFrame, IThread, ITreeElement, MemoryRange, MemoryRangeType, State } from 'vs/workbench/contrib/debug/common/debug'; import { Source, UNKNOWN_SOURCE_LABEL, getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; +import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -244,6 +245,35 @@ function handleSetResponse(expression: ExpressionContainer, response: DebugProto } } +export class VisualizedExpression implements IExpression { + public readonly name: string; + public readonly hasChildren: boolean; + public readonly value: string; + private readonly id = generateUuid(); + + evaluateLazy(): Promise { + return Promise.resolve(); + } + getChildren(): Promise { + return this.visualizer.getVisualizedChildren(this.treeId, this.treeItem.id); + } + + getId(): string { + return this.id; + } + + constructor( + private readonly visualizer: IDebugVisualizerService, + private readonly treeId: string, + public readonly treeItem: IDebugVisualizationTreeItem, + public readonly original?: Variable, + ) { + this.name = treeItem.label; + this.hasChildren = treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; + this.value = treeItem.description || ''; + } +} + export class Expression extends ExpressionContainer implements IExpression { static readonly DEFAULT_VALUE = nls.localize('notAvailable', "not available"); diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 01bf3add5f8ab..9c4ee1217ab07 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -22,6 +22,8 @@ export class ViewModel implements IViewModel { private readonly _onDidSelectExpression = new Emitter<{ expression: IExpression; settingWatch: boolean } | undefined>(); private readonly _onDidEvaluateLazyExpression = new Emitter(); private readonly _onWillUpdateViews = new Emitter(); + private readonly _onDidChangeVisualization = new Emitter<{ original: IExpression; replacement: IExpression }>(); + private readonly visualized = new WeakMap(); private expressionSelectedContextKey!: IContextKey; private loadedScriptsSupportedContextKey!: IContextKey; private stepBackSupportedContextKey!: IContextKey; @@ -125,6 +127,10 @@ export class ViewModel implements IViewModel { return this._onDidFocusStackFrame.event; } + get onDidChangeVisualization() { + return this._onDidChangeVisualization.event; + } + getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined { return this.selectedExpression; } @@ -159,6 +165,21 @@ export class ViewModel implements IViewModel { this.multiSessionDebug.set(isMultiSessionView); } + setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void { + const current = this.visualized.get(original) || original; + + if (visualized) { + this.visualized.set(original, visualized); + } else { + this.visualized.delete(original); + } + this._onDidChangeVisualization.fire({ original: current, replacement: visualized || original }); + } + + getVisualizedExpression(expression: IExpression): IExpression | undefined { + return this.visualized.get(expression); + } + async evaluateLazyExpression(expression: IExpressionContainer): Promise { await expression.evaluateLazy(); this._onDidEvaluateLazyExpression.fire(expression); diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index 9a5cf6885c29e..2b171d5f673e5 100644 --- a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -10,9 +10,9 @@ import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/pla import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_VARIABLE_NAME, CONTEXT_VARIABLE_TYPE, CONTEXT_VARIABLE_VALUE, MainThreadDebugVisualization, IDebugVisualization, IDebugVisualizationContext, IExpression, IExpressionContainer, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { getContextForVariable } from 'vs/workbench/contrib/debug/common/debugContext'; -import { Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Scope, Variable, VisualizedExpression } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -27,6 +27,13 @@ interface VisualizerHandle { disposeDebugVisualizers(ids: number[]): void; } +interface VisualizerTreeHandle { + getTreeItem(element: IDebugVisualizationContext): Promise; + getChildren(element: number): Promise; + disposeItem(element: number): void; + editItem?(item: number, value: string): Promise; +} + export class DebugVisualizer { public get name() { return this.viz.name; @@ -63,13 +70,38 @@ export interface IDebugVisualizerService { * Registers a new visualizer (called from the main thread debug service) */ register(handle: VisualizerHandle): IDisposable; + + /** + * Registers a new visualizer tree. + */ + registerTree(treeId: string, handle: VisualizerTreeHandle): IDisposable; + + /** + * Sets that a certa tree should be used for the visualized node + */ + setVisualizedNodeFor(treeId: string, expr: IExpression): Promise; + + /** + * Gets a visualized node for the given expression if the user has preferred + * to visualize it that way. + */ + getVisualizedNodeFor(expr: IExpression): Promise; + + /** + * Gets children for a visualized tree node. + */ + getVisualizedChildren(treeId: string, treeElementId: number): Promise; } +const emptyRef: IReference = { object: [], dispose: () => { } }; + export class DebugVisualizerService implements IDebugVisualizerService { declare public readonly _serviceBrand: undefined; private readonly handles = new Map(); + private readonly trees = new Map(); private readonly didActivate = new Map>(); + private readonly preferredTrees = new Map(); private registrations: { expr: ContextKeyExpression; id: string; extensionId: ExtensionIdentifier }[] = []; constructor( @@ -85,10 +117,13 @@ export class DebugVisualizerService implements IDebugVisualizerService { } /** @inheritdoc */ - public async getApplicableFor(variable: Variable, token: CancellationToken): Promise> { + public async getApplicableFor(variable: IExpression, token: CancellationToken): Promise> { + if (!(variable instanceof Variable)) { + return emptyRef; + } const threadId = variable.getThreadId(); if (threadId === undefined) { // an expression, not a variable - return { object: [], dispose: () => { } }; + return emptyRef; } const context: IDebugVisualizationContext = { @@ -163,6 +198,92 @@ export class DebugVisualizerService implements IDebugVisualizerService { return toDisposable(() => this.handles.delete(key)); } + /** @inheritdoc */ + public registerTree(treeId: string, handle: VisualizerTreeHandle): IDisposable { + this.trees.set(treeId, handle); + return toDisposable(() => this.trees.delete(treeId)); + } + + /** @inheritdoc */ + public async setVisualizedNodeFor(treeId: string, expr: IExpression): Promise { + return this.getOrSetNodeFor(expr, treeId); + } + + /** @inheritdoc */ + public async getVisualizedNodeFor(expr: IExpression): Promise { + return this.getOrSetNodeFor(expr); + } + + /** @inheritdoc */ + public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { + const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; + return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); + } + + private async getOrSetNodeFor(expr: IExpression, useTreeKey?: string): Promise { + if (!(expr instanceof Variable)) { + return; + } + + const threadId = expr.getThreadId(); + if (threadId === undefined) { + return; + } + + const exprPreferKey = useTreeKey || this.getPreferredTreeKey(expr); + const tree = exprPreferKey && this.trees.get(exprPreferKey); + if (!tree) { + return; + } + + const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); + if (!treeItem) { + return; + } + + if (useTreeKey) { + this.preferredTrees.set(exprPreferKey, exprPreferKey); + } + + return new VisualizedExpression(this, exprPreferKey, treeItem, expr); + } + + private getPreferredTreeKey(expr: Variable) { + return JSON.stringify([ + expr.name, + expr.value, + expr.type, + !!expr.memoryReference, + ].join('\0')); + } + + private getVariableContext(threadId: number, variable: Variable) { + const context: IDebugVisualizationContext = { + sessionId: variable.getSession()?.getId() || '', + containerId: variable.parent.getId(), + threadId, + variable: { + name: variable.name, + value: variable.value, + type: variable.type, + evaluateName: variable.evaluateName, + variablesReference: variable.reference || 0, + indexedVariables: variable.indexedVariables, + memoryReference: variable.memoryReference, + namedVariables: variable.namedVariables, + presentationHint: variable.presentationHint, + } + }; + + for (let p: IExpressionContainer = variable; p instanceof Variable; p = p.parent) { + if (p.parent instanceof Scope) { + context.frameId = p.parent.stackFrame.frameId; + } + } + + return context; + } + private processExtensionRegistration(ext: Readonly) { const viz = ext.contributes?.debugVisualizers; if (!(viz instanceof Array)) { diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts index d23bbfe2edde1..0fc115f7619a7 100644 --- a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -16,6 +16,79 @@ declare module 'vscode' { id: string, provider: DebugVisualizationProvider ): Disposable; + + /** + * Registers a tree that can be referenced by {@link DebugVisualization.visualization}. + * @param id + * @param provider + */ + export function registerDebugVisualizationTreeProvider( + id: string, + provider: DebugVisualizationTree + ): Disposable; + } + + /** + * An item from the {@link DebugVisualizationTree} + */ + export interface DebugTreeItem { + /** + * A human-readable string describing this item. + */ + label: string; + + /** + * A human-readable string which is rendered less prominent. + */ + description?: string; + + /** + * {@link TreeItemCollapsibleState} of the tree item. + */ + collapsibleState?: TreeItemCollapsibleState; + + /** + * Context value of the tree item. This can be used to contribute item specific actions in the tree. + * For example, a tree item is given a context value as `folder`. When contributing actions to `view/item/context` + * using `menus` extension point, you can specify context value for key `viewItem` in `when` expression like `viewItem == folder`. + * ```json + * "contributes": { + * "menus": { + * "view/item/context": [ + * { + * "command": "extension.deleteFolder", + * "when": "viewItem == folder" + * } + * ] + * } + * } + * ``` + * This will show action `extension.deleteFolder` only for items with `contextValue` is `folder`. + */ + contextValue?: string; + + /** + * Whether this item can be edited by the user. + */ + canEdit?: boolean; + } + + /** + * Provides a tree that can be referenced in debug visualizations. + */ + export interface DebugVisualizationTree { + /** + * Gets the tree item for an element or the base context item. + */ + getTreeItem(context: DebugVisualizationContext): ProviderResult; + /** + * Gets children for the tree item or the best context item. + */ + getChildren(element: T): ProviderResult; + /** + * Handles the user editing an item. + */ + editItem?(item: T, value: string): ProviderResult; } export class DebugVisualization { @@ -32,13 +105,9 @@ declare module 'vscode' { /** * Visualization to use for the variable. This may be either: * - A command to run when the visualization is selected for a variable. - * - A {@link TreeDataProvider} which is used to display the data in-line - * where the variable is shown. If a single root item is returned from - * the data provider, it will replace the variable in its tree. - * Otherwise, the items will be shown as children of the variable. + * - A reference to a previously-registered {@link DebugVisualizationTree} */ - // @API don't return TreeDataProvider but a reference to it, like its ids - visualization?: Command | TreeDataProvider; + visualization?: Command | { treeId: string }; /** * Creates a new debug visualization object. From 582b0bea5a9e5f9090573fa3a2b4dd8587030100 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:29:51 -0600 Subject: [PATCH 0243/1863] 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 8fcb3c711720c..9053a746f8940 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 c8f3ec6995860..05383e3e7f889 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 4a5dbb55994b5..88f17fe5c586e 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 0244/1863] 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 9053a746f8940..ab84f0258b1d9 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 05383e3e7f889..c4755a08c47b2 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 88f17fe5c586e..476c808dce17e 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 e8be60a478ca6c351401e00fe068ad18ca210f29 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 09:50:40 -0800 Subject: [PATCH 0245/1863] debug: fix launch using empty directory in external terminal (#205123) Fixes #204039 --- .../platform/externalTerminal/node/externalTerminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 71dbac899b377..9fd3892972607 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -107,7 +107,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl // prefer to use the window terminal to spawn if it's available instead // of start, since that allows ctrl+c handling (#81322) spawnExec = wt; - cmdArgs = ['-d', dir, exec, '/c', command]; + cmdArgs = ['-d', dir || '.', exec, '/c', command]; // default dir fixes #204039 } else { spawnExec = WindowsExternalTerminalService.CMD; cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; From c9c98f010a12da31afa70b7b60b280260f911bac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 11:56:08 -0600 Subject: [PATCH 0246/1863] 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 161b85154786a..b9544ce5febaf 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 c4755a08c47b2..5465c3abed3d3 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 476c808dce17e..4c84b3656bc08 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 0247/1863] 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 b9544ce5febaf..57e3cee5de08b 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 0248/1863] 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 57e3cee5de08b..bb1a012026a7b 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 5465c3abed3d3..a9babf3a5de69 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 0249/1863] 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 a9babf3a5de69..228101656609a 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 0250/1863] 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 228101656609a..85d7ba442960b 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 0251/1863] 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 85d7ba442960b..432d5fd0cbf0a 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 4c84b3656bc08..985fc071cb210 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 6289704f0d96a9860d5303a545e13ddd847cc164 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 10:22:10 -0800 Subject: [PATCH 0252/1863] debug: fix toolbar overlaps traffic lights (#205127) Fixes #204265 --- src/vs/workbench/contrib/debug/browser/debugToolBar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 888e0b120482a..c46cd2d9a6f4f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -254,7 +254,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const positionPercentage = isMainWindow ? Number(this.storageService.get(DEBUG_TOOLBAR_POSITION_KEY, StorageScope.PROFILE)) : this.auxWindowCoordinates.get(currentWindow)?.x; - x = positionPercentage !== undefined + x = positionPercentage !== undefined && !isNaN(positionPercentage) ? positionPercentage * currentWindow.innerWidth : (0.5 * currentWindow.innerWidth - 0.5 * widgetWidth); } From 1617ebe843cb56d7ff8fdd2c17c3fbaa2de9f7ec Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:24:10 -0600 Subject: [PATCH 0253/1863] 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 78fd7df671213..7f777f94f625d 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 bb1a012026a7b..dcf2aec061745 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 432d5fd0cbf0a..b3b9c002aad0f 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 fe89a8a7209a38d806140e954e4b79325ef6853a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 13 Feb 2024 19:31:05 +0100 Subject: [PATCH 0254/1863] add some api todos (#205128) --- src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index 431f3c2d708da..fdd1cb7f5ef98 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -14,6 +14,7 @@ declare module 'vscode' { // TODO@API define this type! result: Thenable; + // TODO@API doc what to expect here stream: AsyncIterable; } @@ -52,6 +53,7 @@ declare module 'vscode' { /** * An event that is fired when the access the language model has has been revoked or re-granted. */ + // TODO@API NAME? readonly onDidChangeAccess: Event; /** @@ -94,7 +96,7 @@ declare module 'vscode' { readonly removed: readonly string[]; } - //@API DEFINE the namespace for this: env, lm, ai? + //@API DEFINE the namespace for this: lm (languageModels), copilot, ai, env,? export namespace chat { /** From d5527a86b26e1586b487dfaadc43001c32b4bd64 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 12:56:50 -0600 Subject: [PATCH 0255/1863] 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 85033c6416685..635233002c486 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 ab84f0258b1d9..1aaf1516c5e0d 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 dcf2aec061745..600322540280d 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 b3b9c002aad0f..9312e56e19b4f 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 985fc071cb210..761708aec3576 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 0256/1863] 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 635233002c486..85033c6416685 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 0257/1863] 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 431a0e9c7158f..882baf8c26b65 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 600322540280d..73ebf9631a917 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 9312e56e19b4f..0a943b362fd70 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 761708aec3576..0ccea8a61cd78 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 0258/1863] 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 74ec952ba1c44..e41ae049b6e4f 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 da8f67171617edccb55461df9e2c6bb6e8aca4d4 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:22:04 -0600 Subject: [PATCH 0259/1863] try to ignore history on editor open for quicksearch (#204880) * try to ignore history on editor open * correct history service change * fix history race condition * edit shouldIgnoreActiveEditorChange description --- .../quickTextSearch/textSearchQuickAccess.ts | 21 +++++++++++++---- .../history/browser/historyService.ts | 23 ++++++++++++++++--- .../services/history/common/history.ts | 1 + .../test/browser/workbenchTestServices.ts | 1 + .../test/common/workbenchTestServices.ts | 1 + 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index b1ba725a1b3a5..b5ad884d5d030 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -30,6 +30,8 @@ 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 = '%'; @@ -48,6 +50,8 @@ interface ITextSearchQuickAccessItem extends IPickerQuickAccessItem { match?: Match; } export class TextSearchQuickAccess extends PickerQuickAccessProvider { + + private editorSequencer: Sequencer; private queryBuilder: QueryBuilder; private searchModel: SearchModel; private currentAsyncSearch: Promise = Promise.resolve({ @@ -80,13 +84,15 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { + // disable and re-enable history service so that we can ignore this history entry + this._historyService.shouldIgnoreActiveEditorChange = true; + await this._editorService.openEditor({ + resource: itemMatch.parent().resource, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } + }); + this._historyService.shouldIgnoreActiveEditorChange = false; }); } })); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 1c73d33421020..3c580614b86e4 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -47,6 +47,10 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); + // Can be set to temporarily ignore messages from the editor service that indicate a new active editor. + // Used for ignoring some editors for history. + public shouldIgnoreActiveEditorChange: boolean = false; + constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -77,10 +81,18 @@ export class HistoryService extends Disposable implements IHistoryService { this.registerMouseNavigationListener(); // Editor changes - this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); + this._register(this.editorService.onDidActiveEditorChange((e) => { + if (!this.shouldIgnoreActiveEditorChange) { + 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(() => this.handleEditorEventInRecentEditorsStack())); + this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => { + if (!this.shouldIgnoreActiveEditorChange) { + this.handleEditorEventInRecentEditorsStack(); + } + })); // Editor group changes this._register(this.editorGroupService.onDidRemoveGroup(e => this.onDidRemoveGroup(e))); @@ -177,7 +189,12 @@ export class HistoryService extends Disposable implements IHistoryService { // Listen to selection changes if the editor pane // is having a selection concept. if (isEditorPaneWithSelection(activeEditorPane)) { - this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e))); + this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { + if (!this.shouldIgnoreActiveEditorChange) { + this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); + } + } + )); } // Context keys diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index b5abcd2dad359..86d21f49dad14 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -60,6 +60,7 @@ export const enum GoScope { export interface IHistoryService { readonly _serviceBrand: undefined; + shouldIgnoreActiveEditorChange: boolean; /** * Navigate forwards in editor navigation history. diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index f6febaf3d2f41..10461c8e293be 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -548,6 +548,7 @@ export class TestMenuService implements IMenuService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; + declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 1a938d7dbd676..d1483ff15beb7 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -149,6 +149,7 @@ export class TestStorageService extends InMemoryStorageService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; + declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } From 702535987ca16b9ba1ed8d86443a1d24a5400538 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 13:29:56 -0600 Subject: [PATCH 0260/1863] 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 7f777f94f625d..8ea1469a4b1d2 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 73ebf9631a917..37cf45c77fcae 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 0a943b362fd70..00b117d6661b0 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 0ccea8a61cd78..41332e575fbe5 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 c2316194e2306d239e8a6e2ea3049ed0087ed191 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 16:33:21 -0300 Subject: [PATCH 0261/1863] Get rid of history2, variables2 (#205124) * Get rid of history2 * Remove prompt2 and variables2 * Clean up variables2/prompt2 * Fix tests * Fix ranges * Fix test --- .../src/singlefolder-tests/chat.test.ts | 4 +- .../api/common/extHostChatAgents2.ts | 4 +- .../api/common/extHostTypeConverters.ts | 5 +- .../contrib/chat/browser/chatVariables.ts | 36 +++++-------- .../contrib/chat/common/chatAgents.ts | 6 +-- .../contrib/chat/common/chatModel.ts | 16 ++---- .../contrib/chat/common/chatParserTypes.ts | 7 ++- .../contrib/chat/common/chatServiceImpl.ts | 51 ++++++++----------- .../chat/test/browser/chatVariables.test.ts | 34 ++++++------- .../__snapshots__/Chat_can_deserialize.0.snap | 5 +- .../__snapshots__/Chat_can_serialize.1.snap | 5 +- .../chat/test/common/chatModel.test.ts | 2 +- .../chat/test/common/chatService.test.ts | 4 +- .../chat/test/common/mockChatVariables.ts | 3 +- .../vscode.proposed.chatAgents2.d.ts | 31 ++--------- 15 files changed, 74 insertions(+), 139 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 871fb8572ff1a..6c0406aadbbee 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -64,8 +64,8 @@ suite('chat', () => { const deferred = getDeferredForRequest(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); const request = await deferred.p; - assert.strictEqual(request.prompt, 'hi [#myVar](values:myVar)'); - assert.strictEqual(request.variables['myVar'][0].value, 'myValue'); + assert.strictEqual(request.prompt, 'hi #myVar'); + assert.strictEqual(request.variables[0].values[0].value, 'myValue'); }); test('result metadata is returned to the followup provider', async () => { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index e32d418c24a2f..bd233330fb22e 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -200,7 +200,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const convertedHistory = await this.prepareHistoryTurns(request, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), - { history: convertedHistory, history2: convertedHistory }, + { history: convertedHistory }, stream.apiObject, token ); @@ -239,7 +239,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables2.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 91f918b71cff9..4c403c0439837 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2631,11 +2631,8 @@ export namespace ChatAgentRequest { export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { return { prompt: request.message, - prompt2: request.variables2.message, - variables: ChatVariable.objectTo(request.variables), command: request.command, - agentId: request.agentId, - variables2: request.variables2.variables.map(ChatAgentResolvedVariable.to) + variables: request.variables.variables.map(ChatAgentResolvedVariable.to) }; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 3bb6fa6b7cabf..95baa1d92da26 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IOffsetRange } from 'vs/editor/common/core/offsetRange'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -29,51 +31,37 @@ export class ChatVariablesService implements IChatVariablesService { } async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { - const resolvedVariables: Record = {}; + let resolvedVariables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[] = []; const jobs: Promise[] = []; - const parsedPrompt: string[] = []; prompt.parts .forEach((part, i) => { if (part instanceof ChatRequestVariablePart) { const data = this._resolver.get(part.variableName.toLowerCase()); if (data) { - jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(value => { - if (value) { - resolvedVariables[part.variableName] = value; - parsedPrompt[i] = `[${part.text}](values:${part.variableName})`; - } else { - parsedPrompt[i] = part.promptText; + jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(values => { + if (values?.length) { + resolvedVariables[i] = { name: part.variableName, range: part.range, values }; } }).catch(onUnexpectedExternalError)); } } else if (part instanceof ChatRequestDynamicVariablePart) { - const referenceName = this.getUniqueReferenceName(part.referenceText, resolvedVariables); - resolvedVariables[referenceName] = part.data; - const safeText = part.text.replace(/[\[\]]/g, '_'); - const safeTarget = referenceName.replace(/[\(\)]/g, '_'); - parsedPrompt[i] = `[${safeText}](values:${safeTarget})`; - } else { - parsedPrompt[i] = part.promptText; + resolvedVariables[i] = { name: part.referenceText, range: part.range, values: part.data }; } }); await Promise.allSettled(jobs); + resolvedVariables = coalesce(resolvedVariables); + + // "reverse", high index first so that replacement is simple + resolvedVariables.sort((a, b) => b.range.start - a.range.start); + return { variables: resolvedVariables, - message: parsedPrompt.join('').trim() }; } - private getUniqueReferenceName(name: string, vars: Record): string { - let i = 1; - while (vars[name]) { - name = `${name}_${i++}`; - } - return name; - } - hasVariable(name: string): boolean { return this._resolver.has(name.toLowerCase()); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index adbaa77da2a9e..dbcabdd422d1e 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,9 +13,8 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent, IChatRequestVariableData2 } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -89,8 +88,7 @@ export interface IChatAgentRequest { agentId: string; command?: string; message: string; - variables: Record; - variables2: IChatRequestVariableData2; + variables: IChatRequestVariableData; } export interface IChatAgentResult { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 56f75f06edac6..64b50701e8432 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -20,16 +20,6 @@ import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatRequestVariableData { - /** - * The user's message with variable references for extension API. - */ - message: string; - - variables: Record; -} - -export interface IChatRequestVariableData2 { - message: string; variables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[]; } @@ -574,8 +564,10 @@ export class ChatModel extends Disposable implements IChatModel { ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); - // Only old messages don't have variableData - const variableData: IChatRequestVariableData = raw.variableData ?? { message: parsedRequest.text, variables: {} }; + // Old messages don't have variableData, or have it in the wrong (non-array) shape + const variableData: IChatRequestVariableData = raw.variableData && Array.isArray(raw.variableData.variables) + ? raw.variableData : + { variables: [] }; const request = new ChatRequestModel(this, parsedRequest, variableData); if (raw.response || raw.result || (raw as any).responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index fd9630ab51542..4f7654fd56a94 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -26,8 +26,11 @@ export interface IParsedChatRequestPart { readonly promptText: string; } -export function getPromptText(request: ReadonlyArray): string { - return request.map(r => r.promptText).join(''); +export function getPromptText(request: IParsedChatRequest): { message: string; diff: number } { + const message = request.parts.map(r => r.promptText).join('').trimStart(); + const diff = request.text.length - message.length; + + return { message, diff }; } export class ChatRequestTextPart implements IParsedChatRequestPart { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 5ec45665e6d52..7197f521f065e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -22,8 +22,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IChatAgentHistoryEntry, 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, IChatRequestVariableData2, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestVariablePart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -537,38 +537,38 @@ export class ChatService extends Disposable implements IChatService { continue; } + const promptTextResult = getPromptText(request.message); const historyRequest: IChatAgentRequest = { sessionId, requestId: request.id, agentId: request.response.agent?.id ?? '', - message: request.variableData.message, - variables: request.variableData.variables, + message: promptTextResult.message, command: request.response.slashCommand?.name, - variables2: asVariablesData2(request.message, request.variableData) + variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack }; history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); } - const initVariableData: IChatRequestVariableData = { message: getPromptText(parsedRequest.parts), variables: {} }; + const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); request.variableData = variableData; + const promptTextResult = getPromptText(request.message); const requestProps: IChatAgentRequest = { sessionId, requestId: request.id, agentId: agent.id, - message: variableData.message, - variables: variableData.variables, + message: promptTextResult.message, command: agentSlashCommandPart?.command.name, - variables2: asVariablesData2(parsedRequest, variableData) + variables: updateRanges(variableData, promptTextResult.diff) // TODO bit of a hack }; const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, followupsCancelToken); } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { - request = model.addRequest(parsedRequest, { message: parsedRequest.text, variables: {} }); + request = model.addRequest(parsedRequest, { variables: [] }); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -670,7 +670,7 @@ export class ChatService extends Disposable implements IChatService { const parsedRequest = typeof message === 'string' ? this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message; - const request = model.addRequest(parsedRequest, variableData || { message: parsedRequest.text, variables: {} }); + const request = model.addRequest(parsedRequest, variableData || { variables: [] }); if (typeof response.message === 'string') { model.acceptResponseProgress(request, { content: response.message, kind: 'content' }); } else { @@ -765,25 +765,14 @@ export class ChatService extends Disposable implements IChatService { } } -function asVariablesData2(parsedRequest: IParsedChatRequest, variableData: IChatRequestVariableData): IChatRequestVariableData2 { - - const res: IChatRequestVariableData2 = { - message: getPromptText(parsedRequest.parts), - variables: [] +function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { + return { + variables: variableData.variables.map(v => ({ + ...v, + range: { + start: v.range.start - diff, + endExclusive: v.range.endExclusive - diff + } + })) }; - - for (const part of parsedRequest.parts) { - if (part instanceof ChatRequestVariablePart) { - const values = variableData.variables[part.variableName]; - res.variables.push({ name: part.variableName, range: part.range, values }); - } else if (part instanceof ChatRequestDynamicVariablePart) { - // Need variable without `#` - res.variables.push({ name: part.referenceText, range: part.range, values: part.data }); - } - } - - // "reverse", high index first so that replacement is simple - res.variables.sort((a, b) => b.range.start - a.range.start); - - return res; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index bda3f40baac91..7b9bbde63c78d 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -41,48 +41,44 @@ suite('ChatVariables', function () { const parser = instantiationService.createInstance(ChatRequestParser); const resolveVariables = async (text: string) => { - const result = await parser.parseChatRequest('1', text); + const result = parser.parseChatRequest('1', text); return await service.resolveVariables(result, null!, CancellationToken.None); }; { const data = await resolveVariables('Hello #foo and#far'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo) and#far'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('#foo Hello'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, '[#foo](values:foo) Hello'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo?'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo)?'); + assert.strictEqual(data.variables.length, 1); + assert.deepEqual(data.variables.map(v => v.name), ['foo']); } { const data = await resolveVariables('Hello #foo and#far #foo'); - assert.strictEqual(Object.keys(data.variables).length, 1); - assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); + assert.strictEqual(data.variables.length, 2); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'foo']); } { const data = await resolveVariables('Hello #foo and #far #foo'); - assert.strictEqual(Object.keys(data.variables).length, 2); - assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); + assert.strictEqual(data.variables.length, 3); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'far', 'foo']); } { const data = await resolveVariables('Hello #foo and #far #foo #unknown'); - assert.strictEqual(Object.keys(data.variables).length, 2); - assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); - assert.strictEqual(data.message, 'Hello [#foo](values:foo) and [#far](values:far) [#foo](values:foo) #unknown'); + assert.strictEqual(data.variables.length, 3); + assert.deepEqual(data.variables.map(v => v.name), ['foo', 'far', 'foo']); } v1.dispose(); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 1e34cf65b08fb..d58da8b3744c2 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -42,10 +42,7 @@ } ] }, - variableData: { - message: "@ChatProviderWithUsedContext test request", - variables: { } - }, + variableData: { variables: [ ] }, response: [ ], result: { metadata: { metadataKey: "value" } }, followups: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 16a53a88b077b..3a6b248a7927e 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -42,10 +42,7 @@ ], text: "@ChatProviderWithUsedContext test request" }, - variableData: { - message: "@ChatProviderWithUsedContext test request", - variables: { } - }, + variableData: { variables: [ ] }, response: [ ], result: { metadata: { metadataKey: "value" } }, followups: undefined, diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index ed18235fe4f8c..ffcda81f6de29 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -111,7 +111,7 @@ suite('ChatModel', () => { model.startInitialize(); model.initialize({} as any, undefined); const text = 'hello'; - model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { message: text, variables: {} }); + model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }); const requests = model.getRequests(); assert.strictEqual(requests.length, 1); diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index eface4d487db3..9f7e33e850ae7 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -131,11 +131,11 @@ suite('Chat', () => { const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); await session1.waitForInitialization(); - session1.addRequest({ parts: [], text: 'request 1' }, { message: 'request 1', variables: {} }); + session1.addRequest({ parts: [], text: 'request 1' }, { variables: [] }); const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None)); await session2.waitForInitialization(); - session2.addRequest({ parts: [], text: 'request 2' }, { message: 'request 2', variables: {} }); + session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }); storageService.flush(); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index 73d64ca339b94..7522025433650 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -29,8 +29,7 @@ export class MockChatVariablesService implements IChatVariablesService { async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { return { - message: prompt.text, - variables: {} + variables: [] }; } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 3ee255c32e1bf..39b2360f3607d 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -31,7 +31,7 @@ declare module 'vscode' { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. * * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. @@ -88,11 +88,6 @@ declare module 'vscode' { * All of the chat messages so far in the current chat session. */ readonly history: ReadonlyArray; - - /** - * @deprecated, use histroy - */ - readonly history2: ReadonlyArray; } /** @@ -325,41 +320,25 @@ declare module 'vscode' { export interface ChatAgentRequest { - /** - * The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentCommand.name command} - * are not part of the prompt. - * - * @see {@link ChatAgentRequest.command} - */ - prompt: string; - /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}. + * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. * * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. */ - prompt2: string; - - /** - * The ID of the chat agent to which this request was directed. - */ - agentId: string; + prompt: string; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. */ command?: string; - /** @deprecated */ - variables: Record; - /** - * + * The list of variables that are referenced in the prompt. */ - variables2: ChatAgentResolvedVariable[]; + variables: ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { 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 0262/1863] 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 7a43cef7d12df..87b1d68f988f3 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 e41ae049b6e4f..aeb85706fc621 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 2f69e1b13f87e..02383b6b3f38c 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 00b117d6661b0..86287257f8f71 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 41332e575fbe5..6374e1cc99156 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 0263/1863] 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 86287257f8f71..f3d7ef853264e 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 f174624716b48b490b3a27f33f844b79448f85ea Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 17:20:00 -0300 Subject: [PATCH 0264/1863] Add tooltip to disabled command button (#205140) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 194e012133d6a..8ab85eb1297b6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -838,9 +838,13 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.commandService.executeCommand(commandButton.command.id, ...(commandButton.command.arguments ?? [])))); From 30faa5af8a5c46faf6aea73b0121dce4abb6fbec Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 13 Feb 2024 17:41:32 -0300 Subject: [PATCH 0265/1863] API notes (#205144) --- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 39b2360f3607d..8aa7e9c184fbd 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -54,9 +54,8 @@ declare module 'vscode' { readonly command: string | undefined; /** - * + * The variables that were referenced in this message. */ - // TODO@API is this needed? readonly variables: ChatAgentResolvedVariable[]; private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); @@ -207,15 +206,18 @@ declare module 'vscode' { export interface ChatAgentFollowup { /** * The message to send to the chat. - * TODO@API is it ok for variables to resolved from the text of this prompt, using the `#` syntax? */ prompt: string; /** - * By default, the followup goes to the same agent/command. But these properties can be set to override that. + * By default, the followup goes to the same agent/command. But this property can be set to invoke a different agent. + * TODO@API do extensions need to specify the extensionID of the agent here as well? */ agentId?: string; + /** + * By default, the followup goes to the same agent/command. But this property can be set to invoke a different command. + */ command?: string; /** From f205511a4154adb94ccdd95c92301ae8243007f7 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 13 Feb 2024 21:26:40 +0100 Subject: [PATCH 0266/1863] Improves observable test docs --- src/vs/base/test/common/observable.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 9227c4655793d..63b4c9c48df88 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -18,11 +18,15 @@ suite('observables', () => { suite('tutorial', () => { test('observable + autorun', () => { const log = new Log(); - // This creates a new observable value. The name is only used for debugging purposes. + // This creates a variable that stores a value and whose value changes can be observed. + // The name is only used for debugging purposes. // The second arg is the initial value. const myObservable = observableValue('myObservable', 0); - // This creates an autorun. The @description is only used for debugging purposes. + // This creates an autorun: It runs immediately and then again whenever any of the + // dependencies change. Dependencies are tracked by reading observables with the `reader` parameter. + // + // The @description is only used for debugging purposes. // The autorun has to be disposed! This is very important. ds.add(autorun(reader => { /** @description myAutorun */ @@ -31,7 +35,7 @@ suite('observables', () => { // Use the `reader` to read observable values and track the dependency to them. // If you use `observable.get()` instead of `observable.read(reader)`, you will just - // get the value and not track the dependency. + // get the value and not subscribe to it. log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); // Now that all dependencies are tracked, the autorun is re-run whenever any of the From bf1bb656259791cf84051759a1d264409b5239c0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:50:05 -0600 Subject: [PATCH 0267/1863] 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 6374e1cc99156..0e8b82043eabe 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 5ef40d1286de90fcc8641155058614632a524409 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 13 Feb 2024 21:32:05 +0100 Subject: [PATCH 0268/1863] Improves observable debug names --- .../base/common/observableInternal/autorun.ts | 81 +++++----- src/vs/base/common/observableInternal/base.ts | 102 +----------- .../common/observableInternal/debugName.ts | 145 ++++++++++++++++++ .../base/common/observableInternal/derived.ts | 62 +++++--- .../base/common/observableInternal/utils.ts | 9 +- 5 files changed, 239 insertions(+), 160 deletions(-) create mode 100644 src/vs/base/common/observableInternal/debugName.ts diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index 6e7fdcf6d52a0..6c14cb20c5bd1 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -5,7 +5,8 @@ import { assertFn } from 'vs/base/common/assert'; import { DisposableStore, IDisposable, markAsDisposed, toDisposable, trackDisposable } from 'vs/base/common/lifecycle'; -import { IReader, IObservable, IObserver, IChangeContext, getFunctionName } from 'vs/base/common/observableInternal/base'; +import { IReader, IObservable, IObserver, IChangeContext } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, IDebugNameData } from 'vs/base/common/observableInternal/debugName'; import { getLogger } from 'vs/base/common/observableInternal/logging'; /** @@ -13,15 +14,25 @@ import { getLogger } from 'vs/base/common/observableInternal/logging'; * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ export function autorun(fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(undefined, fn, undefined, undefined); + return new AutorunObserver( + new DebugNameData(undefined, undefined, fn), + fn, + undefined, + undefined + ); } /** * Runs immediately and whenever a transaction ends and an observed observable changed. * {@link fn} should start with a JS Doc using `@description` to name the autorun. */ -export function autorunOpts(options: { debugName?: string | (() => string | undefined) }, fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(options.debugName, fn, undefined, undefined); +export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + undefined, + undefined + ); } /** @@ -36,22 +47,25 @@ export function autorunOpts(options: { debugName?: string | (() => string | unde * @see autorun */ export function autorunHandleChanges( - options: { - debugName?: string | (() => string | undefined); + options: IDebugNameData & { createEmptyChangeSummary?: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; }, fn: (reader: IReader, changeSummary: TChangeSummary) => void ): IDisposable { - return new AutorunObserver(options.debugName, fn, options.createEmptyChangeSummary, options.handleChange); + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + options.createEmptyChangeSummary, + options.handleChange + ); } /** * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose) */ export function autorunWithStoreHandleChanges( - options: { - debugName?: string | (() => string | undefined); + options: IDebugNameData & { createEmptyChangeSummary?: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; }, @@ -60,7 +74,9 @@ export function autorunWithStoreHandleChanges( const store = new DisposableStore(); const disposable = autorunHandleChanges( { - debugName: options.debugName ?? (() => getFunctionName(fn)), + owner: options.owner, + debugName: options.debugName, + debugReferenceFn: options.debugReferenceFn, createEmptyChangeSummary: options.createEmptyChangeSummary, handleChange: options.handleChange, }, @@ -82,7 +98,9 @@ export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) = const store = new DisposableStore(); const disposable = autorunOpts( { - debugName: () => getFunctionName(fn) || '(anonymous)', + owner: undefined, + debugName: undefined, + debugReferenceFn: fn, }, reader => { store.clear(); @@ -95,6 +113,20 @@ export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) = }); } +export function autorunDelta( + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorunOpts({ debugReferenceFn: handler }, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} + + const enum AutorunState { /** * A dependency could have changed. @@ -118,21 +150,11 @@ export class AutorunObserver implements IObserver, IReader private changeSummary: TChangeSummary | undefined; public get debugName(): string { - if (typeof this._debugName === 'string') { - return this._debugName; - } - if (typeof this._debugName === 'function') { - const name = this._debugName(); - if (name !== undefined) { return name; } - } - const name = getFunctionName(this._runFn); - if (name !== undefined) { return name; } - - return '(anonymous)'; + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; } constructor( - private readonly _debugName: string | (() => string | undefined) | undefined, + private readonly _debugNameData: DebugNameData, public readonly _runFn: (reader: IReader, changeSummary: TChangeSummary) => void, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, @@ -257,16 +279,3 @@ export class AutorunObserver implements IObserver, IReader export namespace autorun { export const Observer = AutorunObserver; } - -export function autorunDelta( - observable: IObservable, - handler: (args: { lastValue: T | undefined; newValue: T }) => void -): IDisposable { - let _lastValue: T | undefined; - return autorunOpts({ debugName: () => getFunctionName(handler) }, (reader) => { - const newValue = observable.read(reader); - const lastValue = _lastValue; - _lastValue = newValue; - handler({ lastValue, newValue }); - }); -} diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 45e6f0ef2011f..74b03df14383a 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -5,6 +5,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { keepObserved, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; +import { DebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; import type { derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -354,105 +355,6 @@ export class TransactionImpl implements ITransaction { } } -/** - * The owner object of an observable. - * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. - */ -export type Owner = object | undefined; -export type DebugNameFn = string | (() => string | undefined); - -const countPerName = new Map(); -const cachedDebugName = new WeakMap(); - -export function getDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { - const cached = cachedDebugName.get(self); - if (cached) { - return cached; - } - - const dbgName = computeDebugName(self, debugNameFn, fn, owner); - if (dbgName) { - let count = countPerName.get(dbgName) ?? 0; - count++; - countPerName.set(dbgName, count); - const result = count === 1 ? dbgName : `${dbgName}#${count}`; - cachedDebugName.set(self, result); - return result; - } - return undefined; -} - -function computeDebugName(self: object, debugNameFn: DebugNameFn | undefined, fn: Function | undefined, owner: Owner): string | undefined { - const cached = cachedDebugName.get(self); - if (cached) { - return cached; - } - - const ownerStr = owner ? formatOwner(owner) + `.` : ''; - - let result: string | undefined; - if (debugNameFn !== undefined) { - if (typeof debugNameFn === 'function') { - result = debugNameFn(); - if (result !== undefined) { - return ownerStr + result; - } - } else { - return ownerStr + debugNameFn; - } - } - - if (fn !== undefined) { - result = getFunctionName(fn); - if (result !== undefined) { - return ownerStr + result; - } - } - - if (owner !== undefined) { - for (const key in owner) { - if ((owner as any)[key] === self) { - return ownerStr + key; - } - } - } - return undefined; -} - -const countPerClassName = new Map(); -const ownerId = new WeakMap(); - -function formatOwner(owner: object): string { - const id = ownerId.get(owner); - if (id) { - return id; - } - const className = getClassName(owner); - let count = countPerClassName.get(className) ?? 0; - count++; - countPerClassName.set(className, count); - const result = count === 1 ? className : `${className}#${count}`; - ownerId.set(owner, result); - return result; -} - -function getClassName(obj: object): string { - const ctor = obj.constructor; - if (ctor) { - return ctor.name; - } - return 'Object'; -} - -export function getFunctionName(fn: Function): string | undefined { - const fnSrc = fn.toString(); - // Pattern: /** @description ... */ - const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; - const match = regexp.exec(fnSrc); - const result = match ? match[1] : undefined; - return result?.trim(); -} - /** * A settable observable. */ @@ -481,7 +383,7 @@ export class ObservableValue protected _value: T; get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner) ?? 'ObservableValue'; + return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'ObservableValue'; } constructor( diff --git a/src/vs/base/common/observableInternal/debugName.ts b/src/vs/base/common/observableInternal/debugName.ts new file mode 100644 index 0000000000000..481d24f03777d --- /dev/null +++ b/src/vs/base/common/observableInternal/debugName.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IDebugNameData { + /** + * The owner object of an observable. + * Used for debugging only, such as computing a name for the observable by iterating over the fields of the owner. + */ + readonly owner?: Owner | undefined; + + /** + * A string or function that returns a string that represents the name of the observable. + * Used for debugging only. + */ + readonly debugName?: DebugNameSource | undefined; + + /** + * A function that points to the defining function of the object. + * Used for debugging only. + */ + readonly debugReferenceFn?: Function | undefined; +} + +export class DebugNameData { + constructor( + public readonly owner: Owner | undefined, + public readonly debugNameSource: DebugNameSource | undefined, + public readonly referenceFn: Function | undefined, + ) { } + + public getDebugName(target: object): string | undefined { + return getDebugName(target, this); + } +} + +/** + * The owner object of an observable. + * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. + */ +export type Owner = object | undefined; +export type DebugNameSource = string | (() => string | undefined); + +const countPerName = new Map(); +const cachedDebugName = new WeakMap(); + +export function getDebugName(target: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(target); + if (cached) { + return cached; + } + + const dbgName = computeDebugName(target, data); + if (dbgName) { + let count = countPerName.get(dbgName) ?? 0; + count++; + countPerName.set(dbgName, count); + const result = count === 1 ? dbgName : `${dbgName}#${count}`; + cachedDebugName.set(target, result); + return result; + } + return undefined; +} + +function computeDebugName(self: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(self); + if (cached) { + return cached; + } + + const ownerStr = data.owner ? formatOwner(data.owner) + `.` : ''; + + let result: string | undefined; + const debugNameSource = data.debugNameSource; + if (debugNameSource !== undefined) { + if (typeof debugNameSource === 'function') { + result = debugNameSource(); + if (result !== undefined) { + return ownerStr + result; + } + } else { + return ownerStr + debugNameSource; + } + } + + const referenceFn = data.referenceFn; + if (referenceFn !== undefined) { + result = getFunctionName(referenceFn); + if (result !== undefined) { + return ownerStr + result; + } + } + + if (data.owner !== undefined) { + const key = findKey(data.owner, self); + if (key !== undefined) { + return ownerStr + key; + } + } + return undefined; +} + +function findKey(obj: object, value: object): string | undefined { + for (const key in obj) { + if ((obj as any)[key] === value) { + return key; + } + } + return undefined; +} + +const countPerClassName = new Map(); +const ownerId = new WeakMap(); + +function formatOwner(owner: object): string { + const id = ownerId.get(owner); + if (id) { + return id; + } + const className = getClassName(owner); + let count = countPerClassName.get(className) ?? 0; + count++; + countPerClassName.set(className, count); + const result = count === 1 ? className : `${className}#${count}`; + ownerId.set(owner, result); + return result; +} + +function getClassName(obj: object): string { + const ctor = obj.constructor; + if (ctor) { + return ctor.name; + } + return 'Object'; +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 573b5ad5258ca..cab2c7c4bc1c7 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -5,7 +5,8 @@ import { assertFn } from 'vs/base/common/assert'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { BaseObservable, DebugNameFn, IChangeContext, IObservable, IObserver, IReader, Owner, _setDerivedOpts, getDebugName, getFunctionName } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, IChangeContext, IObservable, IObserver, IReader, _setDerivedOpts } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; @@ -21,23 +22,44 @@ export function derived(computeFn: (reader: IReader) => T): IObservable; export function derived(owner: object, computeFn: (reader: IReader) => T): IObservable; export function derived(computeFnOrOwner: ((reader: IReader) => T) | object, computeFn?: ((reader: IReader) => T) | undefined): IObservable { if (computeFn !== undefined) { - return new Derived(computeFnOrOwner, undefined, computeFn, undefined, undefined, undefined, defaultEqualityComparer); + return new Derived( + new DebugNameData(computeFnOrOwner, undefined, computeFn), + computeFn, + undefined, + undefined, + undefined, + defaultEqualityComparer + ); } - return new Derived(undefined, undefined, computeFnOrOwner as any, undefined, undefined, undefined, defaultEqualityComparer); + return new Derived( + new DebugNameData(undefined, undefined, computeFnOrOwner as any), + computeFnOrOwner as any, + undefined, + undefined, + undefined, + defaultEqualityComparer + ); } export function derivedOpts( - options: { - owner?: object; - debugName?: DebugNameFn; + options: IDebugNameData & { equalityComparer?: EqualityComparer; onLastObserverRemoved?: (() => void); }, computeFn: (reader: IReader) => T ): IObservable { - return new Derived(options.owner, options.debugName, computeFn, undefined, undefined, options.onLastObserverRemoved, options.equalityComparer ?? defaultEqualityComparer); + return new Derived( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), + computeFn, + undefined, + undefined, + options.onLastObserverRemoved, + options.equalityComparer ?? defaultEqualityComparer + ); } +_setDerivedOpts(derivedOpts); + /** * Represents an observable that is derived from other observables. * The value is only recomputed when absolutely needed. @@ -52,16 +74,21 @@ export function derivedOpts( * @see derived */ export function derivedHandleChanges( - options: { - owner?: object; - debugName?: string | (() => string); + options: IDebugNameData & { createEmptyChangeSummary: () => TChangeSummary; handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; equalityComparer?: EqualityComparer; }, computeFn: (reader: IReader, changeSummary: TChangeSummary) => T ): IObservable { - return new Derived(options.owner, options.debugName, computeFn, options.createEmptyChangeSummary, options.handleChange, undefined, options.equalityComparer ?? defaultEqualityComparer); + return new Derived( + new DebugNameData(options.owner, options.debugName, undefined), + computeFn, + options.createEmptyChangeSummary, + options.handleChange, + undefined, + options.equalityComparer ?? defaultEqualityComparer + ); } export function derivedWithStore(computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; @@ -79,8 +106,7 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: const store = new DisposableStore(); return new Derived( - owner, - (() => getFunctionName(computeFn) ?? '(anonymous)'), + new DebugNameData(owner, undefined, computeFn), r => { store.clear(); return computeFn(r, store); @@ -106,8 +132,7 @@ export function derivedDisposable(computeFnOr const store = new DisposableStore(); return new Derived( - owner, - (() => getFunctionName(computeFn) ?? '(anonymous)'), + new DebugNameData(owner, undefined, computeFn), r => { store.clear(); const result = computeFn(r); @@ -122,8 +147,6 @@ export function derivedDisposable(computeFnOr ); } -_setDerivedOpts(derivedOpts); - const enum DerivedState { /** Initial state, no previous value, recomputation needed */ initial = 0, @@ -155,12 +178,11 @@ export class Derived extends BaseObservable im private changeSummary: TChangeSummary | undefined = undefined; public override get debugName(): string { - return getDebugName(this, this._debugName, this._computeFn, this._owner) ?? '(anonymous)'; + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; } constructor( - private readonly _owner: Owner, - private readonly _debugName: DebugNameFn | undefined, + private readonly _debugNameData: DebugNameData, public readonly _computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, private readonly createChangeSummary: (() => TChangeSummary) | undefined, private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 830785aab401f..a16feb1f65c7c 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -6,7 +6,8 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, Owner, _setKeepObserved, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; +import { DebugNameData, Owner, getFunctionName } from 'vs/base/common/observableInternal/debugName'; import { derived, derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -249,7 +250,7 @@ export interface IObservableSignal extends IObservable { class ObservableSignal extends BaseObservable implements IObservableSignal { public get debugName() { - return getDebugName(this, this._debugName, undefined, this._owner) ?? 'Observable Signal'; + return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'Observable Signal'; } constructor( @@ -352,7 +353,7 @@ export function recomputeInitiallyAndOnChange(observable: IObservable, han _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange); -class KeepAliveObserver implements IObserver { +export class KeepAliveObserver implements IObserver { private _counter = 0; constructor( @@ -415,7 +416,7 @@ export function derivedObservableWithWritableCache(owner: object, computeFn: export function mapObservableArrayCached(owner: Owner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { let m = new ArrayMap(map, keySelector); const self = derivedOpts({ - debugName: () => getDebugName(m, undefined, map, owner), + debugReferenceFn: map, owner, onLastObserverRemoved: () => { m.dispose(); From 83f8ee26b6c832c68f7ba37f7b3f083bc2b76292 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 14:53:59 -0600 Subject: [PATCH 0269/1863] 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 f3d7ef853264e..83adeff265d6b 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 0e8b82043eabe..61aa2c4a4091b 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 71bd033d631e32a97e384f2d7123717354ab7d42 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:02:37 -0600 Subject: [PATCH 0270/1863] Quick Search leaves preview editors open (#205141) Fixes #203236 --- src/vs/workbench/browser/quickaccess.ts | 9 ++++++++- .../browser/quickTextSearch/textSearchQuickAccess.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index a73c3d7072c5c..b0a88a281426f 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -75,12 +75,19 @@ export class EditorViewState { } } - async restore(): Promise { + async restore(shouldCloseCurrEditor = false): Promise { if (this._editorViewState) { const options: IEditorOptions = { viewState: this._editorViewState.state, preserveFocus: true /* import to not close the picker as a result */ }; + if (shouldCloseCurrEditor) { + const activeEditorPane = this.editorService.activeEditorPane; + const currEditor = activeEditorPane?.input; + if (currEditor && currEditor !== this._editorViewState.editor) { + await activeEditorPane.group.closeEditor(currEditor); + } + } await this._editorViewState.group.openEditor(this._editorViewState.editor, options); } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index b5ad884d5d030..15a07cb067524 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -141,7 +141,7 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider Date: Tue, 13 Feb 2024 22:04:29 +0100 Subject: [PATCH 0271/1863] Git - Add button/setting to always replace local tags in case of a conflict during the pull operation (#205148) --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/repository.ts | 22 ++++++++++++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index a36b08c0baa56..f35aa7989ad0d 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2645,6 +2645,12 @@ "default": false, "description": "%config.followTagsWhenSync%" }, + "git.replaceTagsWhenPull": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.replaceTagsWhenPull%" + }, "git.promptToSaveFilesBeforeStash": { "type": "string", "enum": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 388d5387261e4..28ae736038ffd 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -176,6 +176,7 @@ "config.decorations.enabled": "Controls whether Git contributes colors and badges to the Explorer and the Open Editors view.", "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", "config.followTagsWhenSync": "Push all annotated tags when running the sync command.", + "config.replaceTagsWhenPull": "Automatically replace the local tags with the remote tags in case of a conflict when running the pull command.", "config.promptToSaveFilesBeforeStash": "Controls whether Git should check for unsaved files before stashing changes.", "config.promptToSaveFilesBeforeStash.always": "Check for any unsaved files.", "config.promptToSaveFilesBeforeStash.staged": "Check only for unsaved staged files.", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c71069fd3c823..cadb3c717f70c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2630,13 +2630,23 @@ export class Repository implements Disposable { throw new Error(`Unable to extract tag names from error message: ${raw}`); } - // Notification - const replaceLocalTags = l10n.t('Replace Local Tag(s)'); - const message = l10n.t('Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?', tags.join(', ')); - const choice = await window.showErrorMessage(message, { modal: true }, replaceLocalTags); + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const replaceTagsWhenPull = config.get('replaceTagsWhenPull', false) === true; + + if (!replaceTagsWhenPull) { + // Notification + const replaceLocalTags = l10n.t('Replace Local Tag(s)'); + const replaceLocalTagsAlways = l10n.t('Always Replace Local Tag(s)'); + const message = l10n.t('Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?', tags.join(', ')); + const choice = await window.showErrorMessage(message, { modal: true }, replaceLocalTags, replaceLocalTagsAlways); + + if (choice !== replaceLocalTags && choice !== replaceLocalTagsAlways) { + return false; + } - if (choice !== replaceLocalTags) { - return false; + if (choice === replaceLocalTagsAlways) { + await config.update('replaceTagsWhenPull', true, true); + } } // Force fetch tags From 1d262b3146aa7f3c254d28fc0144f9ae08bf9717 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:05:59 -0600 Subject: [PATCH 0272/1863] 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 83adeff265d6b..9c9cb0a2ff8b9 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 0273/1863] 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 61aa2c4a4091b..0c174ed35553f 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 43088f274753b2c6d22f54899028cef88a41514c Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Tue, 13 Feb 2024 13:09:35 -0800 Subject: [PATCH 0274/1863] variable view initialization (#205149) * intitialization fixes * use more appropriate event --- .../contrib/notebookVariables/notebookVariables.ts | 10 +++------- .../contrib/notebookVariables/notebookVariablesView.ts | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 0da62de50b4c8..9e7435fce8870 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -21,7 +21,6 @@ import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/not import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; - export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; private configListener: IDisposable; @@ -40,8 +39,8 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); - this.listeners.push(this.editorService.onDidEditorsChange(() => this.handleInitEvent())); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution(() => this.handleInitEvent())); + this.listeners.push(this.editorService.onDidActiveEditorChange(() => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent())); this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); } @@ -62,11 +61,8 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { - if (this.hasVariableProvider()) { + if (this.hasVariableProvider() && !this.initialized && this.initializeView()) { this.viewEnabled.set(true); - } - - if (!this.initialized && this.initializeView()) { this.initialized = true; this.listeners.forEach(listener => listener.dispose()); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 82360512546e1..515a43f8cf5ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -140,7 +140,7 @@ export class NotebookVariablesView extends ViewPane { private setActiveNotebook() { const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane && activeEditorPane.getId() === 'workbench.editor.notebook') { + if (activeEditorPane?.getId() === 'workbench.editor.notebook') { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; this.activeNotebook = notebookDocument; } From 84fb0821a9347a1b8c563f29259962e758ca6e87 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 13 Feb 2024 15:56:53 -0600 Subject: [PATCH 0275/1863] 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 9c9cb0a2ff8b9..f7edc4b5fd67f 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 0276/1863] 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 f7edc4b5fd67f..221c010600bc2 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 0c174ed35553f..abb8807cdd969 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 07446f62116e478a317fa432b142810f5002181e Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Tue, 13 Feb 2024 22:51:08 +0100 Subject: [PATCH 0277/1863] chat: Use alternating request/response color --- build/lib/stylelint/vscode-known-variables.json | 1 + src/vs/workbench/contrib/chat/browser/media/chat.css | 7 +++++++ src/vs/workbench/contrib/chat/common/chatColors.ts | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0411ea42d7566..5e0c031b144f3 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -44,6 +44,7 @@ "--vscode-chat-avatarBackground", "--vscode-chat-avatarForeground", "--vscode-chat-requestBorder", + "--vscode-chat-requestBackground", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", "--vscode-chat-list-background", diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 52180a6065249..140e418f1e03f 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -221,6 +221,13 @@ .interactive-request { border-bottom: 1px solid var(--vscode-chat-requestBorder); border-top: 1px solid var(--vscode-chat-requestBorder); + background-color: var(--vscode-chat-requestBackground); +} + +.hc-black .interactive-request, +.hc-light .interactive-request { + border-left: 3px solid var(--vscode-chat-requestBorder); + border-right: 3px solid var(--vscode-chat-requestBorder); } .interactive-item-container .value { diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index 97c8bc85f5a3b..a11134cac9d3a 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { badgeBackground, badgeForeground, contrastBorder, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; export const chatRequestBorder = registerColor( 'chat.requestBorder', @@ -13,6 +13,12 @@ export const chatRequestBorder = registerColor( localize('chat.requestBorder', 'The border color of a chat request.') ); +export const chatRequestBackground = registerColor( + 'chat.requestBackground', + { dark: editorBackground, light: editorBackground, hcDark: editorWidgetBackground, hcLight: null }, + localize('chat.requestBackground', 'The background color of a chat request.') +); + export const chatSlashCommandBackground = registerColor( 'chat.slashCommandBackground', { dark: '#34414B', light: '#D2ECFF', hcDark: Color.white, hcLight: badgeBackground }, 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 0278/1863] 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 abb8807cdd969..c3efebdc5ca07 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 0279/1863] 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 221c010600bc2..36db8ad2ab9cd 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 c3efebdc5ca07..402f86385582e 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 0280/1863] 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 37cf45c77fcae..a62ed9442454a 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 0281/1863] 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 a62ed9442454a..348d5bee1591f 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 0282/1863] 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 348d5bee1591f..d0205008d7464 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 9209da7104e2c60b4f16315d9b07e007cc9ec837 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 15:41:15 -0800 Subject: [PATCH 0283/1863] Ensure escape discard/accept changes properly. --- .../controller/chat/cellChatActions.ts | 23 ++++--- .../controller/chat/notebookChatContext.ts | 1 + .../controller/chat/notebookChatController.ts | 62 ++++++++++++++++--- .../browser/controller/editActions.ts | 4 +- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 3829e6e67c27c..7b8ce9ff6df4f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,12 +14,12 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; registerAction2(class extends NotebookAction { @@ -222,11 +222,18 @@ registerAction2(class extends NotebookAction { shortTitle: localize('apply2', 'Accept'), icon: Codicon.check, tooltip: localize('apply3', 'Accept Changes'), - keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), - weight: KeybindingWeight.EditorContrib + 10, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - }, + keybinding: [ + { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED), + weight: KeybindingWeight.EditorContrib + 10, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + }, + { + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT), + weight: KeybindingWeight.EditorCore + 10, + primary: KeyCode.Escape + } + ], menu: [ { id: MENU_CELL_CHAT_WIDGET_STATUS, @@ -251,7 +258,7 @@ registerAction2(class extends NotebookAction { title: localize('discard', 'Discard'), icon: Codicon.discard, keybinding: { - when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, NOTEBOOK_CELL_LIST_FOCUSED), + when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT.negate()), weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape }, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index af75c65626df3..af6c5e38caa24 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -9,6 +9,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); +export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat")); export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 6f0a0ad584a9e..b977e9fbc9222 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -40,11 +40,12 @@ import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contr import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -100,10 +101,21 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { this.inlineChatWidget.focus(); } - async getEditingCellEditor() { + getEditingCell() { + return this._editingCell; + } + + async getOrCreateEditingCell(): Promise<{ cell: CellViewModel; editor: IActiveCodeEditor } | undefined> { if (this._editingCell) { await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor'); - return this._notebookEditor.activeCodeEditor; + if (this._notebookEditor.activeCodeEditor?.hasModel()) { + return { + cell: this._editingCell, + editor: this._notebookEditor.activeCodeEditor + }; + } else { + return undefined; + } } if (!this._notebookEditor.hasModel()) { @@ -117,7 +129,14 @@ class NotebookChatWidget extends Disposable implements INotebookViewZone { } await this._notebookEditor.focusNotebookCell(this._editingCell, 'editor', { revealBehavior: ScrollToRevealBehavior.firstLine }); - return this._notebookEditor.activeCodeEditor; + if (this._notebookEditor.activeCodeEditor?.hasModel()) { + return { + cell: this._editingCell, + editor: this._notebookEditor.activeCodeEditor + }; + } + + return undefined; } async discardChange() { @@ -160,6 +179,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _activeSession?: Session; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; + private readonly _ctxUserDidEdit: IContextKey; + private readonly _userEditingDisposables = this._register(new DisposableStore()); private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; private _widgetDisposableStore = this._register(new DisposableStore()); @@ -174,12 +195,14 @@ export class NotebookChatController extends Disposable implements INotebookEdito @IInlineChatSavingService private readonly _inlineChatSavingService: IInlineChatSavingService, @IModelService private readonly _modelService: IModelService, @ILanguageService private readonly _languageService: ILanguageService, + @INotebookExecutionStateService private _executionStateService: INotebookExecutionStateService, ) { super(); this._ctxHasActiveRequest = CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST.bindTo(this._contextKeyService); this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); + this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService); } run(index: number, input: string | undefined, autoSend: boolean | undefined): void { @@ -270,8 +293,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._languageService ); + this._ctxCellWidgetFocused.set(true); + disposableTimeout(() => { - this._ctxCellWidgetFocused.set(true); this._focusWidget(); }, 0, this._store); @@ -476,6 +500,22 @@ export class NotebookChatController extends Disposable implements INotebookEdito }); } } + + this._userEditingDisposables.clear(); + // monitor user edits + const editingCell = this._widget.getEditingCell(); + if (editingCell) { + this._userEditingDisposables.add(editingCell.model.onDidChangeContent(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeLanguage(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeMetadata(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeInternalMetadata(() => this._updateUserEditingState())); + this._userEditingDisposables.add(editingCell.model.onDidChangeOutputs(() => this._updateUserEditingState())); + this._userEditingDisposables.add(this._executionStateService.onDidChangeExecution(e => { + if (e.type === NotebookExecutionType.cell && e.affectsCell(editingCell.uri)) { + this._updateUserEditingState(); + } + })); + } } } catch (e) { response = new ErrorResponse(e); @@ -519,12 +559,14 @@ export class NotebookChatController extends Disposable implements INotebookEdito assertType(this._strategy); assertType(this._widget); - const editor = await this._widget.getEditingCellEditor(); + const editingCell = await this._widget.getOrCreateEditingCell(); - if (!editor || !editor.hasModel()) { + if (!editingCell) { return; } + const editor = editingCell.editor; + const moreMinimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(editor.getModel().uri, edits); // this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, edits, moreMinimalEdits); @@ -551,6 +593,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito } } + private _updateUserEditingState() { + this._ctxUserDidEdit.set(true); + } + async acceptSession() { assertType(this._activeSession); assertType(this._strategy); @@ -615,6 +661,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito discard() { this._strategy?.cancel(); this._widget?.discardChange(); + this.dismiss(); } async feedbackLast(kind: InlineChatResponseFeedbackKind) { @@ -627,6 +674,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito dismiss() { this._ctxCellWidgetFocused.set(false); + this._ctxUserDidEdit.set(false); this._sessionCtor?.cancel(); this._sessionCtor = undefined; this._widget?.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 59f95276c9fab..f056d2e6c7faf 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -32,6 +32,7 @@ import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; @@ -85,7 +86,8 @@ registerAction2(class EditCellAction extends NotebookCellAction { const quitEditCondition = ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, - InputFocusedContext + InputFocusedContext, + CTX_INLINE_CHAT_FOCUSED.toNegated() ); registerAction2(class QuitEditCellAction extends NotebookCellAction { constructor() { From 8bec0459452bd0cc6122b8f49f6d4943b8328c4e Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 12 Feb 2024 14:08:45 +0100 Subject: [PATCH 0284/1863] rename suggestions: migrate to using list widget --- src/vs/editor/common/languages.ts | 11 +- .../common/standalone/standaloneEnums.ts | 4 + .../editor/contrib/rename/browser/rename.ts | 93 ++++- .../rename/browser/renameInputField.css | 13 - .../rename/browser/renameInputField.ts | 384 ++++++++++++++---- .../standalone/browser/standaloneLanguages.ts | 10 + src/vs/monaco.d.ts | 16 +- .../api/browser/mainThreadLanguageFeatures.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 2 + .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 65 +-- src/vs/workbench/api/common/extHostTypes.ts | 17 + ...scode.proposed.newSymbolNamesProvider.d.ts | 13 +- 13 files changed, 506 insertions(+), 126 deletions(-) diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 1f0a242b31f19..252dabcbda609 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1652,8 +1652,17 @@ export interface RenameProvider { resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + +export interface NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; +} + export interface NewSymbolNamesProvider { - provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + provideNewSymbolNames(model: model.ITextModel, range: IRange, token: CancellationToken): ProviderResult; } export interface Command { diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index bbdeb0560ff61..ed3b49ae7562a 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -702,6 +702,10 @@ export enum MouseTargetType { OUTSIDE_EDITOR = 13 } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + /** * A positioning preference for rendering overlay widgets. */ diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 16229d323e963..9b0eef5895534 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -11,20 +11,23 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { NewSymbolName, Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; -import { Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import * as nls from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -33,9 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; class RenameSkeleton { @@ -213,7 +214,7 @@ class RenameController implements IEditorContribution { this._languageFeaturesService.newSymbolNamesProvider .all(model) .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able - ).then((candidates) => candidates.filter((c): c is string[] => !!c).flat()); + ).then((candidates) => candidates.filter((c): c is NewSymbolName[] => !!c).flat()); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -288,6 +289,14 @@ class RenameController implements IEditorContribution { cancelRenameInput(): void { this._renameInputField.cancelInput(true); } + + focusNextRenameSuggestion(): void { + this._renameInputField.focusNextRenameSuggestion(); + } + + focusPreviousRenameSuggestion(): void { + this._renameInputField.focusPreviousRenameSuggestion(); + } } // ---- action implementation @@ -380,6 +389,76 @@ registerEditorCommand(new RenameCommand({ } })); +registerAction2(class FocusNextRenameSuggestion extends Action2 { + constructor() { + super({ + id: 'focusNextRenameSuggestion', + title: { + ...nls.localize2('focusNextRenameSuggestion', "Focus Next Rename Suggestion"), + }, + precondition: CONTEXT_RENAME_INPUT_VISIBLE, + keybinding: [ + { + when: CONTEXT_RENAME_INPUT_FOCUSED, + primary: KeyCode.Tab, + weight: KeybindingWeight.EditorContrib + 99, + }, + { + when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), + primary: KeyCode.Tab, + secondary: [KeyCode.DownArrow], + weight: KeybindingWeight.EditorContrib + 99, + } + ] + }); + } + + override run(accessor: ServicesAccessor): void { + const currentEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (!currentEditor) { return; } + + const controller = RenameController.get(currentEditor); + if (!controller) { return; } + + controller.focusNextRenameSuggestion(); + } +}); + +registerAction2(class FocusPreviousRenameSuggestion extends Action2 { + constructor() { + super({ + id: 'focusPreviousRenameSuggestion', + title: { + ...nls.localize2('focusPreviousRenameSuggestion', "Focus Previous Rename Suggestion"), + }, + precondition: CONTEXT_RENAME_INPUT_VISIBLE, + keybinding: [ + { + when: CONTEXT_RENAME_INPUT_FOCUSED, + primary: KeyCode.Tab | KeyCode.Shift, + weight: KeybindingWeight.EditorContrib + 99, + }, + { + when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), + primary: KeyMod.Shift | KeyCode.Tab, + secondary: [KeyCode.UpArrow], + weight: KeybindingWeight.EditorContrib + 99, + } + ] + }); + } + + override run(accessor: ServicesAccessor): void { + const currentEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (!currentEditor) { return; } + + const controller = RenameController.get(currentEditor); + if (!controller) { return; } + + controller.focusPreviousRenameSuggestion(); + } +}); + // ---- api bridge command registerModelAndPositionCommand('_executeDocumentRenameProvider', function (accessor, model, position, ...args) { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.css b/src/vs/editor/contrib/rename/browser/renameInputField.css index d02e7d5afcabc..9fa6ac1d3f968 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.css +++ b/src/vs/editor/contrib/rename/browser/renameInputField.css @@ -24,19 +24,6 @@ opacity: .8; } -.rename-box .new-name-candidates-container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin-top: 5px; -} - -.rename-box .new-name-candidates-container > .monaco-text-button { - width: auto; - margin: 2px; - padding: 2px; -} - .monaco-editor .rename-box.preview .rename-label { display: inherit; } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 0f8a2dc7b26a9..742c5a72cbaca 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,26 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Button } from 'vs/base/browser/ui/button/button'; +import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight } from 'vs/base/browser/dom'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import * as arrays from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; +import { Codicon } from 'vs/base/common/codicons'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { NewSymbolName, NewSymbolNameTag } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { editorWidgetBackground, inputBackground, inputBorder, inputForeground, widgetBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { + editorWidgetBackground, + inputBackground, + inputBorder, + inputForeground, + widgetBorder, + widgetShadow +} from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +/** for debugging */ +const _sticky = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + + export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false, localize('renameInputVisible', "Whether the rename input widget is visible")); +export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameInputFocused', false, localize('renameInputFocused', "Whether the rename input widget is focused")); export interface RenameInputFieldResult { newName: string; @@ -34,10 +54,13 @@ export class RenameInputField implements IContentWidget { private _position?: Position; private _domNode?: HTMLElement; private _input?: HTMLInputElement; - private _newNameCandidates?: NewSymbolNameCandidates; + private _candidatesView?: CandidatesView; private _label?: HTMLDivElement; private _visible?: boolean; + private _nPxAvailableAbove?: number; + private _nPxAvailableBelow?: number; private readonly _visibleContextKey: IContextKey; + private readonly _focusedContextKey: IContextKey; private readonly _disposables = new DisposableStore(); readonly allowEditorOverflow: boolean = true; @@ -50,6 +73,7 @@ export class RenameInputField implements IContentWidget { @IContextKeyService contextKeyService: IContextKeyService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); + this._focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED.bindTo(contextKeyService); this._editor.addContentWidget(this); @@ -80,13 +104,12 @@ export class RenameInputField implements IContentWidget { this._input.className = 'rename-input'; this._input.type = 'text'; this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); + // TODO@ulugbekna: is using addDisposableListener's right way to do it? + this._disposables.add(addDisposableListener(this._input, 'focus', () => { this._focusedContextKey.set(true); })); + this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - // TODO@ulugbekna: support accept/escape corresponding to the keybindings - this._newNameCandidates = new NewSymbolNameCandidates(); - this._newNameCandidates.onAccept(() => this.acceptInput(false)); // FIXME@ulugbekna: need to handle preview - this._newNameCandidates.onEscape(() => this._input!.focus()); - this._domNode.appendChild(this._newNameCandidates!.domNode); + this._candidatesView = new CandidatesView(this._domNode, { fontInfo: this._editor.getOption(EditorOption.fontInfo) }); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -119,7 +142,7 @@ export class RenameInputField implements IContentWidget { } private _updateFont(): void { - if (!this._input || !this._label || !this._newNameCandidates) { + if (!this._input || !this._label || !this._candidatesView) { return; } @@ -128,38 +151,85 @@ export class RenameInputField implements IContentWidget { this._input.style.fontWeight = fontInfo.fontWeight; this._input.style.fontSize = `${fontInfo.fontSize}px`; - this._newNameCandidates.domNode.style.fontFamily = fontInfo.fontFamily; - this._newNameCandidates.domNode.style.fontWeight = fontInfo.fontWeight; - this._newNameCandidates.domNode.style.fontSize = `${fontInfo.fontSize}px`; + this._candidatesView.updateFont(fontInfo); + + this._label.style.fontSize = `${this._computeLabelFontSize(fontInfo.fontSize)}px`; + } - this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`; + private _computeLabelFontSize(editorFontSize: number) { + return editorFontSize * 0.8; } getPosition(): IContentWidgetPosition | null { if (!this._visible) { return null; } + + if (!this._editor.hasModel() || // @ulugbekna: shouldn't happen + !this._editor.getDomNode() // @ulugbekna: can happen during tests based on suggestWidget's similar predicate check + ) { + return null; + } + + const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); + const editorBox = getDomNodePagePosition(this._editor.getDomNode()); + const cursorBox = this._editor.getScrolledVisiblePosition(this._position!); + + this._nPxAvailableAbove = cursorBox.top + editorBox.top; + this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; + + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const { totalHeight: candidateViewHeight } = CandidateView.getLayoutInfo({ lineHeight }); + + const positionPreference = this._nPxAvailableBelow > candidateViewHeight * 6 /* approximate # of candidates to fit in (inclusive of rename input box & rename label) */ + ? [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + : [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; + return { position: this._position!, - preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + preference: positionPreference, }; } beforeRender(): IDimension | null { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); - // TODO@ulugbekna: elements larger than maxWidth shouldn't overflow - const maxWidth = Math.ceil(this._editor.getLayoutInfo().contentWidth / 4); - this._domNode!.style.maxWidth = `${maxWidth}px`; + this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in + this._domNode!.style.maxWidth = `400px`; // TODO@ulugbekna: what if we have a very long name? + return null; } afterRender(position: ContentWidgetPositionPreference | null): void { - if (!position) { + if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true); + return; + } + + if (!this._editor.hasModel() || // shouldn't happen + !this._editor.getDomNode() // can happen during tests based on suggestWidget's similar predicate check + ) { + return; + } + + assertType(this._candidatesView); + assertType(this._nPxAvailableAbove !== undefined); + assertType(this._nPxAvailableBelow !== undefined); + + const inputBoxHeight = getTotalHeight(this._input!); + + const labelHeight = getTotalHeight(this._label!); + + let totalHeightAvailable: number; + if (position === ContentWidgetPositionPreference.BELOW) { + totalHeightAvailable = this._nPxAvailableBelow; + } else { + totalHeightAvailable = this._nPxAvailableAbove; } + + this._candidatesView!.layout({ height: totalHeightAvailable - labelHeight - inputBoxHeight }); } @@ -174,7 +244,17 @@ export class RenameInputField implements IContentWidget { this._currentCancelInput?.(focusEditor); } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, newNameCandidates: Promise, token: CancellationToken): Promise { + focusNextRenameSuggestion() { + this._candidatesView?.focusNext(); + } + + focusPreviousRenameSuggestion() { + if (!this._candidatesView?.focusPrevious()) { + this._input!.focus(); + } + } + + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -182,51 +262,46 @@ export class RenameInputField implements IContentWidget { this._input!.value = value; this._input!.setAttribute('selectionStart', selectionStart.toString()); this._input!.setAttribute('selectionEnd', selectionEnd.toString()); - this._input!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); + this._input!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); // determines width const disposeOnDone = new DisposableStore(); - newNameCandidates.then(candidates => { - if (!token.isCancellationRequested) { // TODO@ulugbekna: make sure this's the correct token to check - this._newNameCandidates!.setCandidates(candidates); - } - }); + candidates.then(candidates => this._showRenameCandidates(candidates, value, token)); return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { this._currentAcceptInput = undefined; this._currentCancelInput = undefined; - this._newNameCandidates?.clearCandidates(); + this._candidatesView?.clearCandidates(); resolve(focusEditor); return true; }; this._currentAcceptInput = (wantsPreview) => { - if (this._input!.value.trim().length === 0) { - // empty or whitespace only or not changed - this.cancelInput(true); - return; - } + assertType(this._input !== undefined); + assertType(this._candidatesView !== undefined); - const selectedCandidate = this._newNameCandidates?.selectedCandidate; - if ((selectedCandidate === undefined && this._input!.value === value) || selectedCandidate === value) { + const candidateName = this._candidatesView.focusedCandidate; + if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { this.cancelInput(true); return; } this._currentAcceptInput = undefined; this._currentCancelInput = undefined; - this._newNameCandidates?.clearCandidates(); + this._candidatesView.clearCandidates(); resolve({ - newName: selectedCandidate ?? this._input!.value, + newName: candidateName ?? this._input.value, wantsPreview: supportPreview && wantsPreview }); }; disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + if (!_sticky) { + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + } this._show(); @@ -250,6 +325,26 @@ export class RenameInputField implements IContentWidget { }, 100); } + private _showRenameCandidates(candidates: NewSymbolName[], currentName: string, token: CancellationToken): void { + if (token.isCancellationRequested) { + return; + } + + // deduplicate and filter out the current value + candidates = arrays.distinct(candidates, candidate => candidate.newSymbolName); + candidates = candidates.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + + if (candidates.length < 1) { + return; + } + + // show the candidates + this._candidatesView!.setCandidates(candidates); + + // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates + this._editor.layoutContentWidget(this); + } + private _hide(): void { this._visible = false; this._visibleContextKey.reset(); @@ -257,49 +352,198 @@ export class RenameInputField implements IContentWidget { } } -export class NewSymbolNameCandidates { +export class CandidatesView { - public readonly domNode: HTMLElement; + private readonly _listWidget: List; + private readonly _listContainer: HTMLDivElement; - private _onAcceptEmitter = new Emitter(); - public readonly onAccept = this._onAcceptEmitter.event; - private _onEscapeEmitter = new Emitter(); - public readonly onEscape = this._onEscapeEmitter.event; + private _lineHeight: number; + private _availableHeight: number; - private _candidates: Button[] = []; + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo }) { - private _candidateDisposables: DisposableStore | undefined; + this._availableHeight = 0; - // TODO@ulugbekna: pressing escape when focus is on a candidate should return the focus to the input field - constructor() { - this.domNode = document.createElement('div'); - this.domNode.className = 'rename-box new-name-candidates-container'; - this.domNode.tabIndex = -1; // Make the div unfocusable - } + this._lineHeight = opts.fontInfo.lineHeight; + + this._listContainer = document.createElement('div'); + this._listContainer.style.fontFamily = opts.fontInfo.fontFamily; + this._listContainer.style.fontWeight = opts.fontInfo.fontWeight; + this._listContainer.style.fontSize = `${opts.fontInfo.fontSize}px`; + parent.appendChild(this._listContainer); + + const that = this; + + const virtualDelegate = new class implements IListVirtualDelegate { + getTemplateId(element: NewSymbolName): string { + return 'candidate'; + } + + getHeight(element: NewSymbolName): number { + return that.candidateViewHeight; + } + }; + + const renderer = new class implements IListRenderer { + readonly templateId = 'candidate'; + + renderTemplate(container: HTMLElement): CandidateView { + return new CandidateView(container, { lineHeight: that._lineHeight }); + } + + renderElement(candidate: NewSymbolName, index: number, templateData: CandidateView): void { + templateData.model = candidate; + } - get selectedCandidate(): string | undefined { - const selected = this._candidates.find(c => c.hasFocus()); - return selected === undefined ? undefined : ( - assertType(typeof selected.label === 'string', 'string'), - selected.label + disposeTemplate(templateData: CandidateView): void { + templateData.dispose(); + } + }; + + this._listWidget = new List( + 'NewSymbolNameCandidates', + this._listContainer, + virtualDelegate, + [renderer], + { + keyboardSupport: false, // @ulugbekna: because we handle keyboard events through proper commands & keybinding service, see `rename.ts` + mouseSupport: true, + multipleSelectionSupport: false, + } ); + + this._listWidget.style(defaultListStyles); + } + + public get candidateViewHeight(): number { + const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); + return totalHeight; } - setCandidates(candidates: string[]): void { - this._candidateDisposables = new DisposableStore(); - for (let i = 0; i < candidates.length; i++) { - const candidate = candidates[i]; - const candidateElt = new Button(this.domNode, defaultButtonStyles); - this._candidateDisposables.add(candidateElt.onDidClick(() => this._onAcceptEmitter.fire(candidate))); - this._candidateDisposables.add(candidateElt.onDidEscape(() => this._onEscapeEmitter.fire())); - candidateElt.label = candidate; - this._candidates.push(candidateElt); + // height - max height allowed by parent element + public layout({ height }: { height: number }): void { + this._availableHeight = height; + if (this._listWidget.length > 0) { // candidates have been set + this._listWidget.layout(this._pickListHeight(this._listWidget.length)); } } - clearCandidates(): void { - this._candidateDisposables?.dispose(); - this.domNode.innerText = ''; - this._candidates = []; + private _pickListHeight(nCandidates: number) { + const heightToFitAllCandidates = this.candidateViewHeight * nCandidates; + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this.candidateViewHeight * 7 /* max # of candidates we want to show at once */); + return height; + } + + public setCandidates(candidates: NewSymbolName[]): void { + const height = this._pickListHeight(candidates.length); + + this._listWidget.splice(0, 0, candidates); + + this._listWidget.layout(height); + + this._listContainer.style.height = `${height}px`; + } + + public clearCandidates(): void { + this._listContainer.style.height = '0px'; + this._listWidget.splice(0, this._listWidget.length, []); + } + + public get focusedCandidate(): string | undefined { + return this._listWidget.isDOMFocused() ? this._listWidget.getFocusedElements()[0].newSymbolName : undefined; + } + + public updateFont(fontInfo: FontInfo): void { + this._listContainer.style.fontFamily = fontInfo.fontFamily; + this._listContainer.style.fontWeight = fontInfo.fontWeight; + this._listContainer.style.fontSize = `${fontInfo.fontSize}px`; + + this._lineHeight = fontInfo.lineHeight; + + this._listWidget.rerender(); + } + + public focusNext() { + if (this._listWidget.isDOMFocused()) { + this._listWidget.focusNext(); + } else { + this._listWidget.domFocus(); + this._listWidget.focusFirst(); + } + this._listWidget.reveal(this._listWidget.getFocus()[0]); + } + + /** + * @returns true if focus is moved to previous element + */ + public focusPrevious() { + this._listWidget.domFocus(); + const focusedIx = this._listWidget.getFocus()[0]; + if (focusedIx !== 0) { + this._listWidget.focusPrevious(); + this._listWidget.reveal(this._listWidget.getFocus()[0]); + } + return focusedIx > 0; + } +} + +export class CandidateView { // TODO@ulugbekna: remove export + + // TODO@ulugbekna: accessibility + + private static _PADDING: number = 2; + + public readonly domNode: HTMLElement; + private readonly _icon: HTMLElement; + private readonly _label: HTMLElement; + + constructor(parent: HTMLElement, { lineHeight }: { lineHeight: number }) { + + this.domNode = document.createElement('div'); + this.domNode.style.display = `flex`; + this.domNode.style.alignItems = `center`; + this.domNode.style.height = `${lineHeight}px`; + this.domNode.style.padding = `${CandidateView._PADDING}px`; + + this._icon = document.createElement('div'); + this._icon.style.display = `flex`; + this._icon.style.alignItems = `center`; + this._icon.style.width = this._icon.style.height = `${lineHeight * 0.8}px`; + this.domNode.appendChild(this._icon); + + this._label = document.createElement('div'); + this._icon.style.display = `flex`; + this._icon.style.alignItems = `center`; + this._label.style.marginLeft = '5px'; + this.domNode.appendChild(this._label); + + parent.appendChild(this.domNode); + } + + public set model(value: NewSymbolName) { + + // @ulugbekna: a hack to always include sparkle for now + const alwaysIncludeSparkle = true; + + // update icon + if (alwaysIncludeSparkle || value.tags?.includes(NewSymbolNameTag.AIGenerated)) { + if (this._icon.children.length === 0) { + this._icon.appendChild(renderIcon(Codicon.sparkle)); + } + } else { + if (this._icon.children.length === 1) { + this._icon.removeChild(this._icon.children[0]); + } + } + + this._label.innerText = value.newSymbolName; + } + + public static getLayoutInfo({ lineHeight }: { lineHeight: number }): { totalHeight: number } { + const totalHeight = lineHeight + CandidateView._PADDING * 2 /* top & bottom padding */; + return { totalHeight }; + } + + public dispose() { } } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index ec191dfec137f..5323333e6408a 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -459,6 +459,14 @@ export function registerRenameProvider(languageSelector: LanguageSelector, provi return languageFeaturesService.renameProvider.register(languageSelector, provider); } +/** + * Register a new symbol-name provider (e.g., when a symbol is being renamed, show new possible symbol-names) + */ +export function registerNewSymbolNameProvider(languageSelector: LanguageSelector, provider: languages.NewSymbolNamesProvider): IDisposable { + const languageFeaturesService = StandaloneServices.get(ILanguageFeaturesService); + return languageFeaturesService.newSymbolNamesProvider.register(languageSelector, provider); +} + /** * Register a signature help provider (used by e.g. parameter hints). */ @@ -755,6 +763,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { setMonarchTokensProvider: setMonarchTokensProvider, registerReferenceProvider: registerReferenceProvider, registerRenameProvider: registerRenameProvider, + registerNewSymbolNameProvider: registerNewSymbolNameProvider, registerCompletionItemProvider: registerCompletionItemProvider, registerSignatureHelpProvider: registerSignatureHelpProvider, registerHoverProvider: registerHoverProvider, @@ -792,6 +801,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { InlayHintKind: standaloneEnums.InlayHintKind, InlineCompletionTriggerKind: standaloneEnums.InlineCompletionTriggerKind, CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, + NewSymbolNameTag: standaloneEnums.NewSymbolNameTag, // classes FoldingRangeKind: languages.FoldingRangeKind, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index dc6e5fb74e9e7..b1bad3efdfd88 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6331,6 +6331,11 @@ declare namespace monaco.languages { */ export function registerRenameProvider(languageSelector: LanguageSelector, provider: RenameProvider): IDisposable; + /** + * Register a new symbol-name provider (e.g., when a symbol is being renamed, show new possible symbol-names) + */ + export function registerNewSymbolNameProvider(languageSelector: LanguageSelector, provider: NewSymbolNamesProvider): IDisposable; + /** * Register a signature help provider (used by e.g. parameter hints). */ @@ -7766,8 +7771,17 @@ declare namespace monaco.languages { resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; } + export enum NewSymbolNameTag { + AIGenerated = 1 + } + + export interface NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; + } + export interface NewSymbolNamesProvider { - provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; + provideNewSymbolNames(model: editor.ITextModel, range: IRange, token: CancellationToken): ProviderResult; } export interface Command { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index b9d86e64edf91..28193f860652a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -491,7 +491,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread $registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, this._languageFeaturesService.newSymbolNamesProvider.register(selector, { - provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { + provideNewSymbolNames: (model: ITextModel, range: IRange, token: CancellationToken): Promise => { return this._proxy.$provideNewSymbolNames(handle, model.uri, range, token); } } satisfies languages.NewSymbolNamesProvider)); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index ac17b86e67cd5..a588532185d25 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1672,6 +1672,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, + NewSymbolName: extHostTypes.NewSymbolName, + NewSymbolNameTag: extHostTypes.NewSymbolNameTag, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c3382650e3e9b..b0fbf742fbc90 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2108,7 +2108,7 @@ export interface ExtHostLanguageFeaturesShape { $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; $provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise; $releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void; $provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 90bf9b204692a..4554c2a0b598b 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -3,40 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; +import { coalesce, isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; +import { raceCancellationError } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotImplementedError, isCancellationError } from 'vs/base/common/errors'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { equals, mixin } from 'vs/base/common/objects'; -import type * as vscode from 'vscode'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKind, InternalDataTransferItem, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import * as languages from 'vs/editor/common/languages'; -import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; -import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; -import * as extHostProtocol from './extHost.protocol'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; -import { IPosition } from 'vs/editor/common/core/position'; -import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; -import { isFalsyOrEmpty, isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; import { assertType, isObject } from 'vs/base/common/types'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { IPosition } from 'vs/editor/common/core/position'; +import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import * as languages from 'vs/editor/common/languages'; +import { IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; import { encodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; -import { IdGenerator } from 'vs/base/common/idGenerator'; +import { localize } from 'vs/nls'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { Cache } from './cache'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'; -import { raceCancellationError } from 'vs/base/common/async'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; -import { localize } from 'vs/nls'; -import { IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import type * as vscode from 'vscode'; +import { Cache } from './cache'; +import * as extHostProtocol from './extHost.protocol'; // --- adapter @@ -824,7 +824,7 @@ class NewSymbolNamesAdapter { private readonly _logService: ILogService ) { } - async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { + async provideNewSymbolNames(resource: URI, range: IRange, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Range.to(range); @@ -834,8 +834,11 @@ class NewSymbolNamesAdapter { if (!value) { return undefined; } - return value; - + return value.map(v => + typeof v === 'string' /* @ulugbekna: for backward compatibility because `value` used to be just `string[]` */ + ? { newSymbolName: v } + : { newSymbolName: v.newSymbolName, tags: v.tags } + ); } catch (err: unknown) { this._logService.error(NewSymbolNamesAdapter._asMessage(err) ?? JSON.stringify(err, null, '\t') /* @ulugbekna: assuming `err` doesn't have circular references that could result in an exception when converting to JSON */); return undefined; @@ -2331,7 +2334,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { + $provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise { return this._withAdapter(handle, NewSymbolNamesAdapter, adapter => adapter.provideNewSymbolNames(URI.revive(resource), range, token), undefined, token); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9122ad1b88101..d4d61dfa28ba5 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3100,6 +3100,23 @@ export class InlineValueContext implements vscode.InlineValueContext { } } +export enum NewSymbolNameTag { + AIGenerated = 1 +} + +export class NewSymbolName implements vscode.NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly vscode.NewSymbolNameTag[] | undefined; + + constructor( + newSymbolName: string, + tags?: readonly NewSymbolNameTag[] + ) { + this.newSymbolName = newSymbolName; + this.tags = tags; + } +} + //#region file api export enum FileChangeType { diff --git a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts index 5f2c994d2831d..5b1c719e23f44 100644 --- a/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts @@ -7,6 +7,17 @@ declare module 'vscode' { + export enum NewSymbolNameTag { + AIGenerated = 1 + } + + export class NewSymbolName { + readonly newSymbolName: string; + readonly tags?: readonly NewSymbolNameTag[]; + + constructor(newSymbolName: string, tags?: readonly NewSymbolNameTag[]); + } + export interface NewSymbolNamesProvider { /** * Provide possible new names for the symbol at the given range. @@ -16,7 +27,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return A list of new symbol names. */ - provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + provideNewSymbolNames(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; } export namespace languages { From 702a1ffd587b5fa1ed2fed03a77f0989eac4eb94 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 13 Feb 2024 16:15:48 -0800 Subject: [PATCH 0285/1863] Access tweaks for requestLanguageModelAccess (#205156) 1. remove the requirement that it has to be done during agent invocation 2. don't ask for auth when the model provider and the model requester are the same extension 3. since we don't have "language model activation events" start with a simple 3*2s timeout poll to wait for the language model registration to happen. (scenario: an extension activates before the extension that registers the model activates) --- .../api/browser/mainThreadChatProvider.ts | 11 ++++- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 1 - .../api/common/extHostChatAgents2.ts | 5 --- .../api/common/extHostChatProvider.ts | 42 ++++++------------- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index 801b0139627c4..d8a197db2545f 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -81,7 +82,15 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { - return this._chatProviderService.lookupChatResponseProvider(providerId); + const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); + // TODO: This should use a real activation event. Perhaps following what authentication does. + for (let i = 0; i < 3; i++) { + if (metadata) { + return metadata; + } + await timeout(2000); + } + return undefined; } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a588532185d25..2757392e5f5eb 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -208,7 +208,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService, extHostAuthentication)); - const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService, extHostCommands)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b0fbf742fbc90..b657fa8284061 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1183,7 +1183,6 @@ export interface MainThreadChatProviderShape extends IDisposable { export interface ExtHostChatProviderShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; - $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index bd233330fb22e..efaff6f31c9a5 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -16,7 +16,6 @@ import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; @@ -164,7 +163,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { constructor( mainContext: IMainContext, - private readonly _extHostChatProvider: ExtHostChatProvider, private readonly _logService: ILogService, private readonly commands: ExtHostCommands, ) { @@ -186,8 +184,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); } - this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: true }]); - // Init session disposables let sessionDisposables = this._sessionDisposables.get(request.sessionId); if (!sessionDisposables) { @@ -224,7 +220,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } finally { stream.close(); - this._extHostChatProvider.$updateAccesslist([{ extension: agent.extension.identifier, enabled: false }]); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 923a2db9edaa8..51b9c7fee3b97 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -10,7 +10,7 @@ import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProv import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; -import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; 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'; @@ -97,7 +97,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _languageModels = new Map(); private readonly _languageModelIds = new Set(); // these are ALL models, not just the one in this EH - private readonly _accesslist = new ExtensionIdentifierMap(); private readonly _modelAccessList = new ExtensionIdentifierMap(); private readonly _pendingRequest = new Map(); @@ -197,18 +196,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Array.from(this._languageModelIds); } - $updateAccesslist(data: { extension: ExtensionIdentifier; enabled: boolean }[]): void { - const updated = new ExtensionIdentifierSet(); - for (const { extension, enabled } of data) { - const oldValue = this._accesslist.get(extension); - if (oldValue !== enabled) { - this._accesslist.set(extension, enabled); - updated.add(extension); - } - } - this._onDidChangeAccess.fire(updated); - } - $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void { const updated = new Array<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); for (const { from, to, enabled } of data) { @@ -230,23 +217,15 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; - // check if the extension is in the access list and allowed to make chat requests - if (this._accesslist.get(from) === false) { - throw new Error('Extension is NOT allowed to make chat requests'); - } - const justification = options?.justification; const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, justification); if (!metadata) { - if (!this._accesslist.get(from)) { - throw new Error('Extension is NOT allowed to make chat requests'); - } throw new Error(`Language model '${languageModelId}' NOT found`); } - if (metadata.auth) { - await this._checkAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth?.providerLabel }, justification); + if (this._isUsingAuth(from, metadata)) { + await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, justification); } const that = this; @@ -256,9 +235,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return metadata.model; }, get isRevoked() { - return !that._accesslist.get(from) - || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension)) - || !that._languageModelIds.has(languageModelId); + return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); @@ -267,7 +244,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return Event.signal(Event.any(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { - if (!that._accesslist.get(from) || (metadata.auth && !that._modelAccessList.get(from)?.has(metadata.extension))) { + 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)) { @@ -297,7 +274,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _checkAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): 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 }); @@ -315,4 +292,11 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } + + private _isUsingAuth(from: ExtensionIdentifier, toMetadata: IChatResponseProviderMetadata): toMetadata is IChatResponseProviderMetadata & { auth: NonNullable } { + // If the 'to' extension uses an auth check + return !!toMetadata.auth + // And we're asking from a different extension + && !ExtensionIdentifier.equals(toMetadata.extension, from); + } } From 7f359bb293ccec5aae8e42b74805c7c4d3060ee6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 13 Feb 2024 16:18:29 -0800 Subject: [PATCH 0286/1863] debug: allow editing visualizers in debug tree (#205163) * debug: allow editing visualizers in debug tree * fix visibility lint --- .../contrib/debug/browser/baseDebugView.ts | 36 +++++--- .../contrib/debug/browser/debugHover.ts | 4 +- .../contrib/debug/browser/variablesView.ts | 50 +++++++---- .../debug/browser/watchExpressionsView.ts | 8 +- .../workbench/contrib/debug/common/debug.ts | 7 +- .../contrib/debug/common/debugModel.ts | 33 +++++-- .../contrib/debug/common/debugViewModel.ts | 19 +++- .../contrib/debug/common/debugVisualizers.ts | 90 ++++++------------- .../vscode.proposed.debugVisualization.d.ts | 2 +- 9 files changed, 132 insertions(+), 117 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index d55c8cefd4d73..ac626b82f408e 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -10,17 +10,18 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe 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'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { createSingleCallFunction } from 'vs/base/common/functional'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { IDebugService, IExpression, IExpressionValue } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, ExpressionContainer, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IDebugVisualizerService } from 'vs/workbench/contrib/debug/common/debugVisualizers'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; @@ -146,24 +147,31 @@ export interface IExpressionTemplateData { } export abstract class AbstractExpressionDataSource implements IAsyncDataSource { - constructor(@IDebugService protected debugService: IDebugService) { } + constructor( + @IDebugService protected debugService: IDebugService, + @IDebugVisualizerService protected debugVisualizer: IDebugVisualizerService, + ) { } public abstract hasChildren(element: Input | Element): boolean; - public getChildren(element: Input | Element): Promise { + public async getChildren(element: Input | Element): Promise { const vm = this.debugService.getViewModel(); - return this.doGetChildren(element).then(r => { - let dup: Element[] | undefined; - for (let i = 0; i < r.length; i++) { - const visualized = vm.getVisualizedExpression(r[i] as IExpression); - if (visualized) { - dup ||= r.slice(); - dup[i] = visualized as Element; + const children = await this.doGetChildren(element); + return Promise.all(children.map(async r => { + const vizOrTree = vm.getVisualizedExpression(r as IExpression); + if (typeof vizOrTree === 'string') { + const viz = await this.debugVisualizer.getVisualizedNodeFor(vizOrTree, r); + if (viz) { + vm.setVisualizedExpression(r, viz); + return viz as IExpression as Element; } + } else if (vizOrTree) { + return vizOrTree as Element; } - return dup || r; - }); + + return r; + })); } protected abstract doGetChildren(element: Input | Element): Promise; diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index c0e1b530b3e4d..65ba580cbd4ed 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -127,7 +127,7 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); const tip = dom.append(this.complexValueContainer, $('.tip')); tip.textContent = nls.localize({ key: 'quickTip', comment: ['"switch to editor language hover" means to show the programming language hover widget instead of the debug hover'] }, 'Hold {0} key to switch to editor language hover', isMacintosh ? 'Option' : 'Alt'); - const dataSource = new DebugHoverDataSource(this.debugService); + const dataSource = this.instantiationService.createInstance(DebugHoverDataSource); const linkeDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'DebugHover', this.treeContainer, new DebugHoverDelegate(), [ this.instantiationService.createInstance(VariablesRenderer, linkeDetector), @@ -414,7 +414,7 @@ class DebugHoverDataSource extends AbstractExpressionDataSource { + protected override doGetChildren(element: IExpression): Promise { return element.getChildren(); } } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index ef5e0cfb7d085..03c81f3ff8a3f 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -126,7 +126,7 @@ export class VariablesView extends ViewPane { new ScopesRenderer(), new ScopeErrorRenderer(), ], - new VariablesDataSource(this.debugService), { + this.instantiationService.createInstance(VariablesDataSource), { accessibilityProvider: new VariablesAccessibilityProvider(), identityProvider: { getId: (element: IExpression | IScope) => element.getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression | IScope) => e.name }, @@ -171,7 +171,7 @@ export class VariablesView extends ViewPane { let horizontalScrolling: boolean | undefined; this._register(this.debugService.getViewModel().onDidSelectExpression(e => { const variable = e?.expression; - if (variable instanceof Variable && !e?.settingWatch) { + if (variable && this.tree.hasNode(variable)) { horizontalScrolling = this.tree.options.horizontalScrolling; if (horizontalScrolling) { this.tree.updateOptions({ horizontalScrolling: false }); @@ -210,12 +210,24 @@ export class VariablesView extends ViewPane { } private onMouseDblClick(e: ITreeMouseEvent): void { - const session = this.debugService.getViewModel().focusedSession; - if (session && e.element instanceof Variable && session.capabilities.supportsSetVariable && !e.element.presentationHint?.attributes?.includes('readOnly') && !e.element.presentationHint?.lazy) { + if (this.canSetExpressionValue(e.element)) { this.debugService.getViewModel().setSelectedExpression(e.element, false); } } + private canSetExpressionValue(e: IExpression | IScope | null): e is IExpression { + const session = this.debugService.getViewModel().focusedSession; + if (!session) { + return false; + } + + if (e instanceof VisualizedExpression) { + return !!e.treeItem.canEdit; + } + + return e instanceof Variable && !e.presentationHint?.attributes?.includes('readOnly') && !e.presentationHint?.lazy; + } + private async onContextMenu(e: ITreeContextMenuEvent): Promise { const variable = e.element; if (!(variable instanceof Variable) || !variable.value) { @@ -415,7 +427,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { */ public static rendererOnVisualizationRange(model: IViewModel, tree: AsyncDataTree): IDisposable { return model.onDidChangeVisualization(({ original }) => { - if (!tree.hasElement(original)) { + if (!tree.hasNode(original)) { return; } @@ -461,24 +473,21 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { } protected override getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { - const variable = expression; + const viz = expression; return { initialValue: expression.value, ariaLabel: localize('variableValueAriaLabel', "Type new variable value"), validationOptions: { - validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null + validation: () => viz.errorMessage ? ({ content: viz.errorMessage }) : null }, onFinish: (value: string, success: boolean) => { - variable.errorMessage = undefined; - const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - if (success && variable.value !== value && focusedStackFrame) { - variable.setVariable(value, focusedStackFrame) - // Need to force watch expressions and variables to update since a variable change can have an effect on both - .then(() => { - // Do not refresh scopes due to a node limitation #15520 - forgetScopes = false; - this.debugService.getViewModel().updateViews(); - }); + viz.errorMessage = undefined; + if (success) { + viz.edit(value).then(() => { + // Do not refresh scopes due to a node limitation #15520 + forgetScopes = false; + this.debugService.getViewModel().updateViews(); + }); } } }; @@ -494,7 +503,10 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { createAndFillInContextMenuActions(menu, { arg: context, shouldForwardArgs: false }, { primary, secondary: [] }, 'inline'); if (viz.original) { - primary.push(new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.close), undefined, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined))); + const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); + action.checked = true; + primary.push(action); + actionBar.domNode.style.display = 'initial'; } actionBar.clear(); actionBar.context = context; @@ -601,7 +613,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { if (resolved.type === DebugVisualizationType.Command) { viz.execute(); } else { - const replacement = await this.visualization.setVisualizedNodeFor(resolved.id, expression); + const replacement = await this.visualization.getVisualizedNodeFor(resolved.id, expression); if (replacement) { this.debugService.getViewModel().setVisualizedExpression(expression, replacement); } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 9bdf8801c54b6..8ab1bd56d166d 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -93,7 +93,7 @@ export class WatchExpressionsView extends ViewPane { this.instantiationService.createInstance(VariablesRenderer, linkDetector), this.instantiationService.createInstance(VisualizedVariableRenderer, linkDetector), ], - new WatchExpressionsDataSource(this.debugService), { + this.instantiationService.createInstance(WatchExpressionsDataSource), { accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, keyboardNavigationLabelProvider: { @@ -157,7 +157,7 @@ export class WatchExpressionsView extends ViewPane { let horizontalScrolling: boolean | undefined; this._register(this.debugService.getViewModel().onDidSelectExpression(e => { const expression = e?.expression; - if (expression instanceof Expression || (expression instanceof Variable && e?.settingWatch)) { + if (expression && this.tree.hasElement(expression)) { horizontalScrolling = this.tree.options.horizontalScrolling; if (horizontalScrolling) { this.tree.updateOptions({ horizontalScrolling: false }); @@ -204,7 +204,7 @@ export class WatchExpressionsView extends ViewPane { const element = e.element; // double click on primitive value: open input box to be able to select and copy value. const selectedExpression = this.debugService.getViewModel().getSelectedExpression(); - if (element instanceof Expression && element !== selectedExpression?.expression) { + if ((element instanceof Expression && element !== selectedExpression?.expression) || (element instanceof VisualizedExpression && element.treeItem.canEdit)) { this.debugService.getViewModel().setSelectedExpression(element, false); } else if (!element) { // Double click in watch panel triggers to add a new watch expression @@ -259,7 +259,7 @@ class WatchExpressionsDataSource extends AbstractExpressionDataSource> { + protected override doGetChildren(element: IDebugService | IExpression): Promise> { if (isDebugService(element)) { const debugService = element as IDebugService; const watchExpressions = debugService.getModel().getWatchExpressions(); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 9dcab53e24082..039bcc1fb1083 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -634,8 +634,9 @@ export interface IViewModel extends ITreeElement { */ readonly focusedStackFrame: IStackFrame | undefined; - setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void; - getVisualizedExpression(expression: IExpression): IExpression | undefined; + setVisualizedExpression(original: IExpression, visualized: IExpression & { treeId: string } | undefined): void; + /** Returns the visualized expression if loaded, or a tree it should be visualized with, or undefined */ + getVisualizedExpression(expression: IExpression): IExpression | string | undefined; getSelectedExpression(): { expression: IExpression; settingWatch: boolean } | undefined; setSelectedExpression(expression: IExpression | undefined, settingWatch: boolean): void; updateViews(): void; @@ -1265,7 +1266,7 @@ export interface IReplOptions { export interface IDebugVisualizationContext { variable: DebugProtocol.Variable; - containerId?: string; + containerId?: number; frameId?: number; threadId: number; sessionId: string; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 373ebfa9189dd..98618a40cf4e1 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -246,9 +246,7 @@ function handleSetResponse(expression: ExpressionContainer, response: DebugProto } export class VisualizedExpression implements IExpression { - public readonly name: string; - public readonly hasChildren: boolean; - public readonly value: string; + public errorMessage?: string; private readonly id = generateUuid(); evaluateLazy(): Promise { @@ -262,15 +260,34 @@ export class VisualizedExpression implements IExpression { return this.id; } + get name() { + return this.treeItem.label; + } + + get value() { + return this.treeItem.description || ''; + } + + get hasChildren() { + return this.treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; + } + constructor( private readonly visualizer: IDebugVisualizerService, - private readonly treeId: string, + public readonly treeId: string, public readonly treeItem: IDebugVisualizationTreeItem, public readonly original?: Variable, - ) { - this.name = treeItem.label; - this.hasChildren = treeItem.collapsibleState !== DebugTreeItemCollapsibleState.None; - this.value = treeItem.description || ''; + ) { } + + /** Edits the value, sets the {@link errorMessage} and returns false if unsuccessful */ + public async edit(newValue: string) { + try { + await this.visualizer.editTreeItem(this.treeId, this.treeItem, newValue); + return true; + } catch (e) { + this.errorMessage = e.message; + return false; + } } } diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts index 9c4ee1217ab07..4b0959a97a83a 100644 --- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -24,6 +24,7 @@ export class ViewModel implements IViewModel { private readonly _onWillUpdateViews = new Emitter(); private readonly _onDidChangeVisualization = new Emitter<{ original: IExpression; replacement: IExpression }>(); private readonly visualized = new WeakMap(); + private readonly preferredVisualizers = new Map(); private expressionSelectedContextKey!: IContextKey; private loadedScriptsSupportedContextKey!: IContextKey; private stepBackSupportedContextKey!: IContextKey; @@ -165,23 +166,33 @@ export class ViewModel implements IViewModel { this.multiSessionDebug.set(isMultiSessionView); } - setVisualizedExpression(original: IExpression, visualized: IExpression | undefined): void { + setVisualizedExpression(original: IExpression, visualized: IExpression & { treeId: string } | undefined): void { const current = this.visualized.get(original) || original; - + const key = this.getPreferredVisualizedKey(original); if (visualized) { this.visualized.set(original, visualized); + this.preferredVisualizers.set(key, visualized.treeId); } else { this.visualized.delete(original); + this.preferredVisualizers.delete(key); } this._onDidChangeVisualization.fire({ original: current, replacement: visualized || original }); } - getVisualizedExpression(expression: IExpression): IExpression | undefined { - return this.visualized.get(expression); + getVisualizedExpression(expression: IExpression): IExpression | string | undefined { + return this.visualized.get(expression) || this.preferredVisualizers.get(this.getPreferredVisualizedKey(expression)); } async evaluateLazyExpression(expression: IExpressionContainer): Promise { await expression.evaluateLazy(); this._onDidEvaluateLazyExpression.fire(expression); } + + private getPreferredVisualizedKey(expr: IExpression) { + return JSON.stringify([ + expr.name, + expr.type, + !!expr.memoryReference, + ].join('\0')); + } } diff --git a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts index 2b171d5f673e5..45d19aee6f95f 100644 --- a/src/vs/workbench/contrib/debug/common/debugVisualizers.ts +++ b/src/vs/workbench/contrib/debug/common/debugVisualizers.ts @@ -79,18 +79,17 @@ export interface IDebugVisualizerService { /** * Sets that a certa tree should be used for the visualized node */ - setVisualizedNodeFor(treeId: string, expr: IExpression): Promise; + getVisualizedNodeFor(treeId: string, expr: IExpression): Promise; /** - * Gets a visualized node for the given expression if the user has preferred - * to visualize it that way. + * Gets children for a visualized tree node. */ - getVisualizedNodeFor(expr: IExpression): Promise; + getVisualizedChildren(treeId: string, treeElementId: number): Promise; /** * Gets children for a visualized tree node. */ - getVisualizedChildren(treeId: string, treeElementId: number): Promise; + editTreeItem(treeId: string, item: IDebugVisualizationTreeItem, newValue: string): Promise; } const emptyRef: IReference = { object: [], dispose: () => { } }; @@ -101,7 +100,6 @@ export class DebugVisualizerService implements IDebugVisualizerService { private readonly handles = new Map(); private readonly trees = new Map(); private readonly didActivate = new Map>(); - private readonly preferredTrees = new Map(); private registrations: { expr: ContextKeyExpression; id: string; extensionId: ExtensionIdentifier }[] = []; constructor( @@ -126,29 +124,7 @@ export class DebugVisualizerService implements IDebugVisualizerService { return emptyRef; } - const context: IDebugVisualizationContext = { - sessionId: variable.getSession()?.getId() || '', - containerId: variable.parent.getId(), - threadId, - variable: { - name: variable.name, - value: variable.value, - type: variable.type, - evaluateName: variable.evaluateName, - variablesReference: variable.reference || 0, - indexedVariables: variable.indexedVariables, - memoryReference: variable.memoryReference, - namedVariables: variable.namedVariables, - presentationHint: variable.presentationHint, - } - }; - - for (let p: IExpressionContainer = variable; p instanceof Variable; p = p.parent) { - if (p.parent instanceof Scope) { - context.frameId = p.parent.stackFrame.frameId; - } - } - + const context = this.getVariableContext(threadId, variable); const overlay = getContextForVariable(this.contextKeyService, variable, [ [CONTEXT_VARIABLE_NAME.key, variable.name], [CONTEXT_VARIABLE_VALUE.key, variable.value], @@ -205,22 +181,7 @@ export class DebugVisualizerService implements IDebugVisualizerService { } /** @inheritdoc */ - public async setVisualizedNodeFor(treeId: string, expr: IExpression): Promise { - return this.getOrSetNodeFor(expr, treeId); - } - - /** @inheritdoc */ - public async getVisualizedNodeFor(expr: IExpression): Promise { - return this.getOrSetNodeFor(expr); - } - - /** @inheritdoc */ - public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { - const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; - return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); - } - - private async getOrSetNodeFor(expr: IExpression, useTreeKey?: string): Promise { + public async getVisualizedNodeFor(treeId: string, expr: IExpression): Promise { if (!(expr instanceof Variable)) { return; } @@ -230,37 +191,42 @@ export class DebugVisualizerService implements IDebugVisualizerService { return; } - const exprPreferKey = useTreeKey || this.getPreferredTreeKey(expr); - const tree = exprPreferKey && this.trees.get(exprPreferKey); + const tree = this.trees.get(treeId); if (!tree) { return; } - const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); - if (!treeItem) { - return; - } + try { + const treeItem = await tree.getTreeItem(this.getVariableContext(threadId, expr)); + if (!treeItem) { + return; + } - if (useTreeKey) { - this.preferredTrees.set(exprPreferKey, exprPreferKey); + return new VisualizedExpression(this, treeId, treeItem, expr); + } catch (e) { + this.logService.warn('Failed to get visualized node', e); + return; } + } - return new VisualizedExpression(this, exprPreferKey, treeItem, expr); + /** @inheritdoc */ + public async getVisualizedChildren(treeId: string, treeElementId: number): Promise { + const children = await this.trees.get(treeId)?.getChildren(treeElementId) || []; + return children.map(c => new VisualizedExpression(this, treeId, c, undefined)); } - private getPreferredTreeKey(expr: Variable) { - return JSON.stringify([ - expr.name, - expr.value, - expr.type, - !!expr.memoryReference, - ].join('\0')); + /** @inheritdoc */ + public async editTreeItem(treeId: string, treeItem: IDebugVisualizationTreeItem, newValue: string): Promise { + const newItem = await this.trees.get(treeId)?.editItem?.(treeItem.id, newValue); + if (newItem) { + Object.assign(treeItem, newItem); // replace in-place so rerenders work + } } private getVariableContext(threadId: number, variable: Variable) { const context: IDebugVisualizationContext = { sessionId: variable.getSession()?.getId() || '', - containerId: variable.parent.getId(), + containerId: (variable.parent instanceof Variable ? variable.reference : undefined), threadId, variable: { name: variable.name, diff --git a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts index 0fc115f7619a7..cb12eaa00e367 100644 --- a/src/vscode-dts/vscode.proposed.debugVisualization.d.ts +++ b/src/vscode-dts/vscode.proposed.debugVisualization.d.ts @@ -150,7 +150,7 @@ declare module 'vscode' { * that came from user evaluations in the Debug Console. * @see https://microsoft.github.io/debug-adapter-protocol/specification#Types_Variable */ - containerId?: string; + containerId?: number; /** * The ID of the Debug Adapter Protocol StackFrame in which the variable was found, * for variables that came from scopes in a stack frame. From 3e097d925edc6a3096c85ef8093d2410797594e2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 16:19:23 -0800 Subject: [PATCH 0287/1863] Allow empty selections -> index. --- .../browser/controller/chat/notebookChatController.ts | 1 + .../notebook/browser/viewModel/notebookViewModelImpl.ts | 9 +-------- .../workbench/contrib/notebook/common/notebookRange.ts | 9 ++++++++- .../contrib/notebook/test/browser/notebookCommon.test.ts | 6 ++++++ .../notebook/test/browser/notebookSelection.test.ts | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b977e9fbc9222..0c4c17d60bbe0 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -354,6 +354,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } this._notebookEditor.focusContainer(true); + this._notebookEditor.setFocus({ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition }); this._notebookEditor.setSelections([{ start: this._widget.afterModelPosition, end: this._widget.afterModelPosition diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 385a37ad94883..6444aa95c45e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -361,9 +361,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._focused = focused; } - /** - * Empty selection will be turned to `null` - */ validateRange(cellRange: ICellRange | null | undefined): ICellRange | null { if (!cellRange) { return null; @@ -372,11 +369,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD const start = clamp(cellRange.start, 0, this.length); const end = clamp(cellRange.end, 0, this.length); - if (start === end) { - return null; - } - - if (start < end) { + if (start <= end) { return { start, end }; } else { return { start: end, end: start }; diff --git a/src/vs/workbench/contrib/notebook/common/notebookRange.ts b/src/vs/workbench/contrib/notebook/common/notebookRange.ts index 6760e454fa3b8..75a7a105757e3 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookRange.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookRange.ts @@ -65,7 +65,7 @@ export function reduceCellRanges(ranges: ICellRange[]): ICellRange[] { return []; } - return sorted.reduce((prev: ICellRange[], curr) => { + const reduced = sorted.reduce((prev: ICellRange[], curr) => { const last = prev[prev.length - 1]; if (last.end >= curr.start) { last.end = Math.max(last.end, curr.end); @@ -74,6 +74,13 @@ export function reduceCellRanges(ranges: ICellRange[]): ICellRange[] { } return prev; }, [first] as ICellRange[]); + + if (reduced.length > 1) { + // remove the (0, 0) range + return reduced.filter(range => !(range.start === range.end && range.start === 0)); + } + + return reduced; } export function cellRangesEqual(a: ICellRange[], b: ICellRange[]) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 476c7aa53274d..d18126e162b19 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -399,6 +399,12 @@ suite('CellRange', function () { { start: 0, end: 4 } ]); }); + + test('Reduce ranges 2, empty ranges', function () { + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 0 }, { start: 0, end: 0 }]), [{ start: 0, end: 0 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 0 }, { start: 1, end: 2 }]), [{ start: 1, end: 2 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 2, end: 2 }]), [{ start: 2, end: 2 }]); + }); }); suite('NotebookWorkingCopyTypeIdentifier', function () { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index a45fd46828f42..5bbbf50a502a7 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -278,7 +278,7 @@ suite('NotebookCellList focus/selection', () => { (editor, viewModel) => { assert.deepStrictEqual(viewModel.validateRange(null), null); assert.deepStrictEqual(viewModel.validateRange(undefined), null); - assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), null); + assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), { start: 0, end: 0 }); assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 2 }), { start: 0, end: 2 }); assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 3 }), { start: 0, end: 2 }); assert.deepStrictEqual(viewModel.validateRange({ start: -1, end: 3 }), { start: 0, end: 2 }); From 76aa3171d106b790445fbf9117594b8ce5678166 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 13 Feb 2024 17:52:05 -0800 Subject: [PATCH 0288/1863] easier validation for input document position --- .../browser/controller/chat/notebookChatController.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 0c4c17d60bbe0..58bd629421a23 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -13,7 +13,6 @@ import { Schemas } from 'vs/base/common/network'; import { MovingAverage } from 'vs/base/common/numbers'; import { StopWatch } from 'vs/base/common/stopwatch'; 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'; @@ -229,6 +228,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito } private _createWidget(index: number, input: string | undefined, autoSend: boolean | undefined) { + if (!this._notebookEditor.hasModel()) { + return; + } + // Clear the widget if it's already there this._widgetDisposableStore.clear(); @@ -253,8 +256,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito { isSimpleWidget: true } )); - const inputBoxPath = `/notebook-chat-input-${NotebookChatController.counter++}`; - const inputUri = URI.from({ scheme: Schemas.untitled, path: inputBoxPath }); + const inputBoxFragment = `notebook-chat-input-${NotebookChatController.counter++}`; + const notebookUri = this._notebookEditor.textModel.uri; + const inputUri = notebookUri.with({ scheme: Schemas.untitled, fragment: inputBoxFragment }); const result: ITextModel = this._modelService.createModel('', null, inputUri, false); fakeParentEditor.setModel(result); From 22ecb93990976d1a743c2a9ea999d0a2226bac08 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 11:51:59 +0100 Subject: [PATCH 0289/1863] remove unused event, dispose new event (#205190) --- src/vs/workbench/api/common/extHostChatProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 51b9c7fee3b97..ee4d7fe863f18 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -90,7 +90,6 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; private readonly _proxy: MainThreadChatProviderShape; - private readonly _onDidChangeAccess = new Emitter(); private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; @@ -110,7 +109,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } dispose(): void { - this._onDidChangeAccess.dispose(); + this._onDidChangeModelAccess.dispose(); this._onDidChangeProviders.dispose(); } @@ -238,10 +237,9 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { return (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) || !that._languageModelIds.has(languageModelId); }, get onDidChangeAccess() { - const onDidChangeAccess = Event.filter(that._onDidChangeAccess.event, set => set.has(from)); 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(onDidChangeAccess, onDidRemoveLM, onDidChangeModelAccess)); + return Event.signal(Event.any(onDidRemoveLM, onDidChangeModelAccess)); }, makeChatRequest(messages, options, token) { if (that._isUsingAuth(from, metadata) && !that._modelAccessList.get(from)?.has(metadata.extension)) { From 16730821dd04e574c39afd2a5ca2b553c4c11672 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 14 Feb 2024 11:38:19 +0100 Subject: [PATCH 0290/1863] observable promise utilities --- src/vs/base/common/observable.ts | 8 +- .../common/observableInternal/promise.ts} | 46 ++++++++- .../base/common/observableInternal/utils.ts | 26 ------ src/vs/base/test/common/observable.test.ts | 93 ++++++++++++++++++- .../browser/multiDiffEditorInput.ts | 3 +- 5 files changed, 144 insertions(+), 32 deletions(-) rename src/vs/{workbench/contrib/multiDiffEditor/browser/utils.ts => base/common/observableInternal/promise.ts} (62%) diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts index 033cecf010aa5..978c212d765cd 100644 --- a/src/vs/base/common/observable.ts +++ b/src/vs/base/common/observable.ts @@ -45,9 +45,15 @@ export { observableFromPromise, observableSignal, observableSignalFromEvent, - waitForState, wasEventTriggeredRecently, } from 'vs/base/common/observableInternal/utils'; +export { + ObservableLazy, + ObservableLazyStatefulPromise, + ObservablePromise, + PromiseResult, + waitForState, +} from 'vs/base/common/observableInternal/promise'; import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts b/src/vs/base/common/observableInternal/promise.ts similarity index 62% rename from src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts rename to src/vs/base/common/observableInternal/promise.ts index 26383b333ea07..23292cdea50bf 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/utils.ts +++ b/src/vs/base/common/observableInternal/promise.ts @@ -2,8 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -import { IObservable, derived, observableValue } from 'vs/base/common/observable'; +import { autorun } from 'vs/base/common/observableInternal/autorun'; +import { IObservable, observableValue } from './base'; +import { derived } from 'vs/base/common/observableInternal/derived'; export class ObservableLazy { private readonly _value = observableValue(this, undefined); @@ -97,3 +98,44 @@ export class ObservableLazyStatefulPromise { return this._lazyValue.getValue().promise; } } + +/** + * Resolves the promise when the observables state matches the predicate. + */ +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState, isError?: (state: T) => boolean | unknown | undefined): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined): Promise { + return new Promise((resolve, reject) => { + let isImmediateRun = true; + let shouldDispose = false; + const stateObs = observable.map(state => { + /** @description waitForState.state */ + return { + isFinished: predicate(state), + error: isError ? isError(state) : false, + state + }; + }); + const d = autorun(reader => { + /** @description waitForState */ + const { isFinished, error, state } = stateObs.read(reader); + if (isFinished || error) { + if (isImmediateRun) { + // The variable `d` is not initialized yet + shouldDispose = true; + } else { + d.dispose(); + } + if (error) { + reject(error === true ? state : error); + } else { + resolve(state); + } + } + }); + isImmediateRun = false; + if (shouldDispose) { + d.dispose(); + } + }); +} diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index a16feb1f65c7c..5831de89addeb 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -51,32 +51,6 @@ export function observableFromPromise(promise: Promise): IObservable<{ val return observable; } -export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { - return new Promise(resolve => { - let didRun = false; - let shouldDispose = false; - const stateObs = observable.map(state => ({ isFinished: predicate(state), state })); - const d = autorun(reader => { - /** @description waitForState */ - const { isFinished, state } = stateObs.read(reader); - if (isFinished) { - if (!didRun) { - shouldDispose = true; - } else { - d.dispose(); - } - resolve(state); - } - }); - didRun = true; - if (shouldDispose) { - d.dispose(); - } - }); -} - export function observableFromEvent( event: Event, getValue: (args: TArgs | undefined) => T diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 63b4c9c48df88..426d7d4378c9e 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; -import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved } from 'vs/base/common/observable'; +import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepObserved, waitForState } from 'vs/base/common/observable'; import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableInternal/base'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -1103,6 +1103,97 @@ suite('observables', () => { 'myObservable.lastObserverRemoved', ]); }); + + suite('waitForState', () => { + test('resolve', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + ]); + + myObservable.set({ state: 'ready' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'resolved {\"state\":\"ready\"}', + ]); + }); + + test('resolveImmediate', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'ready' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + myObservable.set({ state: 'error' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'resolved {\"state\":\"ready\"}', + ]); + }); + + test('reject', async () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', { state: 'initializing' as 'initializing' | 'ready' | 'error' }, log); + + const p = waitForState(myObservable, p => p.state === 'ready', p => p.state === 'error').then(r => { + log.log(`resolved ${JSON.stringify(r)}`); + }, (err) => { + log.log(`rejected ${JSON.stringify(err)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.firstObserverAdded', + 'myObservable.get', + ]); + + myObservable.set({ state: 'error' }, undefined); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'myObservable.set (value [object Object])', + 'myObservable.get', + 'myObservable.lastObserverRemoved', + ]); + + await p; + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'rejected {\"state\":\"error\"}' + ]); + }); + }); }); export class LoggingObserver implements IObserver { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 907fd6e15fe81..b813d41bff3d7 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { deepClone } from 'vs/base/common/objects'; -import { autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; @@ -29,7 +29,6 @@ import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorInputWithOpt import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { MultiDiffEditorIcon } from 'vs/workbench/contrib/multiDiffEditor/browser/icons.contribution'; import { ConstResolvedMultiDiffSource, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService'; -import { ObservableLazyStatefulPromise } from 'vs/workbench/contrib/multiDiffEditor/browser/utils'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { ILanguageSupport, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; From cb5ae542533b19e7a79364902d21c123c145e7ca Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 12:13:27 +0100 Subject: [PATCH 0291/1863] adding review changes --- .../browser/multiDiffEditor.ts | 12 ++++++++++ .../browser/multiDiffEditorInput.ts | 24 +++++++++++++++++-- .../browser/multiDiffSourceResolverService.ts | 8 +++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index be2fab9317566..d32ed95ec3d06 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -85,6 +85,18 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 907fd6e15fe81..f7dae8fc3e019 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -49,6 +49,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor resource.modified.resource, ); }), + input.revealResource ? new MultiDiffEditorItem( + input.revealResource.original.resource, + input.revealResource.modified.resource + ) : undefined, ); } @@ -60,7 +64,11 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor data.resources?.map(resource => new MultiDiffEditorItem( resource.originalUri ? URI.parse(resource.originalUri) : undefined, resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )) + )), + data.revealResource ? new MultiDiffEditorItem( + data.revealResource.originalUri ? URI.parse(data.revealResource.originalUri) : undefined, + data.revealResource.modifiedUri ? URI.parse(data.revealResource.modifiedUri) : undefined, + ) : undefined ); } @@ -81,6 +89,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, + public readonly initialResourceToReveal: MultiDiffEditorItem | undefined, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -106,6 +115,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor originalUri: resource.original?.toString(), modifiedUri: resource.modified?.toString(), })), + revealResource: this.initialResourceToReveal ? { + originalUri: this.initialResourceToReveal.original?.toString(), + modifiedUri: this.initialResourceToReveal.modified?.toString(), + } : undefined }; } @@ -224,7 +237,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } if (otherInput instanceof MultiDiffEditorInput) { - return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); + const initialResourcesEqual = (this.initialResourceToReveal && otherInput.initialResourceToReveal + && this.initialResourceToReveal.equals(otherInput.initialResourceToReveal)) + || (!this.initialResourceToReveal && !otherInput.initialResourceToReveal); + return (this.multiDiffSource.toString() === otherInput.multiDiffSource.toString()) && initialResourcesEqual; } return false; @@ -355,6 +371,10 @@ interface ISerializedMultiDiffEditorInput { originalUri: string | undefined; modifiedUri: string | undefined; }[] | undefined; + revealResource: { + originalUri: string | undefined; + modifiedUri: string | undefined; + } | undefined; } export class MultiDiffEditorSerializer implements IEditorSerializer { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index e78363dc2296d..bab0d9e9613b6 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -51,6 +51,14 @@ export class MultiDiffEditorItem { throw new BugIndicatingError('Invalid arguments'); } } + + equals(other: MultiDiffEditorItem): boolean { + if (this.original?.toString() === other.original?.toString() + && this.modified?.toString() === other.modified?.toString()) { + return true; + } + return false; + } } export class MultiDiffSourceResolverService implements IMultiDiffSourceResolverService { From 40c4d29a9d50e2eeeb5b55e3828ee57c9cfa1123 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:18:35 +0100 Subject: [PATCH 0292/1863] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20adopt=20Even?= =?UTF-8?q?t.runAndSubscribe=20(#205194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workbench/contrib/scm/browser/scmViewPane.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 500967073c56c..a42ecfd83742f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3119,7 +3119,7 @@ export class SCMViewPane extends ViewPane { repositoryDisposables.add(repository.input.onDidChangeVisibility(() => this.updateChildren(repository))); repositoryDisposables.add(repository.provider.onDidChangeResourceGroups(() => this.updateChildren(repository))); - const onDidChangeHistoryProvider = () => { + repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { if (!repository.provider.historyProvider) { this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - no history provider present'); return; @@ -3131,10 +3131,7 @@ export class SCMViewPane extends ViewPane { })); this.logService.debug('SCMViewPane:onDidChangeVisibleRepositories - onDidChangeCurrentHistoryItemGroup listener added'); - }; - - repositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(onDidChangeHistoryProvider)); - onDidChangeHistoryProvider(); + })); const resourceGroupDisposables = repositoryDisposables.add(new DisposableMap()); @@ -3783,7 +3780,7 @@ class SCMTreeDataSource implements IAsyncDataSource { + repositoryDisposables.add(Event.runAndSubscribe(repository.provider.onDidChangeHistoryProvider, () => { if (!repository.provider.historyProvider) { this.logService.debug('SCMTreeDataSource:onDidChangeVisibleRepositories - no history provider present'); return; @@ -3795,10 +3792,7 @@ class SCMTreeDataSource implements IAsyncDataSource Date: Wed, 14 Feb 2024 12:19:03 +0100 Subject: [PATCH 0293/1863] Chat API updates (#205184) * api - remove unused types, add jsdoc, make request handler setable (for consistency), more readonly usage https://github.com/microsoft/vscode/issues/199908 * remove ChatMessage and Role * fix a ton of compile errors... * jsdoc --- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostChatAgents2.ts | 18 ++++- .../api/common/extHostChatProvider.ts | 8 +- .../api/common/extHostTypeConverters.ts | 28 ------- src/vs/workbench/api/common/extHostTypes.ts | 18 ----- .../vscode.proposed.chatAgents2.d.ts | 79 ++++++------------- .../vscode.proposed.chatAgents2Additions.d.ts | 4 +- .../vscode.proposed.chatProvider.d.ts | 24 +----- .../vscode.proposed.chatRequestAccess.d.ts | 68 ++++++++++++++-- 9 files changed, 105 insertions(+), 146 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2757392e5f5eb..2f1aac77d56ef 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1421,7 +1421,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, - createChatAgent(name: string, handler: vscode.ChatAgentExtendedHandler) { + createChatAgent(name: string, handler: vscode.ChatAgentExtendedRequestHandler) { checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, @@ -1460,8 +1460,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, ChatAgentResultFeedbackKind: extHostTypes.ChatAgentResultFeedbackKind, - ChatMessage: extHostTypes.ChatMessage, - ChatMessageRole: extHostTypes.ChatMessageRole, ChatVariableLevel: extHostTypes.ChatVariableLevel, ChatAgentCompletionItem: extHostTypes.ChatAgentCompletionItem, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index efaff6f31c9a5..61f74812130ee 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -11,6 +11,7 @@ import { Emitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -169,7 +170,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedRequestHandler): vscode.ChatAgent2 { const handle = ExtHostChatAgents2._idPool++; const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); @@ -358,7 +359,7 @@ class ExtHostChatAgent { public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, - private readonly _callback: vscode.ChatAgentExtendedHandler, + private _requestHandler: vscode.ChatAgentExtendedRequestHandler, ) { } acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { @@ -507,6 +508,13 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, + get requestHandler() { + return that._requestHandler; + }, + set requestHandler(v) { + assertType(typeof v === 'function', 'Invalid request handler'); + that._requestHandler = v; + }, get commandProvider() { return that._commandProvider; }, @@ -585,6 +593,7 @@ class ExtHostChatAgent { return that._onDidReceiveFeedback.event; }, set agentVariableProvider(v) { + checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); that._agentVariableProvider = v; if (v) { if (!v.triggerCharacters.length) { @@ -597,13 +606,16 @@ class ExtHostChatAgent { } }, get agentVariableProvider() { + checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); return that._agentVariableProvider; }, set welcomeMessageProvider(v) { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); that._welcomeMessageProvider = v; updateMetadataSoon(); }, get welcomeMessageProvider() { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); return that._welcomeMessageProvider; }, onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') @@ -621,6 +633,6 @@ class ExtHostChatAgent { } invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { - return this._callback(request, context, response, token); + return this._requestHandler(request, context, response, token); } } diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index ee4d7fe863f18..5e725927e9b3a 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -149,13 +149,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); }); - if (data.provider.provideLanguageModelResponse2) { - return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } else { - // TODO@jrieken remove - return data.provider.provideLanguageModelResponse(messages.map(typeConvert.ChatMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); - } - + return data.provider.provideLanguageModelResponse2(messages.map(typeConvert.LanguageModelMessage.to), options, ExtensionIdentifier.toKey(from), progress, token); } //#region --- making request diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4c403c0439837..ae82d84eae3e3 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2239,12 +2239,6 @@ export namespace ChatInlineFollowup { } } -export namespace ChatMessage { - export function to(message: chatProvider.IChatMessage): vscode.ChatMessage { - return new types.ChatMessage(ChatMessageRole.to(message.role), message.content); - } -} - export namespace LanguageModelMessage { export function to(message: chatProvider.IChatMessage): vscode.LanguageModelMessage { @@ -2268,28 +2262,6 @@ export namespace LanguageModelMessage { } } - -export namespace ChatMessageRole { - - export function to(role: chatProvider.ChatMessageRole): vscode.ChatMessageRole { - switch (role) { - case chatProvider.ChatMessageRole.System: return types.ChatMessageRole.System; - case chatProvider.ChatMessageRole.User: return types.ChatMessageRole.User; - case chatProvider.ChatMessageRole.Assistant: return types.ChatMessageRole.Assistant; - } - } - - export function from(role: vscode.ChatMessageRole): chatProvider.ChatMessageRole { - switch (role) { - case types.ChatMessageRole.System: return chatProvider.ChatMessageRole.System; - case types.ChatMessageRole.Assistant: return chatProvider.ChatMessageRole.Assistant; - case types.ChatMessageRole.User: - default: - return chatProvider.ChatMessageRole.User; - } - } -} - export namespace ChatVariable { export function objectTo(variableObject: Record): Record { const result: Record = {}; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d4d61dfa28ba5..41b31a4901d9a 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4196,24 +4196,6 @@ export enum InteractiveEditorResponseFeedbackKind { Bug = 4 } -export enum ChatMessageRole { - System = 0, - User = 1, - Assistant = 2, -} - -export class ChatMessage implements vscode.ChatMessage { - - role: ChatMessageRole; - content: string; - name?: string; - - constructor(role: ChatMessageRole, content: string) { - this.role = role; - this.content = content; - } -} - export enum ChatAgentResultFeedbackKind { Unhelpful = 0, Helpful = 1, diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 8aa7e9c184fbd..ed55c5ffbf219 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -5,26 +5,6 @@ declare module 'vscode' { - /** - * One request/response pair in chat history. - */ - export interface ChatAgentHistoryEntry { - /** - * The request that was sent to the chat agent. - */ - request: ChatAgentRequest; - - /** - * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. - */ - response: ReadonlyArray; - - /** - * The result that was received from the chat agent. - */ - result: ChatAgentResult2; - } - // TODO@API name: Turn? export class ChatAgentRequestTurn { @@ -38,15 +18,10 @@ declare module 'vscode' { */ readonly prompt: string; - // TODO@API NAME agent - // TODO@API TYPE {agent:string, extension:string} - /** @deprecated */ - readonly agentId: string; - /** * The ID of the chat agent to which this request was directed. */ - readonly agent: { extensionId: string; agentId: string }; + readonly agent: { readonly extensionId: string; readonly agentId: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -74,10 +49,7 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; - /** @deprecated */ - readonly agentId: string; - - readonly agent: { extensionId: string; agentId: string }; + readonly agent: { readonly extensionId: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } @@ -189,7 +161,7 @@ declare module 'vscode' { /** * Returns a list of commands that its agent is capable of handling. A command - * can be selected by the user and will then be passed to the {@link ChatAgentHandler handler} + * can be selected by the user and will then be passed to the {@link ChatAgentRequestHandler handler} * via the {@link ChatAgentRequest.command command} property. * * @@ -197,6 +169,7 @@ declare module 'vscode' { * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ + // TODO@API Q: should we provide the current history or last results for extra context? provideCommands(token: CancellationToken): ProviderResult; } @@ -228,6 +201,7 @@ declare module 'vscode' { /** * A title to show the user, when it is different than the message. */ + // TODO@API title vs tooltip? title?: string; } @@ -243,6 +217,12 @@ declare module 'vscode' { provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; } + /** + * A chat request handler is a callback that will be invoked when a request is made to a chat agent. + */ + export type ChatAgentRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; + + export interface ChatAgent2 { /** @@ -274,6 +254,11 @@ declare module 'vscode' { dark: Uri; } | ThemeIcon; + /** + * The handler for requests to this agent. + */ + requestHandler: ChatAgentRequestHandler; + /** * This provider will be called to retrieve the agent's commands. */ @@ -284,16 +269,6 @@ declare module 'vscode' { */ followupProvider?: ChatAgentFollowupProvider; - - // TODO@API - // notify(request: ChatResponsePart, reference: string): boolean; - // BETTER - // requestResponseStream(result: ChatAgentResult, callback: (stream: ChatAgentResponseStream) => void, why?: string): void; - - // TODO@API - // clear NEVER happens - // onDidClearResult(value: TResult): void; - /** * When the user clicks this agent in `/help`, this text will be submitted to this command */ @@ -315,9 +290,9 @@ declare module 'vscode' { } export interface ChatAgentResolvedVariable { - name: string; - range: [start: number, end: number]; - values: ChatVariableValue[]; + readonly name: string; + readonly range: [start: number, end: number]; + readonly values: ChatVariableValue[]; } export interface ChatAgentRequest { @@ -330,17 +305,17 @@ declare module 'vscode' { * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} * are not part of the prompt. */ - prompt: string; + readonly prompt: string; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. */ - command?: string; + readonly command: string | undefined; /** * The list of variables that are referenced in the prompt. */ - variables: ChatAgentResolvedVariable[]; + readonly variables: readonly ChatAgentResolvedVariable[]; } export interface ChatAgentResponseStream { @@ -429,11 +404,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatAgentResponseStream; } - // TODO@API - // support ChatResponseCommandPart - // support ChatResponseTextEditPart - // support ChatResponseCodeReferencePart - // TODO@API should the name suffix differentiate between rendered items (XYZPart) // and metadata like XYZItem export class ChatResponseTextPart { @@ -482,9 +452,6 @@ declare module 'vscode' { | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - // TODO@API Remove a different type of `request` so that they can - // evolve at their own pace - export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { @@ -495,7 +462,7 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; /** * Register a variable which can be used in a chat request to any agent. diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index b934eeea9952c..32eb4999065a5 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -158,13 +158,13 @@ declare module 'vscode' { constructor(label: string | CompletionItemLabel, values: ChatVariableValue[]); } - export type ChatAgentExtendedHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; + export type ChatAgentExtendedRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { /** * Create a chat agent with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentExtendedRequestHandler): ChatAgent2; } /* diff --git a/src/vscode-dts/vscode.proposed.chatProvider.d.ts b/src/vscode-dts/vscode.proposed.chatProvider.d.ts index 7ac3ddba1b9cb..7a9e140b5a5b7 100644 --- a/src/vscode-dts/vscode.proposed.chatProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatProvider.d.ts @@ -5,27 +5,6 @@ declare module 'vscode' { - // TODO@API NAME: ChatMessageKind? - export enum ChatMessageRole { - System = 0, - User = 1, - // TODO@API name: align with ChatAgent (or whatever we'll rename that to) - Assistant = 2, - } - - /** - * A chat message that is used to make chat request against a language model. - */ - export class ChatMessage { - - readonly role: ChatMessageRole; - - readonly content: string; - - constructor(role: ChatMessageRole, content: string); - } - - export interface ChatResponseFragment { index: number; part: string; @@ -37,8 +16,7 @@ declare module 'vscode' { * Represents a large language model that accepts ChatML messages and produces a streaming response */ export interface ChatResponseProvider { - provideLanguageModelResponse(messages: ChatMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; - provideLanguageModelResponse2?(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; + provideLanguageModelResponse2(messages: LanguageModelMessage[], options: { [name: string]: any }, extensionId: string, progress: Progress, token: CancellationToken): Thenable; } export interface ChatResponseProviderMetadata { diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts index fdd1cb7f5ef98..633c28dc61ee7 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts @@ -5,35 +5,91 @@ declare module 'vscode' { + /** + * Represents a language model response. + * + * @see {@link LanguageModelAccess.makeChatRequest} + */ export interface LanguageModelResponse { /** * The overall result of the request which represents failure or success - * but _not_ the actual response or responses + * 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 */ - // TODO@API define this type! result: Thenable; - // TODO@API doc what to expect here + /** + * An async iterable that is a stream of text chunks forming the overall response. + */ stream: AsyncIterable; } - //TODO@API see https://learn.microsoft.com/en-us/dotnet/api/azure.ai.openai.chatrequestmessage?view=azure-dotnet-preview - // this allows to grow message by type, e.g add more content types to User message to support multimodal language models - + /** + * A language model message that represents a system message. + * + * System messages provide instructions to the language model that define the context in + * which user messages are interpreted. + * + * *Note* that a language model may choose to add additional system messages to the ones + * provided by extensions. + */ export class LanguageModelSystemMessage { + + /** + * The content of this message. + */ content: string; + + /** + * Create a new system message. + * + * @param content The content of the message. + */ constructor(content: string); } + /** + * A language model message that represents a user message. + */ export class LanguageModelUserMessage { + + /** + * The content of this message. + */ content: string; + + /** + * The optional name of a user for this message. + */ name: string | undefined; + + /** + * Create a new user message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ constructor(content: string, name?: string); } + /** + * 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 { + + /** + * The content of this message. + */ content: string; + + /** + * Create a new assistant message. + * + * @param content The content of the message. + */ constructor(content: string); } From 500401c8ac5afd54326a5addcdf092db3eedc311 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 14:53:50 +0100 Subject: [PATCH 0294/1863] polishing the code --- .../multiDiffEditorWidget.ts | 4 +- .../multiDiffEditorWidgetImpl.ts | 2 +- src/vs/workbench/common/editor.ts | 2 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 4 +- .../browser/multiDiffEditor.ts | 6 +-- .../browser/multiDiffEditorInput.ts | 43 ++++++++----------- .../browser/multiDiffSourceResolverService.ts | 8 ---- .../services/editor/common/editorService.ts | 1 + 8 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index e2392cc8b4a1e..8923fc2024e50 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -44,7 +44,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: { original: URI } | { modified: URI }, lineNumber: number): void { + public reveal(resource: IMultiDiffResource, lineNumber: number): void { this._widgetImpl.get().reveal(resource, lineNumber); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index b8547c7d347b7..b9a0287e63caf 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -313,7 +313,7 @@ function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalReso return 'original' in obj && obj.original instanceof URI; } -type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; +export type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 322582ad33c76..283a89b9c8576 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -507,7 +507,7 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { readonly resources?: IResourceDiffEditorInput[]; /** - * Reveal the following resource on open + * Reveal the following resource on editor open */ readonly revealResource?: IResourceDiffEditorInput; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index ff0cc489d2808..21b1e44e809f6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -11,6 +11,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider, BulkFileOperation, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -36,7 +37,6 @@ import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; -import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; const enum State { @@ -344,6 +344,8 @@ export class BulkEditPane extends ViewPane { } else { resources = await this._getResources(fileOperations); } + this._fileOperations = fileOperations; + this._resources = resources; const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index d32ed95ec3d06..4ef87792095e4 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -72,7 +72,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { + return new MultiDiffEditorItem( + resource.original.resource, + resource.modified.resource, + ); + }; const multiDiffSource = input.multiDiffSource ?? URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); return instantiationService.createInstance( MultiDiffEditorInput, multiDiffSource, input.label, - input.resources?.map(resource => { - return new MultiDiffEditorItem( - resource.original.resource, - resource.modified.resource, - ); - }), - input.revealResource ? new MultiDiffEditorItem( - input.revealResource.original.resource, - input.revealResource.modified.resource - ) : undefined, + input.resources?.map(toMultiDiffEditorItem), + input.revealResource ? toMultiDiffEditorItem(input.revealResource) : undefined, ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + const toMultiDiffEditorItem = (resource: { originalUri: string | undefined; modifiedUri: string | undefined }): MultiDiffEditorItem => { + return new MultiDiffEditorItem( + resource.originalUri ? URI.parse(resource.originalUri) : undefined, + resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + ); + }; return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), data.label, - data.resources?.map(resource => new MultiDiffEditorItem( - resource.originalUri ? URI.parse(resource.originalUri) : undefined, - resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )), - data.revealResource ? new MultiDiffEditorItem( - data.revealResource.originalUri ? URI.parse(data.revealResource.originalUri) : undefined, - data.revealResource.modifiedUri ? URI.parse(data.revealResource.modifiedUri) : undefined, - ) : undefined + data.resources?.map(toMultiDiffEditorItem), + data.revealResource ? toMultiDiffEditorItem(data.revealResource) : undefined ); } @@ -236,11 +234,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return true; } - if (otherInput instanceof MultiDiffEditorInput) { - const initialResourcesEqual = (this.initialResourceToReveal && otherInput.initialResourceToReveal - && this.initialResourceToReveal.equals(otherInput.initialResourceToReveal)) - || (!this.initialResourceToReveal && !otherInput.initialResourceToReveal); - return (this.multiDiffSource.toString() === otherInput.multiDiffSource.toString()) && initialResourcesEqual; + if (otherInput instanceof MultiDiffEditorInput && !this.initialResourceToReveal && !otherInput.initialResourceToReveal) { + return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); } return false; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts index bab0d9e9613b6..e78363dc2296d 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffSourceResolverService.ts @@ -51,14 +51,6 @@ export class MultiDiffEditorItem { throw new BugIndicatingError('Invalid arguments'); } } - - equals(other: MultiDiffEditorItem): boolean { - if (this.original?.toString() === other.original?.toString() - && this.modified?.toString() === other.modified?.toString()) { - return true; - } - return false; - } } export class MultiDiffSourceResolverService implements IMultiDiffSourceResolverService { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 19b51a652c7fa..1be2060f98c72 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -236,6 +236,7 @@ export interface IEditorService { * Open an editor in an editor group. * * @param editor the editor to open + * @param options the options to use for the editor * @param group the target group. If unspecified, the editor will open in the currently * active group. Use `SIDE_GROUP` to open the editor in a new editor group to the side * of the currently active group. From 61458114b4da140c638d7a4ceb14657149d8c3d8 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 14 Feb 2024 14:22:45 +0100 Subject: [PATCH 0295/1863] a11y: Use underlined links in HC themes --- src/vs/platform/theme/common/colorRegistry.ts | 6 ++++-- src/vs/workbench/browser/media/style.css | 5 +++++ src/vs/workbench/contrib/chat/browser/media/chat.css | 7 ------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3702702f5428b..c09485337491f 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -230,8 +230,10 @@ export const selectionBackground = registerColor('selection.background', { light // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hcDark: Color.black, hcLight: '#292929' }, nls.localize('textSeparatorForeground', "Color for text separators.")); -export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); -export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); + +export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); +export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#21A6FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); + export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hcDark: '#000000', hcLight: '#FFFFFF' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments.")); export const textPreformatBackground = registerColor('textPreformat.background', { light: '#0000001A', dark: '#FFFFFF1A', hcDark: '#FFFFFF', hcLight: '#09345f' }, nls.localize('textPreformatBackground', "Background color for preformatted text segments.")); export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#f2f2f2', dark: '#222222', hcDark: null, hcLight: '#F2F2F2' }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text.")); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 262745f0ed2cd..db4862c74276d 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -87,6 +87,11 @@ body.web { text-decoration: none; } +.monaco-workbench.hc-black p > a, +.monaco-workbench.hc-light p > a { + text-decoration: underline !important; +} + .monaco-workbench a:active { color: inherit; background-color: inherit; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 140e418f1e03f..20f6f5d621f64 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -207,13 +207,6 @@ color: var(--vscode-textPreformat-foreground); } -.hc-black .interactive-item-container .value .rendered-markdown a:hover, -.hc-black .interactive-item-container .value .rendered-markdown a:active, -.hc-light .interactive-item-container .value .rendered-markdown a:hover, -.hc-light .interactive-item-container .value .rendered-markdown a:active { - color: var(--vscode-textPreformat-foreground); -} - .interactive-list { overflow: hidden; } From ca86859b09829468917c38ebb47b294406763a42 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 14 Feb 2024 07:19:00 -0800 Subject: [PATCH 0296/1863] align contents to top (#205211) --- .../contrib/extensions/browser/extensionFeaturesTab.ts | 2 +- .../contrib/extensions/browser/media/extensionEditor.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index d09f329de49dd..35491eaa6208e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -473,7 +473,7 @@ class ExtensionFeatureView extends Disposable { return $('td', undefined, ...data.map(item => { const result: Node[] = []; if (isMarkdownString(rowData)) { - const element = $('td', undefined); + const element = $('', undefined); this.renderMarkdown(rowData, element); result.push(element); } else if (item instanceof ResolvedKeybinding) { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 4243f5ebd44a6..ce78e8c7f0276 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -616,6 +616,10 @@ padding: 2px 16px 2px 4px; } +.extension-editor > .body > .content table td { + vertical-align: top; +} + .extension-editor > .body > .content table th:last-child, .extension-editor > .body > .content table td:last-child { padding: 2px 4px; From 4d349caa60bb386e0bd7b1ad26c41c009baa62cd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 16:53:15 +0100 Subject: [PATCH 0297/1863] changes from review --- .../multiDiffEditorWidgetImpl.ts | 52 +++++++++++++------ .../browser/bracketMatching.ts | 2 +- src/vs/workbench/common/editor.ts | 5 -- .../bulkEdit/browser/preview/bulkEditPane.ts | 27 ++++++---- .../browser/multiDiffEditor.ts | 23 ++++---- .../browser/multiDiffEditorInput.ts | 39 +++++--------- 6 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index b9a0287e63caf..31cf913dfeb54 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,17 +191,47 @@ export class MultiDiffEditorWidgetImpl extends Disposable { public reveal(resource: IMultiDiffResource, lineNumber: number): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; - if (isMultiDiffOriginalResourceUri(resource)) { + if ('original' in resource) { searchCallback = (item) => item.viewModel.originalUri?.toString() === resource.original.toString(); } else { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - const scrollTopWithinItem = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + /* + // How to find the size of the current editor window? + const scrollTopViewport = this._scrollableElement.getScrollPosition().scrollTop; + const scrollBottomViewport = scrollTopViewport + this._sizeObserver.height.get(); + + const scrollTopWithinViewport = (scrollTop: number) => { + return scrollTop >= scrollTopViewport && scrollTop <= scrollBottomViewport; + } + + console.log('scrollTopViewport', scrollTopViewport); + console.log('scrollBottomViewport', scrollBottomViewport); + + let scrollTopOfResource = 0; + for (let i = 0; i < index; i++) { + scrollTopOfResource += viewItems[i].contentHeight.get() + this._spaceBetweenPx; + } + const lineHeight = this._configurationService.getValue('editor.lineHeight'); + const scrollTopOfRange = scrollTopOfResource + (range.startLineNumber - 1) * lineHeight; + const scrollBottomOfRange = scrollTopOfResource + (range.endLineNumber) * lineHeight; + + console.log('scrollTopOfRange', scrollTopOfRange); + console.log('scrollBottomOfRange', scrollBottomOfRange); + + if (scrollTopWithinViewport(scrollTopOfRange) && scrollTopWithinViewport(scrollBottomOfRange)) { + // Early return because the range is already visible + return; + } + + // The range is not visible, hence jump to the top of the top of the range + this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); + */ + // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri // following does not neccessarily correspond to the appropriate scroll top within the editor - const maxScroll = viewItems[index].template.get()?.maxScroll.get().maxScroll; - let scrollTop = (maxScroll && scrollTopWithinItem < maxScroll) ? scrollTopWithinItem : 0; + let scrollTop = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -301,19 +331,7 @@ interface IMultiDiffDocState { selections?: ISelection[]; } -interface IMultiDiffOriginalResource { - original: URI; -} - -interface IMultiDiffModifiedResource { - modified: URI; -} - -function isMultiDiffOriginalResourceUri(obj: any): obj is IMultiDiffOriginalResource { - return 'original' in obj && obj.original instanceof URI; -} - -export type IMultiDiffResource = IMultiDiffOriginalResource | IMultiDiffModifiedResource; +export type IMultiDiffResource = { original: URI } | { modified: URI }; class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index ffd9e3240dd8f..552cb7c06a3be 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -216,7 +216,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont } return new Selection(position.lineNumber, position.column, position.lineNumber, position.column); }); - + this._editor.revealLineInCenterIfOutsideViewport(); this._editor.setSelections(newSelections); this._editor.revealRange(newSelections[0]); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 283a89b9c8576..bf6785d967516 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,11 +505,6 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; - - /** - * Reveal the following resource on editor open - */ - readonly revealResource?: IResourceDiffEditorInput; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 21b1e44e809f6..f22ba75b048fe 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -324,6 +324,8 @@ export class BulkEditPane extends ViewPane { return; } const options: Mutable = { ...e.editorOptions }; + console.log('options : ', options); + // todo@aiday-mar, we may not need the following options let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; @@ -338,20 +340,18 @@ export class BulkEditPane extends ViewPane { return; } - let resources: IResourceDiffEditorInput[]; - if (this._fileOperations === fileOperations && this._resources) { - resources = this._resources; - } else { - resources = await this._getResources(fileOperations); - } - this._fileOperations = fileOperations; - this._resources = resources; - const revealResource = resources.find(r => r.original.resource!.toString() === fileElement.edit.uri.toString()); + const resources = await this._resolveResources(fileOperations); + options.viewState = { + revealData: { + resource: { original: fileElement.edit.uri }, + lineNumber: 1 + } + }; + console.log('options : ', options); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, - revealResource, resources, label, description: label, @@ -359,7 +359,10 @@ export class BulkEditPane extends ViewPane { }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } - private async _getResources(fileOperations: BulkFileOperation[]): Promise { + private async _resolveResources(fileOperations: BulkFileOperation[]): Promise { + if (this._fileOperations === fileOperations && this._resources) { + return this._resources; + } const resources: IResourceDiffEditorInput[] = []; for (const operation of fileOperations) { const operationUri = operation.uri; @@ -386,6 +389,8 @@ export class BulkEditPane extends ViewPane { }); } } + this._fileOperations = fileOperations; + this._resources = resources; return resources; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 4ef87792095e4..e047d5595b966 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -85,16 +85,19 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { - return new MultiDiffEditorItem( - resource.original.resource, - resource.modified.resource, - ); - }; const multiDiffSource = input.multiDiffSource ?? URI.parse(`multi-diff-editor:${new Date().getMilliseconds().toString() + Math.random().toString()}`); return instantiationService.createInstance( MultiDiffEditorInput, multiDiffSource, input.label, - input.resources?.map(toMultiDiffEditorItem), - input.revealResource ? toMultiDiffEditorItem(input.revealResource) : undefined, + input.resources?.map(resource => { + return new MultiDiffEditorItem( + resource.original.resource, + resource.modified.resource, + ); + }), ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { - const toMultiDiffEditorItem = (resource: { originalUri: string | undefined; modifiedUri: string | undefined }): MultiDiffEditorItem => { - return new MultiDiffEditorItem( - resource.originalUri ? URI.parse(resource.originalUri) : undefined, - resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - ); - }; return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), data.label, - data.resources?.map(toMultiDiffEditorItem), - data.revealResource ? toMultiDiffEditorItem(data.revealResource) : undefined + data.resources?.map(resource => new MultiDiffEditorItem( + resource.originalUri ? URI.parse(resource.originalUri) : undefined, + resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, + )) ); } @@ -87,7 +81,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, - public readonly initialResourceToReveal: MultiDiffEditorItem | undefined, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -113,10 +106,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor originalUri: resource.original?.toString(), modifiedUri: resource.modified?.toString(), })), - revealResource: this.initialResourceToReveal ? { - originalUri: this.initialResourceToReveal.original?.toString(), - modifiedUri: this.initialResourceToReveal.modified?.toString(), - } : undefined }; } @@ -234,7 +223,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return true; } - if (otherInput instanceof MultiDiffEditorInput && !this.initialResourceToReveal && !otherInput.initialResourceToReveal) { + if (otherInput instanceof MultiDiffEditorInput) { return this.multiDiffSource.toString() === otherInput.multiDiffSource.toString(); } @@ -366,10 +355,6 @@ interface ISerializedMultiDiffEditorInput { originalUri: string | undefined; modifiedUri: string | undefined; }[] | undefined; - revealResource: { - originalUri: string | undefined; - modifiedUri: string | undefined; - } | undefined; } export class MultiDiffEditorSerializer implements IEditorSerializer { From b3b6068187ca49a9e2f001acf6e67483e8ccf764 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 17:07:31 +0100 Subject: [PATCH 0298/1863] jsdoc and todo-update (#205201) https://github.com/microsoft/vscode/issues/199908 --- .../vscode.proposed.chatAgents2.d.ts | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index ed55c5ffbf219..1d0caf6a5a4d0 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -21,6 +21,7 @@ declare module 'vscode' { /** * The ID of the chat agent to which this request was directed. */ + // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) readonly agent: { readonly extensionId: string; readonly agentId: string }; /** @@ -49,6 +50,7 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; + // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) readonly agent: { readonly extensionId: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); @@ -289,9 +291,28 @@ declare module 'vscode' { dispose(): void; } + /** + * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. + */ export interface ChatAgentResolvedVariable { + + /** + * The name of the variable. + * + * *Note* that the name doesn't include the leading `#`-character, + * e.g `selection` for `#selection`. + */ readonly name: string; + + /** + * The start and end index of the variable in the {@link ChatAgentRequest.prompt prompt}. + * + * *Note* that the indices take the leading `#`-character into account which means they can + * used to modify the prompt as-is. + */ readonly range: [start: number, end: number]; + + // TODO@API decouple of resolve API, use `value: string | Uri | (maybe) unknown?` readonly values: ChatVariableValue[]; } @@ -313,8 +334,15 @@ declare module 'vscode' { readonly command: string | undefined; /** - * The list of variables that are referenced in the prompt. + * The list of variables and their values that are referenced in the prompt. + * + * *Note* that the prompt contains varibale references as authored and that it is up to the agent + * to further modify the prompt, for instance by inlining variable values or creating links to + * headings which contain the resolved values. vvariables are sorted in reverse by their range + * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies + * string-manipulation of the prompt. */ + // TODO@API Q? are there implicit variables that are not part of the prompt? readonly variables: readonly ChatAgentResolvedVariable[]; } @@ -435,6 +463,7 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; + // TODO@API inline constructor(value: string); } @@ -448,18 +477,21 @@ declare module 'vscode' { constructor(value: Command); } + /** + * Represents the different chat response types. + */ export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - export namespace chat { /** * Create a new {@link ChatAgent2 chat agent} instance. * - * @param name Short name by which this agent is referred to in the UI - * @param handler The reply-handler of the agent. + * @param name Short name by which the agent is referred to in the UI. The name must be unique for the extension + * contributing the agent but can collide with names from other extensions. + * @param handler A request handler for the agent. * @returns A new chat agent */ export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; From abaae7f726d29299d4125a739c0c744e015e823c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 17:23:13 +0100 Subject: [PATCH 0299/1863] adding code --- .../multiDiffEditorWidgetImpl.ts | 9 +++++++- .../browser/bracketMatching.ts | 2 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 21 +++++++------------ .../browser/multiDiffEditor.ts | 16 +++++++------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 31cf913dfeb54..6bb3d5f918512 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -224,7 +224,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { // Early return because the range is already visible return; } - // The range is not visible, hence jump to the top of the top of the range this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); */ @@ -333,6 +332,14 @@ interface IMultiDiffDocState { export type IMultiDiffResource = { original: URI } | { modified: URI }; +export function isIMultiDiffResource(obj: any): obj is IMultiDiffResource { + if (('original' in obj && obj.original instanceof URI) + || ('modified' in obj && obj.modified instanceof URI)) { + return true; + } + return false; +} + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index 552cb7c06a3be..ffd9e3240dd8f 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -216,7 +216,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont } return new Selection(position.lineNumber, position.column, position.lineNumber, position.column); }); - this._editor.revealLineInCenterIfOutsideViewport(); + this._editor.setSelections(newSelections); this._editor.revealRange(newSelections[0]); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index f22ba75b048fe..db1d7174f531f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -323,39 +323,34 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } - const options: Mutable = { ...e.editorOptions }; - console.log('options : ', options); - // todo@aiday-mar, we may not need the following options let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; - options.selection = e.element.edit.textEdit.textEdit.range; - } else if (e.element instanceof FileElement) { fileElement = e.element; - options.selection = e.element.edit.textEdits[0]?.textEdit.textEdit.range; - } else { // invalid event return; } const resources = await this._resolveResources(fileOperations); - options.viewState = { - revealData: { - resource: { original: fileElement.edit.uri }, - lineNumber: 1 + const options: Mutable = { + ...e.editorOptions, + viewState: { + revealData: { + resource: { original: fileElement.edit.uri }, + lineNumber: 1 + } } }; - console.log('options : ', options); const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, resources, label, - description: label, options, + description: label }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index e047d5595b966..1270bc7e977df 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -24,7 +24,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, IMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -93,12 +93,14 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Wed, 14 Feb 2024 17:37:50 +0100 Subject: [PATCH 0300/1863] polishing the code --- .../multiDiffEditorWidget.ts | 5 ++- .../multiDiffEditorWidgetImpl.ts | 39 ++----------------- .../browser/multiDiffEditor.ts | 5 ++- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 8923fc2024e50..a970708463467 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -44,8 +45,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, lineNumber: number): void { - this._widgetImpl.get().reveal(resource, lineNumber); + public reveal(resource: IMultiDiffResource, range: Range): void { + this._widgetImpl.get().reveal(resource, range); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 6bb3d5f918512..381e48b089fe8 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,6 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -188,7 +189,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - public reveal(resource: IMultiDiffResource, lineNumber: number): void { + // todo@aiday-mar need to reveal the range instead of just the start line number + public reveal(resource: IMultiDiffResource, range: Range): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -197,40 +199,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - /* - // How to find the size of the current editor window? - const scrollTopViewport = this._scrollableElement.getScrollPosition().scrollTop; - const scrollBottomViewport = scrollTopViewport + this._sizeObserver.height.get(); - - const scrollTopWithinViewport = (scrollTop: number) => { - return scrollTop >= scrollTopViewport && scrollTop <= scrollBottomViewport; - } - - console.log('scrollTopViewport', scrollTopViewport); - console.log('scrollBottomViewport', scrollBottomViewport); - - let scrollTopOfResource = 0; - for (let i = 0; i < index; i++) { - scrollTopOfResource += viewItems[i].contentHeight.get() + this._spaceBetweenPx; - } - const lineHeight = this._configurationService.getValue('editor.lineHeight'); - const scrollTopOfRange = scrollTopOfResource + (range.startLineNumber - 1) * lineHeight; - const scrollBottomOfRange = scrollTopOfResource + (range.endLineNumber) * lineHeight; - - console.log('scrollTopOfRange', scrollTopOfRange); - console.log('scrollBottomOfRange', scrollBottomOfRange); - - if (scrollTopWithinViewport(scrollTopOfRange) && scrollTopWithinViewport(scrollBottomOfRange)) { - // Early return because the range is already visible - return; - } - // The range is not visible, hence jump to the top of the top of the range - this._scrollableElement.setScrollPosition({ scrollTop: scrollTopOfRange }); - */ - - // todo@aiday-mar: need to find the actual scroll top given the line number specific to the original or modified uri - // following does not neccessarily correspond to the appropriate scroll top within the editor - let scrollTop = (lineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 1270bc7e977df..151f887f0e97c 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -27,6 +27,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -72,8 +73,8 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From d88ae06cede970c1e3ea216fd73346292a359f63 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 14 Feb 2024 17:50:33 +0100 Subject: [PATCH 0301/1863] IRange used --- .../widget/multiDiffEditorWidget/multiDiffEditorWidget.ts | 4 ++-- .../multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts | 4 ++-- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 3 ++- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index a970708463467..4fed15c8da2a3 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -18,7 +18,7 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -45,7 +45,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, range: IRange): void { this._widgetImpl.get().reveal(resource, range); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 381e48b089fe8..3aedbd4cc57d8 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,7 +26,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -190,7 +190,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, range: IRange): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index db1d7174f531f..913f8bdf1366c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -38,6 +38,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; +import { Range } from 'vs/editor/common/core/range'; const enum State { Data = 'data', @@ -339,7 +340,7 @@ export class BulkEditPane extends ViewPane { viewState: { revealData: { resource: { original: fileElement.edit.uri }, - lineNumber: 1 + range: new Range(1, 1, 1, 1) } } }; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 151f887f0e97c..a01863e45edb1 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -27,7 +27,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -73,7 +73,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState Date: Wed, 14 Feb 2024 10:16:07 -0800 Subject: [PATCH 0302/1863] Add api command for fetching notebook variables (#205224) --- .../api/common/extHostNotebookKernels.ts | 19 ++++++- .../notebookVariableCommands.ts | 40 ++++++++++++++ .../controller/chat/notebookChatController.ts | 53 +++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index e73b409421a52..e5b11fac0ce2b 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -12,7 +12,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookOutputDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookOutputDto, VariablesResult } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; @@ -90,7 +90,24 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { }) ], ApiCommandResult.Void); + + const requestKernelVariablesApiCommand = new ApiCommand( + 'vscode.executeNotebookVariableProvider', + '_executeNotebookVariableProvider', + 'Execute notebook variable provider', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to an array of variables', (value, apiArgs) => { + return value.map(variable => { + return { + name: variable.name, + value: variable.value, + editable: false + }; + }); + }) + ); this._commands.registerApiCommand(selectKernelApiCommand); + this._commands.registerApiCommand(requestKernelVariablesApiCommand); } createNotebookController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler?: (cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument, controller: vscode.NotebookController) => void | Thenable, preloads?: vscode.NotebookRendererScript[]): vscode.NotebookController { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts index fcdd865e65e54..03db0e250cd92 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableCommands.ts @@ -3,11 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { contextMenuArg } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; +import { INotebookKernelService, VariablesResult } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export const COPY_NOTEBOOK_VARIABLE_VALUE_ID = 'workbench.debug.viewlet.action.copyWorkspaceVariableValue'; export const COPY_NOTEBOOK_VARIABLE_VALUE_LABEL = localize('copyWorkspaceVariableValue', "Copy Value"); @@ -28,3 +32,39 @@ registerAction2(class extends Action2 { } } }); + + +registerAction2(class extends Action2 { + constructor() { + super({ + id: '_executeNotebookVariableProvider', + title: localize('executeNotebookVariableProvider', "Execute Notebook Variable Provider"), + f1: false, + }); + } + + async run(accessor: ServicesAccessor, resource: UriComponents | undefined): Promise { + if (!resource) { + return []; + } + + const uri = URI.revive(resource); + const notebookKernelService = accessor.get(INotebookKernelService); + const notebookService = accessor.get(INotebookService); + const notebookTextModel = notebookService.getNotebookTextModel(uri); + + if (!notebookTextModel) { + return []; + } + + const selectedKernel = notebookKernelService.getMatchingKernel(notebookTextModel).selected; + if (selectedKernel && selectedKernel.hasVariableProvider) { + const variables = selectedKernel.provideVariables(notebookTextModel.uri, undefined, 'named', 0, CancellationToken.None); + return await variables + .map(variable => { return variable; }) + .toPromise(); + } + + return []; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 58bd629421a23..61ac0a1a06ce9 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -176,6 +176,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _strategy: EditStrategy | undefined; private _sessionCtor: CancelablePromise | undefined; private _activeSession?: Session; + private _warmupRequestCts?: CancellationTokenSource; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; @@ -275,6 +276,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito inlineChatWidget.placeholder = localize('default.placeholder', "Ask a question"); inlineChatWidget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); widgetContainer.appendChild(inlineChatWidget.domNode); + this._widgetDisposableStore.add(inlineChatWidget.onDidChangeInput(() => { + this._warmupRequestCts?.dispose(true); + this._warmupRequestCts = undefined; + })); this._notebookEditor.changeViewZones(accessor => { const notebookViewZone = { @@ -307,6 +312,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (fakeParentEditor.hasModel()) { await this._startSession(fakeParentEditor, token); + this._warmupRequestCts = new CancellationTokenSource(); + this._startInitialFolowups(fakeParentEditor, this._warmupRequestCts.token); if (this._widget) { this._widget.inlineChatWidget.placeholder = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); @@ -368,6 +375,8 @@ export class NotebookChatController extends Disposable implements INotebookEdito async acceptInput() { assertType(this._activeSession); assertType(this._widget); + this._warmupRequestCts?.dispose(true); + this._warmupRequestCts = undefined; this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); assertType(this._activeSession.lastInput); @@ -559,6 +568,50 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._strategy = new EditStrategy(session); } + private async _startInitialFolowups(editor: IActiveCodeEditor, token: CancellationToken) { + if (!this._activeSession || !this._activeSession.provider.provideFollowups) { + return; + } + + const request: IInlineChatRequest = { + requestId: generateUuid(), + prompt: '', + attempt: 0, + selection: { selectionStartLineNumber: 1, selectionStartColumn: 1, positionLineNumber: 1, positionColumn: 1 }, + wholeRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }, + live: true, + previewDocument: editor.getModel().uri, + withIntentDetection: true + }; + + const progress = new AsyncProgress(async data => { }); + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, token); + const reply = await raceCancellationError(Promise.resolve(task), token); + if (token.isCancellationRequested) { + return; + } + + if (!reply) { + return; + } + + const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false }); + const response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), [], request.requestId); + const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, response.raw, token); + if (followups && this._widget) { + const widget = this._widget; + widget.inlineChatWidget.updateFollowUps(followups, async followup => { + if (followup.kind === 'reply') { + widget.inlineChatWidget.value = followup.message; + this.acceptInput(); + } else { + await this.acceptSession(); + this._commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); + } + }); + } + } + private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined) { assertType(this._activeSession); assertType(this._strategy); From ee47ee25bb67793e707129ed258b219419152216 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:18:38 -0600 Subject: [PATCH 0303/1863] 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 ca909ba98e0e9..537c7b8951f62 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 882baf8c26b65..ddea1c2a53b7d 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 d0205008d7464..d6ba18a272ecd 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 36db8ad2ab9cd..da56f4d4041fc 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 402f86385582e..b8c73ef1ffbd4 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 0304/1863] 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 b8c73ef1ffbd4..e522765f21322 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 0305/1863] 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 e522765f21322..49eef7120fa21 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 0353033a81166dcbcaf782bce151bb97174549b4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 14 Feb 2024 19:43:44 +0100 Subject: [PATCH 0306/1863] api - refine `agent` property of turn instances (#205231) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostChatAgents2.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 15 ++++----------- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 4 ++-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 61f74812130ee..360fdb9898a19 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -235,11 +235,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 41b31a4901d9a..6d563a0e6a92b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,28 +4257,21 @@ export class ChatResponseReferencePart { export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { - agentId: string; constructor( readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agentId: string }, - ) { - this.agentId = agent.agentId; - } + readonly agent: { extensionId: string; agent: string; agentId: string }, + ) { } } export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { - agentId: string; - constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agentId: string } - ) { - this.agentId = agent.agentId; - } + readonly agent: { extensionId: string; agent: string; agentId: string } + ) { } } export class LanguageModelSystemMessage { diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1d0caf6a5a4d0..82cdc7615f51b 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -22,7 +22,7 @@ declare module 'vscode' { * The ID of the chat agent to which this request was directed. */ // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -51,7 +51,7 @@ declare module 'vscode' { readonly result: ChatAgentResult2; // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); } From e6a59ec582fe27893f4fffebf884b9835d6e7e70 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 14 Feb 2024 10:46:04 -0800 Subject: [PATCH 0307/1863] Couple LanguageModel auth fixes (#205233) 1. use the accountLabel 2. always have our detail, and then add on a justification if provided --- .../workbench/api/browser/mainThreadChatProvider.ts | 13 +++++-------- src/vs/workbench/api/common/extHostChatProvider.ts | 11 +++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index d8a197db2545f..a3637ad0000f6 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -120,15 +120,16 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { return Disposable.None; } + const accountLabel = auth.accountLabel ?? localize('languageModelsAccountId', 'Language Models'); const disposables = new DisposableStore(); - this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, auth.accountLabel)); + this._authenticationService.registerAuthenticationProvider(authProviderId, new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, accountLabel)); disposables.add(toDisposable(() => { this._authenticationService.unregisterAuthenticationProvider(authProviderId); })); disposables.add(this._authenticationService.onDidChangeSessions(async (e) => { if (e.providerId === authProviderId) { if (e.event.removed?.length) { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); const extensionsToUpdateAccess = []; for (const allowed of allowedExtensions) { const from = await this._extensionService.getExtension(allowed.id); @@ -146,7 +147,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } })); disposables.add(this._authenticationService.onDidChangeExtensionSessionAccess(async (e) => { - const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, authProviderId); + const allowedExtensions = this._authenticationService.readAllowedExtensions(authProviderId, accountLabel); const accessList = []; for (const allowedExtension of allowedExtensions) { const from = await this._extensionService.getExtension(allowedExtension.id); @@ -174,11 +175,7 @@ class LanguageModelAccessAuthProvider implements IAuthenticationProvider { private _session: AuthenticationSession | undefined; - constructor( - readonly id: string, - readonly label: string, - private readonly _accountLabel: string = localize('languageModelsAccountId', 'Language Models') - ) { } + constructor(readonly id: string, readonly label: string, private readonly _accountLabel: string) { } async getSessions(scopes?: string[] | undefined): Promise { // If there are no scopes and no session that means no extension has requested a session yet diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 5e725927e9b3a..3f35da68ace23 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -266,17 +266,16 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { } // BIG HACK: Using AuthenticationProviders to check access to Language Models - private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, detail?: string): Promise { + private async _getAuthAccess(from: IExtensionDescription, to: { identifier: ExtensionIdentifier; displayName: string }, justification?: string): 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 { - await this._extHostAuthentication.getSession(from, providerId, [], { - forceNewSession: { - detail: detail ?? localize('chatAccess', "To allow access to the language models provided by {0}", to.displayName), - } - }); + 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'); } From 1565341d4496a71da7860467d3ea32a72103dbab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 12:50:49 -0600 Subject: [PATCH 0308/1863] 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 da56f4d4041fc..a5d73cd459a64 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 49eef7120fa21..a485dd40c4faa 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 bbd14fc377163315e92a7aadc29581219828b175 Mon Sep 17 00:00:00 2001 From: John Murray Date: Wed, 14 Feb 2024 19:03:53 +0000 Subject: [PATCH 0309/1863] Make Collapse/Expand All button of Search tree initialize correctly (fix #204316) (#205235) --- src/vs/workbench/contrib/search/browser/searchView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 654ac8d1ed2c9..649a025c5c3c1 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -901,6 +901,7 @@ export class SearchView extends ViewPane { const updateHasSomeCollapsible = () => this.toggleCollapseStateDelayer.trigger(() => this.hasSomeCollapsibleResultKey.set(this.hasSomeCollapsible())); updateHasSomeCollapsible(); this._register(this.tree.onDidChangeCollapseState(() => updateHasSomeCollapsible())); + this._register(this.tree.onDidChangeModel(() => updateHasSomeCollapsible())); this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, DEBOUNCE_DELAY, true)(options => { if (options.element instanceof Match) { From 55aca1a766b3223d5bd2fa164cbb17ff54615b9f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 13:06:47 -0600 Subject: [PATCH 0310/1863] 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 8ea1469a4b1d2..a7805bfac4684 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 a5d73cd459a64..5c105fee3533d 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 0311/1863] 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 a7805bfac4684..8ea1469a4b1d2 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 0312/1863] 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 afd920ac1f9a2..a5d30356feda6 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 8ea1469a4b1d2..5ac4cdca5ac1f 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 a3a7b55757a2b..eebaa8b3b8acb 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 0000000000000..949a2d8cc513b --- /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 5c105fee3533d..515d7b895e816 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 e9352a77392aa47f53783c0939579779b68df3e1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 14 Feb 2024 20:58:17 +0000 Subject: [PATCH 0313/1863] Enable progress messages and references from variable resolvers (#205174) * Enable progress messages and references from variable resolvers For #204539 * Fixes --- .../api/browser/mainThreadChatVariables.ts | 19 +++- .../workbench/api/common/extHost.protocol.ts | 8 +- .../api/common/extHostChatVariables.ts | 91 ++++++++++++++++--- .../contrib/chat/browser/chatFollowups.ts | 5 + .../contrib/chat/browser/chatVariables.ts | 6 +- .../browser/contrib/chatHistoryVariables.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatVariables.ts | 9 +- .../chat/test/browser/chatVariables.test.ts | 2 +- .../chat/test/common/mockChatVariables.ts | 4 +- .../vscode.proposed.chatAgents2Additions.d.ts | 41 +++++++++ 11 files changed, 162 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatVariables.ts b/src/vs/workbench/api/browser/mainThreadChatVariables.ts index 1f5e98ff8ac8b..fbdc3060424db 100644 --- a/src/vs/workbench/api/browser/mainThreadChatVariables.ts +++ b/src/vs/workbench/api/browser/mainThreadChatVariables.ts @@ -5,8 +5,8 @@ import { DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; -import { ExtHostChatVariablesShape, ExtHostContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatRequestVariableValue, IChatVariableData, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { ExtHostChatVariablesShape, ExtHostContext, IChatVariableResolverProgressDto, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChatVariables) @@ -14,6 +14,7 @@ export class MainThreadChatVariables implements MainThreadChatVariablesShape { private readonly _proxy: ExtHostChatVariablesShape; private readonly _variables = new DisposableMap(); + private readonly _pendingProgress = new Map void>(); constructor( extHostContext: IExtHostContext, @@ -27,12 +28,22 @@ export class MainThreadChatVariables implements MainThreadChatVariablesShape { } $registerVariable(handle: number, data: IChatVariableData): void { - const registration = this._chatVariablesService.registerVariable(data, async (messageText, _arg, _model, token) => { - return revive(await this._proxy.$resolveVariable(handle, messageText, token)); + const registration = this._chatVariablesService.registerVariable(data, async (messageText, _arg, model, progress, token) => { + const varRequestId = `${model.sessionId}-${handle}`; + this._pendingProgress.set(varRequestId, progress); + const result = revive(await this._proxy.$resolveVariable(handle, varRequestId, messageText, token)); + + this._pendingProgress.delete(varRequestId); + return result; }); this._variables.set(handle, registration); } + async $handleProgressChunk(requestId: string, progress: IChatVariableResolverProgressDto): Promise { + const revivedProgress = revive(progress); + this._pendingProgress.get(requestId)?.(revivedProgress as IChatVariableResolverProgress); + } + $unregisterVariable(handle: number): void { this._variables.deleteAndDispose(handle); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b657fa8284061..05ae4f752d222 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -54,7 +54,7 @@ import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentRes import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -1231,15 +1231,19 @@ export interface ExtHostChatAgentsShape2 { $releaseSession(sessionId: string): void; } +export type IChatVariableResolverProgressDto = + | Dto; + export interface MainThreadChatVariablesShape extends IDisposable { $registerVariable(handle: number, data: IChatVariableData): void; + $handleProgressChunk(requestId: string, progress: IChatVariableResolverProgressDto): Promise; $unregisterVariable(handle: number): void; } export type IChatRequestVariableValueDto = Dto; export interface ExtHostChatVariablesShape { - $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise; + $resolveVariable(handle: number, requestId: string, messageText: string, token: CancellationToken): Promise; } export interface MainThreadInlineChatShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts index a151a67432f31..ca3b265007b1f 100644 --- a/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -3,35 +3,46 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from 'vscode'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostChatVariablesShape, IMainContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostChatVariablesShape, IChatVariableResolverProgressDto, IMainContext, MainContext, MainThreadChatVariablesShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { ChatVariable } from 'vs/workbench/api/common/extHostTypeConverters'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import type * as vscode from 'vscode'; export class ExtHostChatVariables implements ExtHostChatVariablesShape { private static _idPool = 0; - private readonly _resolver = new Map(); + private readonly _resolver = new Map(); private readonly _proxy: MainThreadChatVariablesShape; constructor(mainContext: IMainContext) { this._proxy = mainContext.getProxy(MainContext.MainThreadChatVariables); } - async $resolveVariable(handle: number, messageText: string, token: CancellationToken): Promise { + async $resolveVariable(handle: number, requestId: string, messageText: string, token: CancellationToken): Promise { const item = this._resolver.get(handle); if (!item) { return undefined; } try { - const value = await item.resolver.resolve(item.data.name, { prompt: messageText }, token); - if (value) { - return value.map(ChatVariable.from); + if (item.resolver.resolve2) { + checkProposedApiEnabled(item.extension, 'chatAgents2Additions'); + const stream = new ChatVariableResolverResponseStream(requestId, this._proxy); + const value = await item.resolver.resolve2(item.data.name, { prompt: messageText }, stream.apiObject, token); + if (value) { + return value.map(typeConvert.ChatVariable.from); + } + } else { + const value = await item.resolver.resolve(item.data.name, { prompt: messageText }, token); + if (value) { + return value.map(typeConvert.ChatVariable.from); + } } } catch (err) { onUnexpectedExternalError(err); @@ -41,7 +52,7 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { registerVariableResolver(extension: IExtensionDescription, name: string, description: string, resolver: vscode.ChatVariableResolver): IDisposable { const handle = ExtHostChatVariables._idPool++; - this._resolver.set(handle, { extension: extension.identifier, data: { name, description }, resolver: resolver }); + this._resolver.set(handle, { extension, data: { name, description }, resolver: resolver }); this._proxy.$registerVariable(handle, { name, description }); return toDisposable(() => { @@ -50,3 +61,61 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { }); } } + +class ChatVariableResolverResponseStream { + + private _isClosed: boolean = false; + private _apiObject: vscode.ChatVariableResolverResponseStream | undefined; + + constructor( + private readonly _requestId: string, + private readonly _proxy: MainThreadChatVariablesShape, + ) { } + + close() { + this._isClosed = true; + } + + get apiObject() { + if (!this._apiObject) { + const that = this; + + function throwIfDone(source: Function | undefined) { + if (that._isClosed) { + const err = new Error('Response stream has been closed'); + Error.captureStackTrace(err, source); + throw err; + } + } + + const _report = (progress: IChatVariableResolverProgressDto) => { + this._proxy.$handleProgressChunk(this._requestId, progress); + }; + + this._apiObject = { + progress(value) { + throwIfDone(this.progress); + const part = new extHostTypes.ChatResponseProgressPart(value); + const dto = typeConvert.ChatResponseProgressPart.to(part); + _report(dto); + return this; + }, + reference(value) { + throwIfDone(this.reference); + const part = new extHostTypes.ChatResponseReferencePart(value); + const dto = typeConvert.ChatResponseReferencePart.to(part); + _report(dto); + return this; + }, + push(part) { + throwIfDone(this.push); + const dto = typeConvert.ChatResponsePart.to(part); + _report(dto as IChatVariableResolverProgressDto); + return this; + } + }; + } + + return this._apiObject; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index 87ab89782893f..ab3733a119288 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -37,6 +37,11 @@ export class ChatFollowups extend return; } + if (!this.chatAgentService.getDefaultAgent()) { + // No default agent yet, which affects how followups are rendered, so can't render this yet + return; + } + const tooltip = 'tooltip' in followup ? followup.tooltip : undefined; const button = this._register(new Button(container, { ...this.options, supportIcons: true, title: tooltip })); if (followup.kind === 'reply') { diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 95baa1d92da26..147b267245cce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -13,7 +13,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatDynamicVariableModel } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest, ChatRequestVariablePart, ChatRequestDynamicVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariablesService, IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IDynamicVariable, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; interface IChatData { data: IChatVariableData; @@ -30,7 +30,7 @@ export class ChatVariablesService implements IChatVariablesService { ) { } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise { let resolvedVariables: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }[] = []; const jobs: Promise[] = []; @@ -39,7 +39,7 @@ export class ChatVariablesService implements IChatVariablesService { if (part instanceof ChatRequestVariablePart) { const data = this._resolver.get(part.variableName.toLowerCase()); if (data) { - jobs.push(data.resolver(prompt.text, part.variableArg, model, token).then(values => { + jobs.push(data.resolver(prompt.text, part.variableArg, model, progress, token).then(values => { if (values?.length) { resolvedVariables[i] = { name: part.variableName, range: part.range, values }; } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts index 5aa6815b69843..5df9a406f5be2 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables.ts @@ -15,7 +15,7 @@ class ChatHistoryVariables extends Disposable { ) { super(); - this._register(chatVariablesService.registerVariable({ name: 'response', description: '', canTakeArgument: true, hidden: true }, async (message, arg, model, token) => { + this._register(chatVariablesService.registerVariable({ name: 'response', description: '', canTakeArgument: true, hidden: true }, async (message, arg, model, progress, token) => { if (!arg) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 7197f521f065e..fe8185212e411 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -551,7 +551,7 @@ export class ChatService extends Disposable implements IChatService { const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); - const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, model, progressCallback, token); request.variableData = variableData; const promptTextResult = getPromptText(request.message); diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index f95b9da120e79..859daf171e5d8 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -10,6 +10,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatContentReference, IChatProgressMessage } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatVariableData { name: string; @@ -25,9 +26,13 @@ export interface IChatRequestVariableValue { description?: string; } +export type IChatVariableResolverProgress = + | IChatContentReference + | IChatProgressMessage; + export interface IChatVariableResolver { // TODO should we spec "zoom level" - (messageText: string, arg: string | undefined, model: IChatModel, token: CancellationToken): Promise; + (messageText: string, arg: string | undefined, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise; } export const IChatVariablesService = createDecorator('IChatVariablesService'); @@ -42,7 +47,7 @@ export interface IChatVariablesService { /** * Resolves all variables that occur in `prompt` */ - resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise; + resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise; } export interface IDynamicVariable { diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index 7b9bbde63c78d..f835af908b4e1 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -42,7 +42,7 @@ suite('ChatVariables', function () { const resolveVariables = async (text: string) => { const result = parser.parseChatRequest('1', text); - return await service.resolveVariables(result, null!, CancellationToken.None); + return await service.resolveVariables(result, null!, () => { }, CancellationToken.None); }; { diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts index 7522025433650..4e34de6d72a70 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatVariables.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IChatModel, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatVariableData, IChatVariableResolver, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { IChatVariableData, IChatVariableResolver, IChatVariableResolverProgress, IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; export class MockChatVariablesService implements IChatVariablesService { _serviceBrand: undefined; @@ -27,7 +27,7 @@ export class MockChatVariablesService implements IChatVariablesService { return []; } - async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise { + async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise { return { variables: [] }; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts index 32eb4999065a5..40955904331c7 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts @@ -244,4 +244,45 @@ declare module 'vscode' { placeholder?: string; }; } + + export interface ChatVariableResolverResponseStream { + /** + * Push a progress part to this stream. Short-hand for + * `push(new ChatResponseProgressPart(value))`. + * + * @param value + * @returns This stream. + */ + progress(value: string): ChatVariableResolverResponseStream; + + /** + * Push a reference to this stream. Short-hand for + * `push(new ChatResponseReferencePart(value))`. + * + * *Note* that the reference is not rendered inline with the response. + * + * @param value A uri or location + * @returns This stream. + */ + reference(value: Uri | Location): ChatVariableResolverResponseStream; + + /** + * Pushes a part to this stream. + * + * @param part A response part, rendered or metadata + */ + push(part: ChatVariableResolverResponsePart): ChatVariableResolverResponseStream; + } + + export type ChatVariableResolverResponsePart = ChatResponseProgressPart | ChatResponseReferencePart; + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult; + } } From d881ddbae52bec5cdc5e87351d6c3f8f7fa5c67f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 14 Feb 2024 15:30:36 -0600 Subject: [PATCH 0314/1863] 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 a5d30356feda6..bba5a1f648ee1 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 85033c6416685..02cf92cfa93b6 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 5ac4cdca5ac1f..8ea1469a4b1d2 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 eebaa8b3b8acb..70804842c3eaa 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 ddea1c2a53b7d..f75c9c3d9a7da 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 0000000000000..5bbe76db85f29 --- /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 949a2d8cc513b..8c146698482ef 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 d6ba18a272ecd..641737229dcb2 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 55222fabf2a78be3699958314db20b1ed8e9991b Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 14 Feb 2024 13:47:56 -0800 Subject: [PATCH 0315/1863] bump distro for https://github.com/microsoft/vscode-distro/commit/660e1818ca4ddccb41c09b6a44360c278ae1610e --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 488e3cd2c58ec..dcd8d8916b33f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "cee7e51a2bc638aa4fe4bc62668aec6d46ac94dc", + "distro": "660e1818ca4ddccb41c09b6a44360c278ae1610e", "author": { "name": "Microsoft Corporation" }, From d16c5304a07acc6158e3e1e6c52e61d373f1d80d Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Wed, 14 Feb 2024 16:08:50 -0800 Subject: [PATCH 0316/1863] enable variable view for IW (#205249) --- .../notebookVariables/notebookVariables.ts | 39 +++++++++++-------- .../notebookVariablesView.ts | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts index 9e7435fce8870..933c67cea811c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariables.ts @@ -3,23 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewsRegistry } from 'vs/workbench/common/views'; import { VIEWLET_ID as debugContainerId } from 'vs/workbench/contrib/debug/common/debug'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; import { NotebookVariablesView } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { variablesViewIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { NOTEBOOK_VARIABLE_VIEW_ENABLED } from 'vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariableContextKeys'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class NotebookVariables extends Disposable implements IWorkbenchContribution { private listeners: IDisposable[] = []; @@ -33,14 +35,15 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @INotebookExecutionStateService private readonly notebookExecutionStateService: INotebookExecutionStateService, - @INotebookKernelService private readonly notebookKernelService: INotebookKernelService + @INotebookKernelService private readonly notebookKernelService: INotebookKernelService, + @INotebookService private readonly notebookDocumentService: INotebookService ) { super(); this.viewEnabled = NOTEBOOK_VARIABLE_VIEW_ENABLED.bindTo(contextKeyService); this.listeners.push(this.editorService.onDidActiveEditorChange(() => this.handleInitEvent())); - this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent())); + this.listeners.push(this.notebookExecutionStateService.onDidChangeExecution((e) => this.handleInitEvent(e.notebook))); this.configListener = configurationService.onDidChangeConfiguration((e) => this.handleConfigChange(e)); } @@ -57,11 +60,11 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut } } - private handleInitEvent() { + private handleInitEvent(notebook?: URI) { if (this.configurationService.getValue(NotebookSetting.notebookVariablesView) - && this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook') { + && (!!notebook || this.editorService.activeEditorPane?.getId() === 'workbench.editor.notebook')) { - if (this.hasVariableProvider() && !this.initialized && this.initializeView()) { + if (this.hasVariableProvider(notebook) && !this.initialized && this.initializeView()) { this.viewEnabled.set(true); this.initialized = true; this.listeners.forEach(listener => listener.dispose()); @@ -69,9 +72,11 @@ export class NotebookVariables extends Disposable implements IWorkbenchContribut } } - private hasVariableProvider() { - const notebookDocument = getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; - return notebookDocument && this.notebookKernelService.getMatchingKernel(notebookDocument).selected?.hasVariableProvider; + private hasVariableProvider(notebookUri?: URI) { + const notebook = notebookUri ? + this.notebookDocumentService.getNotebookTextModel(notebookUri) : + getNotebookEditorFromEditorPane(this.editorService.activeEditorPane)?.getViewModel()?.notebookDocument; + return notebook && this.notebookKernelService.getMatchingKernel(notebook).selected?.hasVariableProvider; } private initializeView() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index 515a43f8cf5ae..df59bbeb0442d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -140,7 +140,7 @@ export class NotebookVariablesView extends ViewPane { private setActiveNotebook() { const current = this.activeNotebook; const activeEditorPane = this.editorService.activeEditorPane; - if (activeEditorPane?.getId() === 'workbench.editor.notebook') { + if (activeEditorPane?.getId() === 'workbench.editor.notebook' || activeEditorPane?.getId() === 'workbench.editor.interactive') { const notebookDocument = getNotebookEditorFromEditorPane(activeEditorPane)?.getViewModel()?.notebookDocument; this.activeNotebook = notebookDocument; } From e587755905208e47725c5196539c4ca898255fe6 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 14 Feb 2024 16:30:07 -0800 Subject: [PATCH 0317/1863] Expose notebook variable type (#205250) --- src/vs/workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostNotebookKernels.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 05ae4f752d222..b8b4266b5e113 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1119,6 +1119,7 @@ export interface VariablesResult { id: number; name: string; value: string; + type?: string; hasNamedChildren: boolean; indexedChildrenCount: number; extensionId: string; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index e5b11fac0ce2b..f2a201e9e06e8 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -101,6 +101,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { return { name: variable.name, value: variable.value, + type: variable.type, editable: false }; }); From 3ce7ccff4116da34370cecfb329420f266499ec4 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 14 Feb 2024 21:36:53 -0800 Subject: [PATCH 0318/1863] debug: initial support of breakpoint modes (#205251) This supports breakpoint modes on exception, instruction, and source breakpoints. It doesn't yet do data breakpoints since I was having trouble figuring out a good user flow for that. You can test this on the `connor4312/breakpoint-modes` branch of mock-debug, which I'll merge in after the next DAP release. ![](https://memes.peet.io/img/24-02-68cd0222-8ef5-4d39-aa51-81ba5b8c2405.png) --- .../api/browser/mainThreadDebugService.ts | 7 +- .../workbench/api/common/extHost.protocol.ts | 4 + .../api/common/extHostDebugService.ts | 12 +- src/vs/workbench/api/common/extHostTypes.ts | 18 +- .../contrib/debug/browser/breakpointWidget.ts | 53 ++- .../contrib/debug/browser/breakpointsView.ts | 114 ++++- .../debug/browser/debugEditorActions.ts | 2 +- .../contrib/debug/browser/debugService.ts | 18 +- .../contrib/debug/browser/debugSession.ts | 9 +- .../contrib/debug/browser/disassemblyView.ts | 2 +- .../debug/browser/media/breakpointWidget.css | 93 ++-- .../contrib/debug/browser/variablesView.ts | 6 +- .../workbench/contrib/debug/common/debug.ts | 32 +- .../contrib/debug/common/debugModel.ts | 411 ++++++++++++------ .../contrib/debug/common/debugProtocol.d.ts | 194 ++++++--- .../contrib/debug/common/debugStorage.ts | 17 +- .../debug/test/browser/breakpoints.test.ts | 10 +- .../debug/test/common/debugModel.test.ts | 13 +- .../contrib/debug/test/common/mockDebug.ts | 3 +- 19 files changed, 734 insertions(+), 284 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 1b383b49d75fb..3178df6d09304 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -217,14 +217,15 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb column: l.character > 0 ? l.character + 1 : undefined, // a column value of 0 results in an omitted column attribute; see #46784 condition: l.condition, hitCondition: l.hitCondition, - logMessage: l.logMessage + logMessage: l.logMessage, + mode: l.mode, } ); this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps); } else if (dto.type === 'function') { - this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); + this.debugService.addFunctionBreakpoint(dto.functionName, dto.id, dto.mode); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType); + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType, dto.mode); } } return Promise.resolve(); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b8b4266b5e113..34e3f6664b35d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2277,11 +2277,13 @@ export interface IBreakpointDto { condition?: string; hitCondition?: string; logMessage?: string; + mode?: string; } export interface IFunctionBreakpointDto extends IBreakpointDto { type: 'function'; functionName: string; + mode?: string; } export interface IDataBreakpointDto extends IBreakpointDto { @@ -2291,6 +2293,7 @@ export interface IDataBreakpointDto extends IBreakpointDto { label: string; accessTypes?: DebugProtocol.DataBreakpointAccessType[]; accessType: DebugProtocol.DataBreakpointAccessType; + mode?: string; } export interface ISourceBreakpointDto extends IBreakpointDto { @@ -2317,6 +2320,7 @@ export interface ISourceMultiBreakpointDto { logMessage?: string; line: number; character: number; + mode?: string; }[]; } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 133ff216767ee..38d5f2a320521 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -427,7 +427,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E hitCondition: bp.hitCondition, logMessage: bp.logMessage, line: bp.location.range.start.line, - character: bp.location.range.start.character + character: bp.location.range.start.character, + mode: bp.mode, }); } else if (bp instanceof FunctionBreakpoint) { dtos.push({ @@ -437,7 +438,8 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E hitCondition: bp.hitCondition, logMessage: bp.logMessage, condition: bp.condition, - functionName: bp.functionName + functionName: bp.functionName, + mode: bp.mode, }); } } @@ -713,12 +715,12 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E if (id && !this._breakpoints.has(id)) { let bp: Breakpoint; if (bpd.type === 'function') { - bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage, bpd.mode); } else if (bpd.type === 'data') { - bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); + bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage, bpd.mode); } else { const uri = URI.revive(bpd.uri); - bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage, bpd.mode); } setBreakpointId(bp, id); this._breakpoints.set(id, bp); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6d563a0e6a92b..d6c3996105eec 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2918,8 +2918,9 @@ export class Breakpoint { readonly condition?: string; readonly hitCondition?: string; readonly logMessage?: string; + readonly mode?: string; - protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { this.enabled = typeof enabled === 'boolean' ? enabled : true; if (typeof condition === 'string') { this.condition = condition; @@ -2930,6 +2931,9 @@ export class Breakpoint { if (typeof logMessage === 'string') { this.logMessage = logMessage; } + if (typeof mode === 'string') { + this.mode = mode; + } } get id(): string { @@ -2944,8 +2948,8 @@ export class Breakpoint { export class SourceBreakpoint extends Breakpoint { readonly location: Location; - constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); if (location === null) { throw illegalArgument('location'); } @@ -2957,8 +2961,8 @@ export class SourceBreakpoint extends Breakpoint { export class FunctionBreakpoint extends Breakpoint { readonly functionName: string; - constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); this.functionName = functionName; } } @@ -2969,8 +2973,8 @@ export class DataBreakpoint extends Breakpoint { readonly dataId: string; readonly canPersist: boolean; - constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { - super(enabled, condition, hitCondition, logMessage); + constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, mode?: string) { + super(enabled, condition, hitCondition, logMessage, mode); if (!dataId) { throw illegalArgument('dataId'); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 877c0921a4c71..26ae95ccf1d06 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -85,10 +85,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private selectBreakpointContainer!: HTMLElement; private input!: IActiveCodeEditor; private selectBreakpointBox!: SelectBox; + private selectModeBox?: SelectBox; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; private logMessageInput = ''; + private modeInput?: DebugProtocol.BreakpointMode; private breakpoint: IBreakpoint | undefined; private context: Context; private heightInPx: number | undefined; @@ -216,6 +218,8 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.updateContextInput(); }); + this.createModesInput(container); + this.inputContainer = $('.inputContainer'); this.createBreakpointInput(dom.append(container, this.inputContainer)); @@ -232,6 +236,33 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi setTimeout(() => this.focusInput(), 150); } + private createModesInput(container: HTMLElement) { + const modes = this.debugService.getModel().getBreakpointModes('source'); + if (modes.length <= 1) { + return; + } + + const sb = this.selectModeBox = new SelectBox( + [ + { text: nls.localize('bpMode', 'Mode'), isDisabled: true }, + ...modes.map(mode => ({ text: mode.label, description: mode.description })), + ], + modes.findIndex(m => m.mode === this.breakpoint?.mode) + 1, + this.contextViewService, + defaultSelectBoxStyles, + ); + this.toDispose.push(sb); + this.toDispose.push(sb.onDidSelect(e => { + this.modeInput = modes[e.index - 1]; + })); + + const modeWrapper = $('.select-mode-container'); + const selectionWrapper = $('.select-box-container'); + dom.append(modeWrapper, selectionWrapper); + sb.render(selectionWrapper); + dom.append(container, modeWrapper); + } + private createTriggerBreakpointInput(container: HTMLElement) { const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); @@ -404,10 +435,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (success) { // if there is already a breakpoint on this location - remove it. - let condition = this.breakpoint && this.breakpoint.condition; - let hitCondition = this.breakpoint && this.breakpoint.hitCondition; - let logMessage = this.breakpoint && this.breakpoint.logMessage; - let triggeredBy = this.breakpoint && this.breakpoint.triggeredBy; + let condition = this.breakpoint?.condition; + let hitCondition = this.breakpoint?.hitCondition; + let logMessage = this.breakpoint?.logMessage; + let triggeredBy = this.breakpoint?.triggeredBy; + let mode = this.breakpoint?.mode; + let modeLabel = this.breakpoint?.modeLabel; this.rememberInput(); @@ -420,6 +453,10 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { logMessage = this.logMessageInput; } + if (this.selectModeBox) { + mode = this.modeInput?.mode; + modeLabel = this.modeInput?.label; + } if (this.context === Context.TRIGGER_POINT) { // currently, trigger points don't support additional conditions: condition = undefined; @@ -434,7 +471,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi condition, hitCondition, logMessage, - triggeredBy + triggeredBy, + mode, + modeLabel, }); this.debugService.updateBreakpoints(this.breakpoint.originalUri, data, false).then(undefined, onUnexpectedError); } else { @@ -447,7 +486,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi condition, hitCondition, logMessage, - triggeredBy + triggeredBy, + mode, + modeLabel, }]); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 5222b57d2e748..e60c8079a1418 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -39,6 +39,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -48,7 +49,7 @@ import { IEditorPane } from 'vs/workbench/common/editor'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { DisassemblyView } from 'vs/workbench/contrib/debug/browser/disassemblyView'; -import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; +import { BREAKPOINTS_VIEW_ID, BREAKPOINT_EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_HAS_MODES, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_BREAKPOINT_ITEM_TYPE, CONTEXT_BREAKPOINT_SUPPORTS_CONDITION, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_IN_DEBUG_MODE, DEBUG_SCHEME, DebuggerString, IBaseBreakpoint, IBreakpoint, IBreakpointEditorContribution, IBreakpointUpdateData, IDataBreakpoint, IDebugModel, IDebugService, IEnablement, IExceptionBreakpoint, IFunctionBreakpoint, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; import { Breakpoint, DataBreakpoint, ExceptionBreakpoint, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -84,6 +85,7 @@ export class BreakpointsView extends ViewPane { private ignoreLayout = false; private menu: IMenu; private breakpointItemType: IContextKey; + private breakpointHasMultipleModes: IContextKey; private breakpointSupportsCondition: IContextKey; private _inputBoxData: InputBoxData | undefined; breakpointInputFocused: IContextKey; @@ -116,6 +118,7 @@ export class BreakpointsView extends ViewPane { this.menu = menuService.createMenu(MenuId.DebugBreakpointsContext, contextKeyService); this._register(this.menu); this.breakpointItemType = CONTEXT_BREAKPOINT_ITEM_TYPE.bindTo(contextKeyService); + this.breakpointHasMultipleModes = CONTEXT_BREAKPOINT_HAS_MODES.bindTo(contextKeyService); this.breakpointSupportsCondition = CONTEXT_BREAKPOINT_SUPPORTS_CONDITION.bindTo(contextKeyService); this.breakpointInputFocused = CONTEXT_BREAKPOINT_INPUT_FOCUSED.bindTo(contextKeyService); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); @@ -132,12 +135,12 @@ export class BreakpointsView extends ViewPane { const delegate = new BreakpointsDelegate(this); this.list = this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ - this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), - new ExceptionBreakpointsRenderer(this.menu, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService), + this.instantiationService.createInstance(BreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType), + new ExceptionBreakpointsRenderer(this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType, this.debugService), new ExceptionBreakpointInputRenderer(this, this.debugService, this.contextViewService), this.instantiationService.createInstance(FunctionBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), new FunctionBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), - this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointSupportsCondition, this.breakpointItemType), + this.instantiationService.createInstance(DataBreakpointsRenderer, this.menu, this.breakpointHasMultipleModes, this.breakpointSupportsCondition, this.breakpointItemType), new DataBreakpointInputRenderer(this, this.debugService, this.contextViewService, this.labelService), this.instantiationService.createInstance(InstructionBreakpointsRenderer), ], { @@ -426,6 +429,7 @@ interface IBaseBreakpointTemplateData { context: BreakpointItem; actionBar: ActionBar; toDispose: IDisposable[]; + badge: HTMLElement; } interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData { @@ -433,7 +437,6 @@ interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateDat } interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData { - lineNumber: HTMLElement; filePath: HTMLElement; } @@ -486,6 +489,7 @@ class BreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, @IDebugService private readonly debugService: IDebugService, @@ -521,8 +525,8 @@ class BreakpointsRenderer implements IListRenderer 1); createAndFillInActionBarActions(this.menu, { arg: breakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); @@ -567,6 +576,7 @@ class ExceptionBreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, private debugService: IDebugService @@ -598,6 +608,9 @@ class ExceptionBreakpointsRenderer implements IListRenderer 1); createAndFillInActionBarActions(this.menu, { arg: exceptionBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); @@ -661,6 +682,8 @@ class FunctionBreakpointsRenderer implements IListRenderer, private breakpointSupportsCondition: IContextKey, private breakpointItemType: IContextKey, @IDebugService private readonly debugService: IDebugService, @@ -738,6 +769,8 @@ class DataBreakpointsRenderer implements IListRenderer 1); this.breakpointItemType.set('dataBreakpoint'); createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); data.actionBar.clear(); @@ -817,6 +858,8 @@ class InstructionBreakpointsRenderer implements IListRenderer { view.renderInputBox({ breakpoint, type: 'hitCount' }); } }); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'debug.editBreakpointMode', + viewId: BREAKPOINTS_VIEW_ID, + title: localize('editMode', "Edit Mode..."), + menu: [{ + id: MenuId.DebugBreakpointsContext, + group: 'navigation', + order: 20, + when: ContextKeyExpr.and( + CONTEXT_BREAKPOINT_HAS_MODES, + ContextKeyExpr.or(CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('breakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('exceptionBreakpoint'), CONTEXT_BREAKPOINT_ITEM_TYPE.isEqualTo('instructionBreakpoint')) + ) + }] + }); + } + + async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: IBreakpoint) { + const kind = breakpoint instanceof Breakpoint ? 'source' : breakpoint instanceof InstructionBreakpoint ? 'instruction' : 'exception'; + const debugService = accessor.get(IDebugService); + const modes = debugService.getModel().getBreakpointModes(kind); + const picked = await accessor.get(IQuickInputService).pick( + modes.map(mode => ({ label: mode.label, description: mode.description, mode: mode.mode })), + { placeHolder: localize('selectBreakpointMode', "Select Breakpoint Mode") } + ); + + if (!picked) { + return; + } + + if (kind === 'source') { + const data = new Map(); + data.set(breakpoint.getId(), { mode: picked.mode, modeLabel: picked.label }); + debugService.updateBreakpoints(breakpoint.originalUri, data, false); + } else if (breakpoint instanceof InstructionBreakpoint) { + debugService.removeInstructionBreakpoints(breakpoint.instructionReference, breakpoint.offset); + debugService.addInstructionBreakpoint({ ...breakpoint.toJSON(), mode: picked.mode, modeLabel: picked.label }); + } else if (breakpoint instanceof ExceptionBreakpoint) { + breakpoint.mode = picked.mode; + breakpoint.modeLabel = picked.label; + debugService.setExceptionBreakpointCondition(breakpoint, breakpoint.condition); // no-op to trigger a re-send + } + } +}); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 50a0b86f6b0e1..2dd7760e49207 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -67,7 +67,7 @@ class ToggleBreakpointAction extends Action2 { if (toRemove) { debugService.removeInstructionBreakpoints(toRemove.instructionReference, toRemove.offset); } else { - debugService.addInstructionBreakpoint(location.reference, location.offset, location.address); + debugService.addInstructionBreakpoint({ instructionReference: location.reference, offset: location.offset, address: location.address, canPersist: false }); } } return; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 73750df8fb6f3..5478398dfb671 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -44,7 +44,7 @@ import { DebugTaskRunner, TaskRunResult } from 'vs/workbench/contrib/debug/brows import { CALLSTACK_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, CONTEXT_HAS_DEBUGGED, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_UX, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_MODE, debuggerDisabledMessage, DEBUG_MEMORY_SCHEME, getStateLabel, IAdapterManager, IBreakpoint, IBreakpointData, ICompound, IConfig, IConfigurationManager, IDebugConfiguration, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEnablement, IExceptionBreakpoint, IGlobalConfig, ILaunch, IStackFrame, IThread, IViewModel, REPL_VIEW_ID, State, VIEWLET_ID, DEBUG_SCHEME, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; -import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Breakpoint, DataBreakpoint, DebugModel, FunctionBreakpoint, IInstructionBreakpointOptions, InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; import { DebugTelemetry } from 'vs/workbench/contrib/debug/common/debugTelemetry'; @@ -1065,8 +1065,8 @@ export class DebugService implements IDebugService { return this.sendAllBreakpoints(); } - addFunctionBreakpoint(name?: string, id?: string): void { - this.model.addFunctionBreakpoint(name || '', id); + addFunctionBreakpoint(name?: string, id?: string, mode?: string): void { + this.model.addFunctionBreakpoint(name || '', id, mode); } async updateFunctionBreakpoint(id: string, update: { name?: string; hitCondition?: string; condition?: string }): Promise { @@ -1081,8 +1081,8 @@ export class DebugService implements IDebugService { await this.sendFunctionBreakpoints(); } - async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise { - this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes, accessType); + async addDataBreakpoint(description: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise { + this.model.addDataBreakpoint({ description, dataId, canPersist, accessTypes, accessType, mode }); this.debugStorage.storeBreakpoints(this.model); await this.sendDataBreakpoints(); this.debugStorage.storeBreakpoints(this.model); @@ -1100,8 +1100,8 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } - async addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { - this.model.addInstructionBreakpoint(instructionReference, offset, address, condition, hitCondition); + async addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise { + this.model.addInstructionBreakpoint(opts); this.debugStorage.storeBreakpoints(this.model); await this.sendInstructionBreakpoints(); this.debugStorage.storeBreakpoints(this.model); @@ -1118,8 +1118,8 @@ export class DebugService implements IDebugService { this.debugStorage.storeBreakpoints(this.model); } - setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { - this.model.setExceptionBreakpointsForSession(session.getId(), data); + setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void { + this.model.setExceptionBreakpointsForSession(session.getId(), filters); this.debugStorage.storeBreakpoints(this.model); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index b7502e41c7417..861135c6f6a8a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -344,6 +344,7 @@ export class DebugSession implements IDebugSession, IDisposable { this.initialized = true; this._onDidChangeState.fire(); this.debugService.setExceptionBreakpointsForSession(this, (this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []); + this.debugService.getModel().registerBreakpointModes(this.configuration.type, this.raw.capabilities.breakpointModes || []); } catch (err) { this.initialized = true; this._onDidChangeState.fire(); @@ -457,7 +458,7 @@ export class DebugSession implements IDebugSession, IDisposable { const response = await this.raw.setBreakpoints({ source: rawSource, lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber), - breakpoints: breakpointsToSend.map(bp => ({ line: bp.sessionAgnosticData.lineNumber, column: bp.sessionAgnosticData.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })), + breakpoints: breakpointsToSend.map(bp => bp.toDAP()), sourceModified }); if (response && response.body) { @@ -476,7 +477,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts }); + const response = await this.raw.setFunctionBreakpoints({ breakpoints: fbpts.map(bp => bp.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < fbpts.length; i++) { @@ -534,7 +535,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }); + const response = await this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints.map(bp => bp.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < dataBreakpoints.length; i++) { @@ -551,7 +552,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toJSON()) }); + const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toDAP()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < instructionBreakpoints.length; i++) { diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index f196bb14aa43f..e092df645379d 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -690,7 +690,7 @@ class BreakpointRenderer implements ITableRenderer { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write', undefined); } } }); @@ -813,7 +813,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite', undefined); } } }); @@ -824,7 +824,7 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read'); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read', undefined); } } }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 039bcc1fb1083..86d0e94b8266f 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,6 +24,7 @@ import { ITelemetryEndpoint } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorPane } from 'vs/workbench/common/editor'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; +import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { ITaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -61,6 +62,7 @@ export const CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD = new RawContextKey('watchItemType', undefined, { type: 'string', description: nls.localize('watchItemType', "Represents the item type of the focused element in the WATCH view. For example: 'expression', 'variable'") }); export const CONTEXT_CAN_VIEW_MEMORY = new RawContextKey('canViewMemory', undefined, { type: 'boolean', description: nls.localize('canViewMemory', "Indicates whether the item in the view has an associated memory refrence.") }); export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey('breakpointItemType', undefined, { type: 'string', description: nls.localize('breakpointItemType', "Represents the item type of the focused element in the BREAKPOINTS view. For example: 'breakpoint', 'exceptionBreakppint', 'functionBreakpoint', 'dataBreakpoint'") }); +export const CONTEXT_BREAKPOINT_HAS_MODES = new RawContextKey('breakpointHasModes', false, { type: 'boolean', description: nls.localize('breakpointHasModes', "Whether the breakpoint has multiple modes it can switch to.") }); export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey('breakpointSupportsCondition', false, { type: 'boolean', description: nls.localize('breakpointSupportsCondition', "True when the focused breakpoint supports conditions.") }); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false, { type: 'boolean', description: nls.localize('loadedScriptsSupported', "True when the focused sessions supports the LOADED SCRIPTS view") }); export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined, { type: 'string', description: nls.localize('loadedScriptsItemType', "Represents the item type of the focused element in the LOADED SCRIPTS view.") }); @@ -540,6 +542,8 @@ export interface IBreakpointData { readonly logMessage?: string; readonly hitCondition?: string; readonly triggeredBy?: string; + readonly mode?: string; + readonly modeLabel?: string; } export interface IBreakpointUpdateData { @@ -549,6 +553,8 @@ export interface IBreakpointUpdateData { readonly lineNumber?: number; readonly column?: number; readonly triggeredBy?: string; + readonly mode?: string; + readonly modeLabel?: string; } export interface IBaseBreakpoint extends IEnablement { @@ -558,6 +564,10 @@ export interface IBaseBreakpoint extends IEnablement { readonly verified: boolean; readonly supported: boolean; readonly message?: string; + /** The preferred mode of the breakpoint from {@link DebugProtocol.BreakpointMode} */ + readonly mode?: string; + /** The preferred mode label of the breakpoint from {@link DebugProtocol.BreakpointMode} */ + readonly modeLabel?: string; readonly sessionsThatVerified: string[]; getIdFromAdapter(sessionId: string): number | undefined; } @@ -582,10 +592,13 @@ export interface IBreakpoint extends IBaseBreakpoint { setSessionDidTrigger(sessionId: string): void; /** Gets whether the `triggeredBy` condition has been met in the given sesison ID. */ getSessionDidTrigger(sessionId: string): boolean; + + toDAP(): DebugProtocol.SourceBreakpoint; } export interface IFunctionBreakpoint extends IBaseBreakpoint { readonly name: string; + toDAP(): DebugProtocol.FunctionBreakpoint; } export interface IExceptionBreakpoint extends IBaseBreakpoint { @@ -599,6 +612,7 @@ export interface IDataBreakpoint extends IBaseBreakpoint { readonly dataId: string; readonly canPersist: boolean; readonly accessType: DebugProtocol.DataBreakpointAccessType; + toDAP(): DebugProtocol.DataBreakpoint; } export interface IInstructionBreakpoint extends IBaseBreakpoint { @@ -606,7 +620,7 @@ export interface IInstructionBreakpoint extends IBaseBreakpoint { readonly offset?: number; /** Original instruction memory address; display purposes only */ readonly address: bigint; - toJSON(): DebugProtocol.InstructionBreakpoint; + toDAP(): DebugProtocol.InstructionBreakpoint; } export interface IExceptionInfo { @@ -683,7 +697,8 @@ export interface IDebugModel extends ITreeElement { getInstructionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; - + registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]): void; + getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[]; onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; onDidChangeWatchExpressions: Event; @@ -1112,7 +1127,7 @@ export interface IDebugService { /** * Adds a new function breakpoint for the given name. */ - addFunctionBreakpoint(name?: string, id?: string): void; + addFunctionBreakpoint(name?: string, id?: string, mode?: string): void; /** * Updates an already existing function breakpoint. @@ -1129,7 +1144,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise; + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, mode: string | undefined): Promise; /** * Updates an already existing data breakpoint. @@ -1146,7 +1161,7 @@ export interface IDebugService { /** * Adds a new instruction breakpoint. */ - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise; + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise; /** * Removes all instruction breakpoints. If address is passed only removes the instruction breakpoint with the passed address. @@ -1157,7 +1172,12 @@ export interface IDebugService { setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; - setExceptionBreakpointsForSession(session: IDebugSession, data: DebugProtocol.ExceptionBreakpointsFilter[]): void; + /** + * Creates breakpoints based on the sesison filter options. This will create + * disabled breakpoints (or enabled, if the filter indicates it's a default) + * for each filter provided in the session. + */ + setExceptionBreakpointsForSession(session: IDebugSession, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void; /** * Sends all breakpoints to the passed session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 98618a40cf4e1..8098d1ce57bff 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -817,22 +817,35 @@ function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: D }, data); } +export interface IBaseBreakpointOptions { + enabled?: boolean; + hitCondition?: string; + condition?: string; + logMessage?: string; + mode?: string; + modeLabel?: string; +} + export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint { private sessionData = new Map(); protected data: IBreakpointSessionData | undefined; + public hitCondition: string | undefined; + public condition: string | undefined; + public logMessage: string | undefined; + public mode: string | undefined; + public modeLabel: string | undefined; constructor( - enabled: boolean, - public hitCondition: string | undefined, - public condition: string | undefined, - public logMessage: string | undefined, - id: string + id: string, + opts: IBaseBreakpointOptions ) { - super(enabled, id); - if (enabled === undefined) { - this.enabled = true; - } + super(opts.enabled ?? true, id); + this.condition = opts.condition; + this.hitCondition = opts.hitCondition; + this.logMessage = opts.logMessage; + this.mode = opts.mode; + this.modeLabel = opts.modeLabel; } setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { @@ -904,37 +917,59 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi return undefined; } - toJSON(): any { - const result = Object.create(null); - result.id = this.getId(); - result.enabled = this.enabled; - result.condition = this.condition; - result.hitCondition = this.hitCondition; - result.logMessage = this.logMessage; - - return result; + toJSON(): IBaseBreakpointOptions & { id: string } { + return { + id: this.getId(), + enabled: this.enabled, + condition: this.condition, + hitCondition: this.hitCondition, + logMessage: this.logMessage, + mode: this.mode, + modeLabel: this.modeLabel, + }; } } +export interface IBreakpointOptions extends IBaseBreakpointOptions { + uri: uri; + lineNumber: number; + column: number | undefined; + adapterData: any; + triggeredBy: string | undefined; +} + export class Breakpoint extends BaseBreakpoint implements IBreakpoint { private sessionsDidTrigger?: Set; + private readonly _uri: uri; + private _adapterData: any; + private _lineNumber: number; + private _column: number | undefined; + public triggeredBy: string | undefined; constructor( - private readonly _uri: uri, - private _lineNumber: number, - private _column: number | undefined, - enabled: boolean, - condition: string | undefined, - hitCondition: string | undefined, - logMessage: string | undefined, - private _adapterData: any, + opts: IBreakpointOptions, private readonly textFileService: ITextFileService, private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, id = generateUuid(), - public triggeredBy: string | undefined = undefined ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this._uri = opts.uri; + this._lineNumber = opts.lineNumber; + this._column = opts.column; + this._adapterData = opts.adapterData; + this.triggeredBy = opts.triggeredBy; + } + + toDAP(): DebugProtocol.SourceBreakpoint { + return { + line: this.sessionAgnosticData.lineNumber, + column: this.sessionAgnosticData.column, + condition: this.condition, + hitCondition: this.hitCondition, + logMessage: this.logMessage, + mode: this.mode + }; } get originalUri() { @@ -1019,14 +1054,15 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } } - override toJSON(): any { - const result = super.toJSON(); - result.uri = this._uri; - result.lineNumber = this._lineNumber; - result.column = this._column; - result.adapterData = this.adapterData; - result.triggeredBy = this.triggeredBy; - return result; + override toJSON(): IBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + uri: this._uri, + lineNumber: this._lineNumber, + column: this._column, + adapterData: this.adapterData, + triggeredBy: this.triggeredBy, + }; } override toString(): string { @@ -1058,6 +1094,10 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { if (data.hasOwnProperty('logMessage')) { this.logMessage = data.logMessage; } + if (data.hasOwnProperty('mode')) { + this.mode = data.mode; + this.modeLabel = data.modeLabel; + } if (data.hasOwnProperty('triggeredBy')) { this.triggeredBy = data.triggeredBy; this.sessionsDidTrigger = undefined; @@ -1065,24 +1105,34 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } } +export interface IFunctionBreakpointOptions extends IBaseBreakpointOptions { + name: string; +} + export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreakpoint { + public name: string; constructor( - public name: string, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, + opts: IFunctionBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.name = opts.name; } - override toJSON(): any { - const result = super.toJSON(); - result.name = this.name; + toDAP(): DebugProtocol.FunctionBreakpoint { + return { + name: this.name, + condition: this.condition, + hitCondition: this.hitCondition, + }; + } - return result; + override toJSON(): IFunctionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + name: this.name, + }; } get supported(): boolean { @@ -1098,30 +1148,51 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak } } +export interface IDataBreakpointOptions extends IBaseBreakpointOptions { + description: string; + dataId: string; + canPersist: boolean; + accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; + accessType: DebugProtocol.DataBreakpointAccessType; +} + export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { + public readonly description: string; + public readonly dataId: string; + public readonly canPersist: boolean; + public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined; + public readonly accessType: DebugProtocol.DataBreakpointAccessType; constructor( - public readonly description: string, - public readonly dataId: string, - public readonly canPersist: boolean, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, - public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, - public readonly accessType: DebugProtocol.DataBreakpointAccessType, + opts: IDataBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.description = opts.description; + this.dataId = opts.dataId; + this.canPersist = opts.canPersist; + this.accessTypes = opts.accessTypes; + this.accessType = opts.accessType; } - override toJSON(): any { - const result = super.toJSON(); - result.description = this.description; - result.dataId = this.dataId; - result.accessTypes = this.accessTypes; - result.accessType = this.accessType; - return result; + toDAP(): DebugProtocol.DataBreakpoint { + return { + dataId: this.dataId, + accessType: this.accessType, + condition: this.condition, + hitCondition: this.hitCondition, + }; + } + + override toJSON(): IDataBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + description: this.description, + dataId: this.dataId, + accessTypes: this.accessTypes, + accessType: this.accessType, + canPersist: this.canPersist, + }; } get supported(): boolean { @@ -1137,35 +1208,51 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { } } +export interface IExceptionBreakpointOptions extends IBaseBreakpointOptions { + filter: string; + label: string; + supportsCondition: boolean; + description: string | undefined; + conditionDescription: string | undefined; + fallback?: boolean; +} + export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint { private supportedSessions: Set = new Set(); + public readonly filter: string; + public readonly label: string; + public readonly supportsCondition: boolean; + public readonly description: string | undefined; + public readonly conditionDescription: string | undefined; + private fallback: boolean = false; + constructor( - public readonly filter: string, - public readonly label: string, - enabled: boolean, - public readonly supportsCondition: boolean, - condition: string | undefined, - public readonly description: string | undefined, - public readonly conditionDescription: string | undefined, - private fallback: boolean = false + opts: IExceptionBreakpointOptions, + id = generateUuid(), ) { - super(enabled, undefined, condition, undefined, generateUuid()); + super(id, opts); + this.filter = opts.filter; + this.label = opts.label; + this.supportsCondition = opts.supportsCondition; + this.description = opts.description; + this.conditionDescription = opts.conditionDescription; + this.fallback = opts.fallback || false; } - override toJSON(): any { - const result = Object.create(null); - result.filter = this.filter; - result.label = this.label; - result.enabled = this.enabled; - result.supportsCondition = this.supportsCondition; - result.conditionDescription = this.conditionDescription; - result.condition = this.condition; - result.fallback = this.fallback; - result.description = this.description; - - return result; + override toJSON(): IExceptionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + filter: this.filter, + label: this.label, + enabled: this.enabled, + supportsCondition: this.supportsCondition, + conditionDescription: this.conditionDescription, + condition: this.condition, + fallback: this.fallback, + description: this.description, + }; } setSupportedSession(sessionId: string, supported: boolean): void { @@ -1198,7 +1285,11 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre } matches(filter: DebugProtocol.ExceptionBreakpointsFilter) { - return this.filter === filter.filter && this.label === filter.label && this.supportsCondition === !!filter.supportsCondition && this.conditionDescription === filter.conditionDescription && this.description === filter.description; + return this.filter === filter.filter + && this.label === filter.label + && this.supportsCondition === !!filter.supportsCondition + && this.conditionDescription === filter.conditionDescription + && this.description === filter.description; } override toString(): string { @@ -1206,27 +1297,48 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre } } +export interface IInstructionBreakpointOptions extends IBaseBreakpointOptions { + instructionReference: string; + offset: number; + canPersist: boolean; + address: bigint; +} + export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint { + public readonly instructionReference: string; + public readonly offset: number; + public readonly canPersist: boolean; + public readonly address: bigint; constructor( - public readonly instructionReference: string, - public readonly offset: number, - public readonly canPersist: boolean, - enabled: boolean, - hitCondition: string | undefined, - condition: string | undefined, - logMessage: string | undefined, - public readonly address: bigint, + opts: IInstructionBreakpointOptions, id = generateUuid() ) { - super(enabled, hitCondition, condition, logMessage, id); + super(id, opts); + this.instructionReference = opts.instructionReference; + this.offset = opts.offset; + this.canPersist = opts.canPersist; + this.address = opts.address; + } + + toDAP(): DebugProtocol.InstructionBreakpoint { + return { + instructionReference: this.instructionReference, + condition: this.condition, + hitCondition: this.hitCondition, + mode: this.mode, + offset: this.offset, + }; } - override toJSON(): DebugProtocol.InstructionBreakpoint { - const result = super.toJSON(); - result.instructionReference = this.instructionReference; - result.offset = this.offset; - return result; + override toJSON(): IInstructionBreakpointOptions & { id: string } { + return { + ...super.toJSON(), + instructionReference: this.instructionReference, + offset: this.offset, + canPersist: this.canPersist, + address: this.address, + }; } get supported(): boolean { @@ -1250,6 +1362,10 @@ export class ThreadAndSessionIds implements ITreeElement { } } +interface IBreakpointModeInternal extends DebugProtocol.BreakpointMode { + firstFromDebugType: string; +} + export class DebugModel extends Disposable implements IDebugModel { private sessions: IDebugSession[]; @@ -1258,6 +1374,7 @@ export class DebugModel extends Disposable implements IDebugModel { private readonly _onDidChangeBreakpoints = this._register(new Emitter()); private readonly _onDidChangeCallStack = this._register(new Emitter()); private readonly _onDidChangeWatchExpressions = this._register(new Emitter()); + private readonly _breakpointModes = new Map(); private breakpoints!: Breakpoint[]; private functionBreakpoints!: FunctionBreakpoint[]; private exceptionBreakpoints!: ExceptionBreakpoint[]; @@ -1492,24 +1609,33 @@ export class DebugModel extends Disposable implements IDebugModel { return this.instructionBreakpoints; } - setExceptionBreakpointsForSession(sessionId: string, data: DebugProtocol.ExceptionBreakpointsFilter[]): void { - if (data) { - let didChangeBreakpoints = false; - data.forEach(d => { - let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop(); - - if (!ebp) { - didChangeBreakpoints = true; - ebp = new ExceptionBreakpoint(d.filter, d.label, !!d.default, !!d.supportsCondition, undefined /* condition */, d.description, d.conditionDescription); - this.exceptionBreakpoints.push(ebp); - } + setExceptionBreakpointsForSession(sessionId: string, filters: DebugProtocol.ExceptionBreakpointsFilter[]): void { + if (!filters) { + return; + } - ebp.setSupportedSession(sessionId, true); - }); + let didChangeBreakpoints = false; + filters.forEach((d) => { + let ebp = this.exceptionBreakpoints.filter((exbp) => exbp.matches(d)).pop(); - if (didChangeBreakpoints) { - this._onDidChangeBreakpoints.fire(undefined); + if (!ebp) { + didChangeBreakpoints = true; + ebp = new ExceptionBreakpoint({ + filter: d.filter, + label: d.label, + enabled: !!d.default, + supportsCondition: !!d.supportsCondition, + description: d.description, + conditionDescription: d.conditionDescription, + }); + this.exceptionBreakpoints.push(ebp); } + + ebp.setSupportedSession(sessionId, true); + }); + + if (didChangeBreakpoints) { + this._onDidChangeBreakpoints.fire(undefined); } } @@ -1539,7 +1665,19 @@ export class DebugModel extends Disposable implements IDebugModel { addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { const newBreakpoints = rawData.map(rawBp => { - return new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, this.uriIdentityService, this.logService, rawBp.id, rawBp.triggeredBy); + return new Breakpoint({ + uri, + lineNumber: rawBp.lineNumber, + column: rawBp.column, + enabled: rawBp.enabled ?? true, + condition: rawBp.condition, + hitCondition: rawBp.hitCondition, + logMessage: rawBp.logMessage, + triggeredBy: rawBp.triggeredBy, + adapterData: undefined, + mode: rawBp.mode, + modeLabel: rawBp.modeLabel, + }, this.textFileService, this.uriIdentityService, this.logService, rawBp.id); }); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; @@ -1635,6 +1773,37 @@ export class DebugModel extends Disposable implements IDebugModel { return undefined; } + getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[] { + return [...this._breakpointModes.values()].filter(mode => mode.appliesTo.includes(forBreakpointType)); + } + + registerBreakpointModes(debugType: string, modes: DebugProtocol.BreakpointMode[]) { + for (const mode of modes) { + const key = `${mode.mode}/${mode.label}`; + const rec = this._breakpointModes.get(key); + if (rec) { + for (const target of mode.appliesTo) { + if (!rec.appliesTo.includes(target)) { + rec.appliesTo.push(target); + } + } + } else { + const duplicate = [...this._breakpointModes.values()].find(r => r !== rec && r.label === mode.label); + if (duplicate) { + duplicate.label = `${duplicate.label} (${duplicate.firstFromDebugType})`; + } + + this._breakpointModes.set(key, { + mode: mode.mode, + label: duplicate ? `${mode.label} (${debugType})` : mode.label, + firstFromDebugType: debugType, + description: mode.description, + appliesTo: mode.appliesTo.slice(), // avoid later mutations + }); + } + } + } + private sortAndDeDup(): void { this.breakpoints = this.breakpoints.sort((first, second) => { if (first.uri.toString() !== second.uri.toString()) { @@ -1703,8 +1872,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ changed: changed, sessionOnly: false }); } - addFunctionBreakpoint(functionName: string, id?: string): IFunctionBreakpoint { - const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id); + addFunctionBreakpoint(functionName: string, id?: string, mode?: string): IFunctionBreakpoint { + const newFunctionBreakpoint = new FunctionBreakpoint({ name: functionName, mode }, id); this.functionBreakpoints.push(newFunctionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint], sessionOnly: false }); @@ -1739,8 +1908,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType, id?: string): void { - const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes, accessType, id); + addDataBreakpoint(opts: IDataBreakpointOptions, id?: string): void { + const newDataBreakpoint = new DataBreakpoint(opts, id); this.dataBreakpoints.push(newDataBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false }); } @@ -1770,8 +1939,8 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): void { - const newInstructionBreakpoint = new InstructionBreakpoint(instructionReference, offset, false, true, hitCondition, condition, undefined, address); + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): void { + const newInstructionBreakpoint = new InstructionBreakpoint(opts); this.instructionBreakpoints.push(newInstructionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true }); } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 07a0024f98808..b00a4fd466a03 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -52,10 +52,11 @@ declare module DebugProtocol { This raw error might be interpreted by the client and is not shown in the UI. Some predefined values exist. Values: - 'cancelled': request was cancelled. + 'cancelled': the request was cancelled. + 'notStopped': the request may be retried once the adapter is in a 'stopped' state. etc. */ - message?: 'cancelled' | string; + message?: 'cancelled' | 'notStopped' | string; /** Contains request result if success is true and error details if success is false. */ body?: any; } @@ -71,7 +72,8 @@ declare module DebugProtocol { /** Cancel request; value of command field is 'cancel'. The `cancel` request is used by the client in two situations: - to indicate that it is no longer interested in the result produced by a specific request issued earlier - - to cancel a progress sequence. Clients should only call this request if the corresponding capability `supportsCancelRequest` is true. + - to cancel a progress sequence. + Clients should only call this request if the corresponding capability `supportsCancelRequest` is true. This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honoring this request but there are no guarantees. The `cancel` request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users. The request that got cancelled still needs to send a response back. This can either be a normal result (`success` attribute true) or an error response (`success` attribute false and the `message` set to `cancelled`). @@ -230,7 +232,7 @@ declare module DebugProtocol { A non-empty `output` attribute is shown as the unindented end of the group. */ group?: 'start' | 'startCollapsed' | 'end'; - /** If an attribute `variablesReference` exists and its value is > 0, the output contains objects which can be retrieved by passing `variablesReference` to the `variables` request. The value should be less than or equal to 2147483647 (2^31-1). */ + /** If an attribute `variablesReference` exists and its value is > 0, the output contains objects which can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The source location where the output was produced. */ source?: Source; @@ -430,7 +432,7 @@ declare module DebugProtocol { /** Arguments for `runInTerminal` request. */ interface RunInTerminalRequestArguments { - /** What kind of terminal to launch. */ + /** What kind of terminal to launch. Defaults to `integrated` if not specified. */ kind?: 'integrated' | 'external'; /** Title of the terminal. */ title?: string; @@ -676,7 +678,7 @@ declare module DebugProtocol { /** Arguments for `breakpointLocations` request. */ interface BreakpointLocationsArguments { - /** The source location of the breakpoints; either `source.path` or `source.reference` must be specified. */ + /** The source location of the breakpoints; either `source.path` or `source.sourceReference` must be specified. */ source: Source; /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ line: number; @@ -763,8 +765,7 @@ declare module DebugProtocol { } /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. - The request configures the debugger's response to thrown exceptions. - If an exception is configured to break, a `stopped` event is fired (with reason `exception`). + The request configures the debugger's response to thrown exceptions. Each of the `filters`, `filterOptions`, and `exceptionOptions` in the request are independent configurations to a debug adapter indicating a kind of exception to catch. An exception thrown in a program should result in a `stopped` event from the debug adapter (with reason `exception`) if any of the configured filters match. Clients should only call this request if the corresponding capability `exceptionBreakpointFilters` returns one or more filters. */ interface SetExceptionBreakpointsRequest extends Request { @@ -786,7 +787,7 @@ declare module DebugProtocol { /** Response to `setExceptionBreakpoints` request. The response contains an array of `Breakpoint` objects with information about each exception breakpoint or filter. The `Breakpoint` objects are in the same order as the elements of the `filters`, `filterOptions`, `exceptionOptions` arrays given as arguments. If both `filters` and `filterOptions` are given, the returned array must start with `filters` information first, followed by `filterOptions` information. - The `verified` property of a `Breakpoint` object signals whether the exception breakpoint or filter could be successfully created and whether the condition or hit count expressions are valid. In case of an error the `message` property explains the problem. The `id` property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. + The `verified` property of a `Breakpoint` object signals whether the exception breakpoint or filter could be successfully created and whether the condition is valid. In case of an error the `message` property explains the problem. The `id` property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. For backward compatibility both the `breakpoints` array and the enclosing `body` are optional. If these elements are missing a client is not able to show problems for individual exception breakpoints or filters. */ interface SetExceptionBreakpointsResponse extends Response { @@ -809,18 +810,22 @@ declare module DebugProtocol { /** Arguments for `dataBreakpointInfo` request. */ interface DataBreakpointInfoArguments { - /** Reference to the variable container if the data breakpoint is requested for a child of the container. */ + /** Reference to the variable container if the data breakpoint is requested for a child of the container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The name of the variable's child to obtain data breakpoint information for. If `variablesReference` isn't specified, this can be an expression. */ name: string; + /** When `name` is an expression, evaluate it in the scope of this stack frame. If not specified, the expression is evaluated in the global scope. When `variablesReference` is specified, this property has no effect. */ + frameId?: number; + /** The mode of the desired breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Response to `dataBreakpointInfo` request. */ interface DataBreakpointInfoResponse extends Response { body: { - /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. */ + /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */ dataId: string | null; /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ description: string; @@ -1032,7 +1037,7 @@ declare module DebugProtocol { } /** RestartFrame request; value of command field is 'restartFrame'. - The request restarts execution of the specified stackframe. + The request restarts execution of the specified stack frame. The debug adapter first sends the response and then a `stopped` event (with reason `restart`) after the restart has completed. Clients should only call this request if the corresponding capability `supportsRestartFrame` is true. */ @@ -1043,7 +1048,7 @@ declare module DebugProtocol { /** Arguments for `restartFrame` request. */ interface RestartFrameArguments { - /** Restart this stackframe. */ + /** Restart the stack frame identified by `frameId`. The `frameId` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ frameId: number; } @@ -1120,7 +1125,7 @@ declare module DebugProtocol { /** Response to `stackTrace` request. */ interface StackTraceResponse extends Response { body: { - /** The frames of the stackframe. If the array has length zero, there are no stackframes available. + /** The frames of the stack frame. If the array has length zero, there are no stack frames available. This means that there is no location information available. */ stackFrames: StackFrame[]; @@ -1130,7 +1135,7 @@ declare module DebugProtocol { } /** Scopes request; value of command field is 'scopes'. - The request returns the variable scopes for a given stackframe ID. + The request returns the variable scopes for a given stack frame ID. */ interface ScopesRequest extends Request { // command: 'scopes'; @@ -1139,14 +1144,14 @@ declare module DebugProtocol { /** Arguments for `scopes` request. */ interface ScopesArguments { - /** Retrieve the scopes for this stackframe. */ + /** Retrieve the scopes for the stack frame identified by `frameId`. The `frameId` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ frameId: number; } /** Response to `scopes` request. */ interface ScopesResponse extends Response { body: { - /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ + /** The scopes of the stack frame. If the array has length zero, there are no scopes available. */ scopes: Scope[]; }; } @@ -1162,13 +1167,17 @@ declare module DebugProtocol { /** Arguments for `variables` request. */ interface VariablesArguments { - /** The Variable reference. */ + /** The variable for which to retrieve its children. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** Filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ filter?: 'indexed' | 'named'; - /** The index of the first variable to return; if omitted children start at 0. */ + /** The index of the first variable to return; if omitted children start at 0. + The attribute is only honored by a debug adapter if the corresponding capability `supportsVariablePaging` is true. + */ start?: number; - /** The number of variables to return. If count is missing or 0, all variables are returned. */ + /** The number of variables to return. If count is missing or 0, all variables are returned. + The attribute is only honored by a debug adapter if the corresponding capability `supportsVariablePaging` is true. + */ count?: number; /** Specifies details on how to format the Variable values. The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. @@ -1195,7 +1204,7 @@ declare module DebugProtocol { /** Arguments for `setVariable` request. */ interface SetVariableArguments { - /** The reference of the variable container. */ + /** The reference of the variable container. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The name of the variable in the container. */ name: string; @@ -1212,9 +1221,7 @@ declare module DebugProtocol { value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; - /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the new value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1226,6 +1233,11 @@ declare module DebugProtocol { The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** A memory reference to a location appropriate for this result. + For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; } @@ -1356,16 +1368,16 @@ declare module DebugProtocol { frameId?: number; /** The context in which the evaluate request is used. Values: - 'variables': evaluate is called from a variables view context. 'watch': evaluate is called from a watch view context. 'repl': evaluate is called from a REPL context. 'hover': evaluate is called to generate the debug hover contents. This value should only be used if the corresponding capability `supportsEvaluateForHovers` is true. 'clipboard': evaluate is called to generate clipboard contents. This value should only be used if the corresponding capability `supportsClipboardContext` is true. + 'variables': evaluate is called from a variables view context. etc. */ - context?: 'variables' | 'watch' | 'repl' | 'hover' | 'clipboard' | string; + context?: 'watch' | 'repl' | 'hover' | 'clipboard' | 'variables' | string; /** Specifies details on how to format the result. The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. */ @@ -1383,9 +1395,7 @@ declare module DebugProtocol { type?: string; /** Properties of an evaluate result that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1399,7 +1409,7 @@ declare module DebugProtocol { indexedVariables?: number; /** A memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. - This attribute should be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; }; @@ -1439,9 +1449,7 @@ declare module DebugProtocol { type?: string; /** Properties of a value that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If `variablesReference` is > 0, the value is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. - The value should be less than or equal to 2147483647 (2^31-1). - */ + /** If `variablesReference` is > 0, the evaluate result is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference?: number; /** The number of named child variables. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1453,6 +1461,11 @@ declare module DebugProtocol { The value should be less than or equal to 2147483647 (2^31-1). */ indexedVariables?: number; + /** A memory reference to a location appropriate for this result. + For pointer type eval results, this is generally a reference to the memory address contained in the pointer. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. + */ + memoryReference?: string; }; } @@ -1596,7 +1609,7 @@ declare module DebugProtocol { This can be used to determine the number of bytes that should be skipped before a subsequent `readMemory` request succeeds. */ unreadableBytes?: number; - /** The bytes read from memory, encoded using base64. */ + /** The bytes read from memory, encoded using base64. If the decoded length of `data` is less than the requested `count` in the original `readMemory` request, and `unreadableBytes` is zero or omitted, then the client should assume it's reached the end of readable memory. */ data?: string; }; } @@ -1667,6 +1680,42 @@ declare module DebugProtocol { }; } + /** DataAddressBreakpointInfo request; value of command field is 'DataAddressBreakpointInfo'. + Obtains information on a possible data breakpoint that could be set on a memory address or memory address range. + + Clients should only call this request if the corresponding capability `supportsDataAddressInfo` is true. + */ + interface DataAddressBreakpointInfoRequest extends Request { + // command: 'DataAddressBreakpointInfo'; + arguments: DataAddressBreakpointInfoArguments; + } + + /** Arguments for `dataAddressBreakpointInfo` request. */ + interface DataAddressBreakpointInfoArguments { + /** The address of the data for which to obtain breakpoint information. + Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. + */ + address?: string; + /** If passed, requests breakpoint information for an exclusive byte range rather than a single address. The range extends the given number of `bytes` from the start `address`. + Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. + */ + bytes?: string; + } + + /** Response to `dataAddressBreakpointInfo` request. */ + interface DataAddressBreakpointInfoResponse extends Response { + body: { + /** An identifier for the data on which a data breakpoint can be registered with the `setDataBreakpoints` request or null if no data breakpoint is available. If a `variablesReference` or `frameId` is passed, the `dataId` is valid in the current suspended state, otherwise it's valid indefinitely. See 'Lifetime of Object References' in the Overview section for details. Breakpoints set using the `dataId` in the `setDataBreakpoints` request may outlive the lifetime of the associated `dataId`. */ + dataId: string | null; + /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ + description: string; + /** Attribute lists the available access types for a potential data breakpoint. A UI client could surface this information. */ + accessTypes?: DataBreakpointAccessType[]; + /** Attribute indicates that a potential data breakpoint could be persisted across sessions. */ + canPersist?: boolean; + }; + } + /** Information about the capabilities of a debug adapter. */ interface Capabilities { /** The debug adapter supports the `configurationDone` request. */ @@ -1739,6 +1788,8 @@ declare module DebugProtocol { supportsBreakpointLocationsRequest?: boolean; /** The debug adapter supports the `clipboard` context value in the `evaluate` request. */ supportsClipboardContext?: boolean; + /** The debug adapter supports the `dataAddressBreakpointInfo` request. */ + supportsDataAddressInfo?: boolean; /** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */ supportsSteppingGranularity?: boolean; /** The debug adapter supports adding breakpoints based on instruction references. */ @@ -1747,6 +1798,11 @@ declare module DebugProtocol { supportsExceptionFilterOptions?: boolean; /** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */ supportsSingleThreadExecutionRequests?: boolean; + /** Modes of breakpoints supported by the debug adapter, such as 'hardware' or 'software'. If present, the client may allow the user to select a mode and include it in its `setBreakpoints` request. + + Clients may present the first applicable mode in this array as the 'default' mode in gestures that set breakpoints. + */ + breakpointModes?: BreakpointMode[]; } /** An `ExceptionBreakpointsFilter` is shown in the UI as an filter option for configuring how exceptions are dealt with. */ @@ -1767,7 +1823,7 @@ declare module DebugProtocol { /** A structured message object. Used to return errors from requests. */ interface Message { - /** Unique identifier for the message. */ + /** Unique (within a debug adapter implementation) identifier for the message. The purpose of these error IDs is to help extension authors that have the requirement that every user visible error message needs a corresponding error number, so that users or customer support can find information about the specific error more easily. */ id: number; /** A format string for the message. Embedded variables have the form `{name}`. If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. @@ -1833,13 +1889,6 @@ declare module DebugProtocol { width?: number; } - /** The ModulesViewDescriptor is the container for all declarative configuration options of a module view. - For now it only specifies the columns to be shown in the modules view. - */ - interface ModulesViewDescriptor { - columns: ColumnDescriptor[]; - } - /** A Thread */ interface Thread { /** Unique identifier for the thread. */ @@ -1884,7 +1933,7 @@ declare module DebugProtocol { /** A Stackframe contains the source location. */ interface StackFrame { /** An identifier for the stack frame. It must be unique across all threads. - This id can be used to retrieve the scopes of the frame with the `scopes` request or to restart the execution of a stackframe. + This id can be used to retrieve the scopes of the frame with the `scopes` request or to restart the execution of a stack frame. */ id: number; /** The name of the stack frame, typically a method name. */ @@ -1899,7 +1948,7 @@ declare module DebugProtocol { endLine?: number; /** End position of the range covered by the stack frame. It is measured in UTF-16 code units and the client capability `columnsStartAt1` determines whether it is 0- or 1-based. */ endColumn?: number; - /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. */ + /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. If a debug adapter has this capability, then `canRestart` defaults to `true` if the property is absent. */ canRestart?: boolean; /** A memory reference for the current instruction pointer in this frame. */ instructionPointerReference?: string; @@ -1923,7 +1972,7 @@ declare module DebugProtocol { etc. */ presentationHint?: 'arguments' | 'locals' | 'registers' | string; - /** The variables of this scope can be retrieved by passing the value of `variablesReference` to the `variables` request. */ + /** The variables of this scope can be retrieved by passing the value of `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named variables in this scope. The client can use this information to present the variables in a paged UI and fetch them in chunks. @@ -1971,7 +2020,7 @@ declare module DebugProtocol { presentationHint?: VariablePresentationHint; /** The evaluatable name of this variable which can be passed to the `evaluate` request to fetch the variable's value. */ evaluateName?: string; - /** If `variablesReference` is > 0, the variable is structured and its children can be retrieved by passing `variablesReference` to the `variables` request. */ + /** If `variablesReference` is > 0, the variable is structured and its children can be retrieved by passing `variablesReference` to the `variables` request as long as execution remains suspended. See 'Lifetime of Object References' in the Overview section for details. */ variablesReference: number; /** The number of named child variables. The client can use this information to present the children in a paged UI and fetch them in chunks. @@ -1981,8 +2030,10 @@ declare module DebugProtocol { The client can use this information to present the children in a paged UI and fetch them in chunks. */ indexedVariables?: number; - /** The memory reference for the variable if the variable represents executable code, such as a function pointer. - This attribute is only required if the corresponding capability `supportsMemoryReferences` is true. + /** A memory reference associated with this variable. + For pointer type variables, this is generally a reference to the memory address contained in the pointer. + For executable data, this reference may later be used in a `disassemble` request. + This attribute may be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; } @@ -2011,8 +2062,8 @@ declare module DebugProtocol { 'constant': Indicates that the object is a constant. 'readOnly': Indicates that the object is read only. 'rawString': Indicates that the object is a raw string. - 'hasObjectId': Indicates that the object can have an Object ID created for it. - 'canHaveObjectId': Indicates that the object has an Object ID associated with it. + 'hasObjectId': Indicates that the object can have an Object ID created for it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol. + 'canHaveObjectId': Indicates that the object has an Object ID associated with it. This is a vestigial attribute that is used by some clients; 'Object ID's are not specified in the protocol. 'hasSideEffects': Indicates that the evaluation had side effects. 'hasDataBreakpoint': Indicates that the object has its value tracked by a data breakpoint. etc. @@ -2054,13 +2105,17 @@ declare module DebugProtocol { /** The expression that controls how many hits of the breakpoint are ignored. The debug adapter is expected to interpret the expression as needed. The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. + If both this property and `condition` are specified, `hitCondition` should be evaluated only if the `condition` is met, and the debug adapter should stop only if both conditions are met. */ hitCondition?: string; /** If this attribute exists and is non-empty, the debug adapter must not 'break' (stop) but log the message instead. Expressions within `{}` are interpolated. The attribute is only honored by a debug adapter if the corresponding capability `supportsLogPoints` is true. + If either `hitCondition` or `condition` is specified, then the message should only be logged if those conditions are met. */ logMessage?: string; + /** The mode of this breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Properties of a breakpoint passed to the `setFunctionBreakpoints` request. */ @@ -2101,7 +2156,7 @@ declare module DebugProtocol { This should be a memory or instruction pointer reference from an `EvaluateResponse`, `Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`. */ instructionReference: string; - /** The offset from the instruction reference. + /** The offset from the instruction reference in bytes. This can be negative. */ offset?: number; @@ -2114,6 +2169,8 @@ declare module DebugProtocol { The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. */ hitCondition?: string; + /** The mode of this breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** Information about a breakpoint created in `setBreakpoints`, `setFunctionBreakpoints`, `setInstructionBreakpoints`, or `setDataBreakpoints` requests. */ @@ -2144,6 +2201,12 @@ declare module DebugProtocol { This can be negative. */ offset?: number; + /** A machine-readable explanation of why a breakpoint may not be verified. If a breakpoint is verified or a specific reason is not known, the adapter should omit this property. Possible values include: + + - `pending`: Indicates a breakpoint might be verified in the future, but the adapter cannot verify it in the current state. + - `failed`: Indicates a breakpoint was not able to be verified, and the adapter does not believe it can be verified without intervention. + */ + reason?: 'pending' | 'failed'; } /** The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. @@ -2259,6 +2322,8 @@ declare module DebugProtocol { The exception breaks into the debugger if the result of the condition is true. */ condition?: string; + /** The mode of this exception breakpoint. If defined, this must be one of the `breakpointModes` the debug adapter advertised in its `Capabilities`. */ + mode?: string; } /** An `ExceptionOptions` assigns configuration options to a set of exceptions. */ @@ -2328,6 +2393,11 @@ declare module DebugProtocol { endLine?: number; /** The end column of the range that corresponds to this instruction, if any. */ endColumn?: number; + /** A hint for how to present the instruction in the UI. + + A value of `invalid` may be used to indicate this instruction is 'filler' and cannot be reached by the program. For example, unreadable memory addresses may be presented is 'invalid.' + */ + presentationHint?: 'normal' | 'invalid'; } /** Logical areas that can be invalidated by the `invalidated` event. @@ -2339,5 +2409,27 @@ declare module DebugProtocol { etc. */ type InvalidatedAreas = 'all' | 'stacks' | 'threads' | 'variables' | string; + + /** A `BreakpointMode` is provided as a option when setting breakpoints on sources or instructions. */ + interface BreakpointMode { + /** The internal ID of the mode. This value is passed to the `setBreakpoints` request. */ + mode: string; + /** The name of the breakpoint mode. This is shown in the UI. */ + label: string; + /** A help text providing additional information about the breakpoint mode. This string is typically shown as a hover and can be translated. */ + description?: string; + /** Describes one or more type of breakpoint this mode applies to. */ + appliesTo: BreakpointModeApplicability[]; + } + + /** Describes one or more type of breakpoint a `BreakpointMode` applies to. This is a non-exhaustive enumeration and may expand as future breakpoint types are added. + Values: + 'source': In `SourceBreakpoint`s + 'exception': In exception breakpoints applied in the `ExceptionFilterOptions` + 'data': In data breakpoints requested in the the `DataBreakpointInfo` request + 'instruction': In `InstructionBreakpoint`s + etc. + */ + type BreakpointModeApplicability = 'source' | 'exception' | 'data' | 'instruction' | string; } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index dd267f9035834..b7e57fff65442 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -65,8 +65,9 @@ export class DebugStorage extends Disposable { private loadBreakpoints(): Breakpoint[] { let result: Breakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { - return new Breakpoint(URI.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id, breakpoint.triggeredBy); + result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: ReturnType) => { + breakpoint.uri = URI.revive(breakpoint.uri); + return new Breakpoint(breakpoint, this.textFileService, this.uriIdentityService, this.logService, breakpoint.id); }); } catch (e) { } @@ -76,8 +77,8 @@ export class DebugStorage extends Disposable { private loadFunctionBreakpoints(): FunctionBreakpoint[] { let result: FunctionBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { - return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage, fb.id); + result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: ReturnType) => { + return new FunctionBreakpoint(fb, fb.id); }); } catch (e) { } @@ -87,8 +88,8 @@ export class DebugStorage extends Disposable { private loadExceptionBreakpoints(): ExceptionBreakpoint[] { let result: ExceptionBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition, exBreakpoint.description, exBreakpoint.conditionDescription, !!exBreakpoint.fallback); + result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: ReturnType) => { + return new ExceptionBreakpoint(exBreakpoint, exBreakpoint.id); }); } catch (e) { } @@ -98,8 +99,8 @@ export class DebugStorage extends Disposable { private loadDataBreakpoints(): DataBreakpoint[] { let result: DataBreakpoint[] | undefined; try { - result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes, dbp.accessType, dbp.id); + result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: ReturnType) => { + return new DataBreakpoint(dbp, dbp.id); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index 1b7bc10db5641..b85e544f9bb85 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -291,7 +291,7 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); //address: string, offset: number, condition?: string, hitCondition?: string - model.addInstructionBreakpoint('0xCCCCFFFF', 0, 0n); + model.addInstructionBreakpoint({ instructionReference: '0xCCCCFFFF', offset: 0, address: 0n, canPersist: false }); assert.strictEqual(eventCount, 1); let instructionBreakpoints = model.getInstructionBreakpoints(); @@ -299,7 +299,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(instructionBreakpoints[0].instructionReference, '0xCCCCFFFF'); assert.strictEqual(instructionBreakpoints[0].offset, 0); - model.addInstructionBreakpoint('0xCCCCEEEE', 1, 0n); + model.addInstructionBreakpoint({ instructionReference: '0xCCCCEEEE', offset: 1, address: 0n, canPersist: false }); assert.strictEqual(eventCount, 2); instructionBreakpoints = model.getInstructionBreakpoints(); assert.strictEqual(instructionBreakpoints.length, 2); @@ -313,8 +313,8 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); - model.addDataBreakpoint('label', 'id', true, ['read'], 'read', '1'); - model.addDataBreakpoint('second', 'secondId', false, ['readWrite'], 'readWrite', '2'); + model.addDataBreakpoint({ description: 'label', dataId: 'id', canPersist: true, accessTypes: ['read'], accessType: 'read' }, '1'); + model.addDataBreakpoint({ description: 'second', dataId: 'secondId', canPersist: false, accessTypes: ['readWrite'], accessType: 'readWrite' }, '2'); model.updateDataBreakpoint('1', { condition: 'aCondition' }); model.updateDataBreakpoint('2', { hitCondition: '10' }); const dataBreakpoints = model.getDataBreakpoints(); @@ -374,7 +374,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); - model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); + model.addDataBreakpoint({ description: 'label', canPersist: true, accessTypes: ['read'], accessType: 'read', dataId: 'id' }); const dataBreakpoints = model.getDataBreakpoints(); result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0], ls, model); assert.strictEqual(result.message, 'Data Breakpoint'); diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index 96e097d1205d4..26c5549841b06 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -18,7 +18,7 @@ suite('DebugModel', () => { suite('FunctionBreakpoint', () => { test('Id is saved', () => { - const fbp = new FunctionBreakpoint('function', true, 'hit condition', 'condition', 'log message'); + const fbp = new FunctionBreakpoint({ name: 'function', enabled: true, hitCondition: 'hit condition', condition: 'condition', logMessage: 'log message' }); const strigified = JSON.stringify(fbp); const parsed = JSON.parse(strigified); assert.equal(parsed.id, fbp.getId()); @@ -27,10 +27,17 @@ suite('DebugModel', () => { suite('ExceptionBreakpoint', () => { test('Restored matches new', () => { - const ebp = new ExceptionBreakpoint('id', 'label', true, true, 'condition', 'description', 'condition description', false); + const ebp = new ExceptionBreakpoint({ + conditionDescription: 'condition description', + description: 'description', + filter: 'condition', + label: 'label', + supportsCondition: true, + enabled: true, + }, 'id'); const strigified = JSON.stringify(ebp); const parsed = JSON.parse(strigified); - const newEbp = new ExceptionBreakpoint(parsed.filter, parsed.label, parsed.enabled, parsed.supportsCondition, parsed.condition, parsed.description, parsed.conditionDescription, !!parsed.fallback); + const newEbp = new ExceptionBreakpoint(parsed); assert.ok(ebp.matches(newEbp)); }); }); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 5dca0f132e5fa..617f46d449fb0 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -15,6 +15,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { AdapterEndEvent, IAdapterManager, IBreakpoint, IBreakpointData, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IDebugger, IExceptionBreakpoint, IExceptionInfo, IFunctionBreakpoint, IInstructionBreakpoint, ILaunch, IMemoryRegion, INewReplElementData, IRawModelUpdate, IRawStoppedDetails, IReplElement, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; import { DebugCompoundRoot } from 'vs/workbench/contrib/debug/common/debugCompoundRoot'; +import { IInstructionBreakpointOptions } from 'vs/workbench/contrib/debug/common/debugModel'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugStorage } from 'vs/workbench/contrib/debug/common/debugStorage'; @@ -85,7 +86,7 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { + addInstructionBreakpoint(opts: IInstructionBreakpointOptions): Promise { throw new Error('Method not implemented.'); } From 6722b81416d8f1ee24209ca4a19792b6e6a55beb Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 14 Feb 2024 21:52:18 -0800 Subject: [PATCH 0319/1863] Support cmd+arrow/enter when focus is outside of chat widget. (#205260) --- .../browser/contrib/navigation/arrow.ts | 38 +++++--- .../controller/chat/cellChatActions.ts | 90 ++++++++++++++++++- .../controller/chat/notebookChatContext.ts | 2 + .../controller/chat/notebookChatController.ts | 49 +++++++++- .../browser/controller/insertCellActions.ts | 3 +- .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 3 + 7 files changed, 171 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index 2436576bbce5e..52eea945ec68b 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -18,6 +18,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -196,12 +197,18 @@ registerAction2(class extends NotebookAction { super({ id: NOTEBOOK_FOCUS_TOP, title: localize('focusFirstCell', 'Focus First Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.Home, - mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, + keybinding: [ + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.Home, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, + weight: KeybindingWeight.WorkbenchContrib + } + ], }); } @@ -221,12 +228,19 @@ registerAction2(class extends NotebookAction { super({ id: NOTEBOOK_FOCUS_BOTTOM, title: localize('focusLastCell', 'Focus Last Cell'), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.End, - mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, - weight: KeybindingWeight.WorkbenchContrib - }, + keybinding: [ + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.End, + mac: undefined, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), + mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, + weight: KeybindingWeight.WorkbenchContrib + } + ], }); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 7b8ce9ff6df4f..8c97251257ec4 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -14,7 +14,7 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -232,6 +232,15 @@ registerAction2(class extends NotebookAction { when: ContextKeyExpr.and(CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_INLINE_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_USER_DID_EDIT), weight: KeybindingWeight.EditorCore + 10, primary: KeyCode.Escape + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') + ), + primary: KeyMod.CtrlCmd | KeyCode.Enter, + weight: KeybindingWeight.WorkbenchContrib } ], menu: [ @@ -483,3 +492,82 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) ) }); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focus', + title: localize('focusNotebookChat', 'Focus Chat'), + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('above') + ), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.not(InputFocusedContextKey), + CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('below') + ), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focus(); + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focusNextCell', + title: localize('focusNextCell', 'Focus Next Cell'), + keybinding: [ + { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + ), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focusNext(); + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super({ + id: 'notebook.cell.chat.focusPreviousCell', + title: localize('focusPreviousCell', 'Focus Previous Cell'), + keybinding: [ + { + when: ContextKeyExpr.and( + CTX_NOTEBOOK_CELL_CHAT_FOCUSED, + CTX_INLINE_CHAT_FOCUSED, + ), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib + } + ], + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + NotebookChatController.get(context.notebookEditor)?.focusAbove(); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts index af6c5e38caa24..4c8a6aa024d0c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext.ts @@ -10,6 +10,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const CTX_NOTEBOOK_CELL_CHAT_FOCUSED = new RawContextKey('notebookCellChatFocused', false, localize('notebookCellChatFocused', "Whether the cell chat editor is focused")); export const CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('notebookChatHasActiveRequest', false, localize('notebookChatHasActiveRequest', "Whether the cell chat editor has an active request")); export const CTX_NOTEBOOK_CHAT_USER_DID_EDIT = new RawContextKey('notebookChatUserDidEdit', false, localize('notebookChatUserDidEdit', "Whether the user did changes ontop of the notebook cell chat")); +export const CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION = new RawContextKey<'above' | 'below' | ''>('notebookChatOuterFocusPosition', '', localize('notebookChatOuterFocusPosition', "Whether the focus of the notebook editor is above or below the cell chat")); + export const MENU_CELL_CHAT_INPUT = MenuId.for('cellChatInput'); export const MENU_CELL_CHAT_WIDGET = MenuId.for('cellChatWidget'); export const MENU_CELL_CHAT_WIDGET_STATUS = MenuId.for('cellChatWidget.status'); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 61ac0a1a06ce9..7686f52b8b307 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -39,7 +39,7 @@ import { IInlineChatMessageAppender, InlineChatWidget } from 'vs/workbench/contr import { asProgressiveEdit, performAsyncTextEdit } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, EditMode, IInlineChatProgressItem, IInlineChatRequest, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; +import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_FEEDBACK, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; import { INotebookEditor, INotebookEditorContribution, INotebookViewZone, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; @@ -180,6 +180,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; + private readonly _ctxOuterFocusPosition: IContextKey<'above' | 'below' | ''>; private readonly _userEditingDisposables = this._register(new DisposableStore()); private readonly _ctxLastResponseType: IContextKey; private _widget: NotebookChatWidget | undefined; @@ -203,6 +204,29 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._ctxCellWidgetFocused = CTX_NOTEBOOK_CELL_CHAT_FOCUSED.bindTo(this._contextKeyService); this._ctxLastResponseType = CTX_INLINE_CHAT_LAST_RESPONSE_TYPE.bindTo(this._contextKeyService); this._ctxUserDidEdit = CTX_NOTEBOOK_CHAT_USER_DID_EDIT.bindTo(this._contextKeyService); + this._ctxOuterFocusPosition = CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.bindTo(this._contextKeyService); + + this._registerFocusTracker(); + } + + private _registerFocusTracker() { + this._register(this._notebookEditor.onDidChangeFocus(() => { + if (!this._widget) { + this._ctxOuterFocusPosition.set(''); + return; + } + + const widgetIndex = this._widget.afterModelPosition; + const focus = this._notebookEditor.getFocus().start; + + if (focus + 1 === widgetIndex) { + this._ctxOuterFocusPosition.set('above'); + } else if (focus === widgetIndex) { + this._ctxOuterFocusPosition.set('below'); + } else { + this._ctxOuterFocusPosition.set(''); + } + })); } run(index: number, input: string | undefined, autoSend: boolean | undefined): void { @@ -672,6 +696,25 @@ export class NotebookChatController extends Disposable implements INotebookEdito this.dismiss(); } + async focusAbove() { + if (!this._widget) { + return; + } + + const index = this._widget.afterModelPosition; + const prev = index - 1; + if (prev < 0) { + return; + } + + const cell = this._notebookEditor.cellAt(prev); + if (!cell) { + return; + } + + await this._notebookEditor.focusNotebookCell(cell, 'editor'); + } + async focusNext() { if (!this._widget) { return; @@ -686,6 +729,10 @@ export class NotebookChatController extends Disposable implements INotebookEdito await this._notebookEditor.focusNotebookCell(cell, 'editor'); } + focus() { + this._focusWidget(); + } + focusNearestWidget(index: number, direction: 'above' | 'below') { switch (direction) { case 'above': diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index 92ecb50915399..d998aed0f143f 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -17,6 +17,7 @@ import { INotebookActionContext, NotebookAction } from 'vs/workbench/contrib/not import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext'; const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow'; @@ -110,7 +111,7 @@ registerAction2(class InsertCodeCellBelowAction extends InsertCellCommand { title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated()), + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION.isEqualTo('')), weight: KeybindingWeight.WorkbenchContrib }, menu: { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ae7113667a3e5..4f6205e00ccdb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -467,6 +467,7 @@ export interface INotebookEditor { readonly onDidChangeViewCells: Event; readonly onDidChangeVisibleRanges: Event; readonly onDidChangeSelection: Event; + readonly onDidChangeFocus: Event; /** * An event emitted when the model of this editor has changed. */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0eb7c2800059e..cdffe911b5d91 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -154,6 +154,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidScroll: Event = this._onDidScroll.event; private readonly _onDidChangeActiveCell = this._register(new Emitter()); readonly onDidChangeActiveCell: Event = this._onDidChangeActiveCell.event; + private readonly _onDidChangeFocus = this._register(new Emitter()); + readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; private readonly _onDidChangeSelection = this._register(new Emitter()); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; private readonly _onDidChangeVisibleRanges = this._register(new Emitter()); @@ -1010,6 +1012,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this._list.onDidChangeFocus(_e => { this._onDidChangeActiveEditor.fire(this); this._onDidChangeActiveCell.fire(); + this._onDidChangeFocus.fire(); this._cursorNavMode.set(false); })); From 633992ca1cc89097c60d4a4e0eba8acb050edabb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 09:47:17 +0100 Subject: [PATCH 0320/1863] remove `XYZTurn#agent#agentId` because it is now just `agent` (#205266) https://github.com/microsoft/vscode/issues/199908 --- src/vs/workbench/api/common/extHostChatAgents2.ts | 4 ++-- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 15 ++++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 360fdb9898a19..78adfe193fdaa 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -235,11 +235,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId, agentId: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId })); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d6c3996105eec..39e91887625e0 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4265,7 +4265,7 @@ export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agent: string; agentId: string }, + readonly agent: { extensionId: string; agent: string }, ) { } } @@ -4274,7 +4274,7 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string; agentId: string } + readonly agent: { extensionId: string; agent: string } ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 82cdc7615f51b..1762bdafb6408 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -19,10 +19,9 @@ declare module 'vscode' { readonly prompt: string; /** - * The ID of the chat agent to which this request was directed. + * The name of the chat agent and contributing extension to which this request was directed. */ - // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; + readonly agent: { readonly extensionId: string; readonly agent: string }; /** * The name of the {@link ChatAgentCommand command} that was selected for this request. @@ -34,7 +33,7 @@ declare module 'vscode' { */ readonly variables: ChatAgentResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agent: string }); } // TODO@API name: Turn? @@ -50,10 +49,12 @@ declare module 'vscode' { */ readonly result: ChatAgentResult2; - // TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name) - readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string }; + /** + * The name of the chat agent and contributing extension to which this request was directed. + */ + readonly agent: { readonly extensionId: string; readonly agent: string }; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string }); + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); } export interface ChatAgentContext { From 4c06e3f8670fb6005cf861acbe8a522243ca7b64 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 12:02:08 +0100 Subject: [PATCH 0321/1863] rename proposal from `chatRequestAccess` to `languageModels`, move into new `lm` namespace (#205272) * rename proposal from `chatRequestAccess` to `languageModels`, move into new `lm` namespace https://github.com/microsoft/vscode/issues/199908 * fix itests --- extensions/vscode-api-tests/package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 31 +++++++++++-------- .../common/extensionsApiProposals.ts | 2 +- ...ts => vscode.proposed.languageModels.d.ts} | 6 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) rename src/vscode-dts/{vscode.proposed.chatRequestAccess.d.ts => vscode.proposed.languageModels.d.ts} (98%) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index a35e4bc08ccab..85f70af3f4f38 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -8,7 +8,7 @@ "activeComment", "authSession", "chatAgents2", - "chatRequestAccess", + "languageModels", "defaultChatAgent", "contribViewsRemote", "contribStatusBarItems", diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2f1aac77d56ef..4e95fc270b096 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1395,24 +1395,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; - // namespace: llm + // namespace: chat const chat: typeof vscode.chat = { registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, - requestLanguageModelAccess(id, options) { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.requestLanguageModelAccess(extension, id, options); - }, - get languageModels() { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.getLanguageModelIds(); - }, - onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension, 'chatRequestAccess'); - return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); - }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); @@ -1427,6 +1415,22 @@ 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(); + }, + onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension, 'languageModels'); + return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); + } + }; + // namespace: speech const speech: typeof vscode.speech = { registerSpeechProvider(id: string, provider: vscode.SpeechProvider) { @@ -1449,6 +1453,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I interactive, l10n, languages, + lm, notebooks, scm, speech, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 879db06731102..526ed79e09246 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -14,7 +14,6 @@ export const allApiProposals = Object.freeze({ chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', - chatRequestAccess: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts', @@ -66,6 +65,7 @@ export const allApiProposals = Object.freeze({ interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', + languageModels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageModels.d.ts', languageStatusText: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatusText.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', multiDocumentHighlightProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts similarity index 98% rename from src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts rename to src/vscode-dts/vscode.proposed.languageModels.d.ts index 633c28dc61ee7..0e35351833b8f 100644 --- a/src/vscode-dts/vscode.proposed.chatRequestAccess.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -152,8 +152,10 @@ declare module 'vscode' { readonly removed: readonly string[]; } - //@API DEFINE the namespace for this: lm (languageModels), copilot, ai, env,? - export namespace chat { + /** + * Namespace for language model related functionality. + */ + export namespace lm { /** * Request access to a language model. From 4f50cc2da005de52ac879866fd00d84d7d638391 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 14 Feb 2024 15:51:39 +0100 Subject: [PATCH 0322/1863] extensions: Match "Extensions" label with website --- src/vs/workbench/contrib/extensions/browser/extensionEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 53f55fa804c67..658f429cff140 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -912,7 +912,7 @@ export class ExtensionEditor extends EditorPane { } if (resources.length || extension.publisherSponsorLink) { const extensionResourcesContainer = append(container, $('.resources-container.additional-details-element')); - append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Extension Resources"))); + append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); for (const [label, uri] of resources) { this.transientDisposables.add(onClick(append(resourcesElement, $('a.resource', { title: uri.toString(), tabindex: '0' }, label)), () => this.openerService.open(uri))); From 474ca46d7bc18ddf5f663fbc7fcf31002ae36554 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Feb 2024 11:53:54 +0100 Subject: [PATCH 0323/1863] Update icons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79504 -> 79468 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 4894dfa316d4a98f4804f67b7840b00e0e20051a..33316df13f2b83c5651215ccccfa5d5e311152fa 100644 GIT binary patch delta 726 zcmbR6mgUVGmI)3#!I!@kFfgd=Ffe?nndoD}av@>TuZbQu$MUj`Z+5HHYuK0BFR;JkAmR|>(BW{yk;~D-vB+_P;}yq0PEAgGoXwmY zTvA--xSVq}agA_Y=EmWc;5NtYk-M4uIu9m~5RWX6Sso8OB|Hs0BRnfSZ+P)|P4oKV z9p>HPeZz;vC(CD+&jDWvUlU&^-#>myer^5?{x$xG0*nH(0u}}Q3e>0%j0k)Yr>`g*%Ga zih7DQiX)0w72he5D9J0?Q1Yi#rnIm0Qkh6uURht+nX(_{LFEM%CKXF6-PkJkRWViB zZPrqMUR%%100Lha1sJ#)_!*QL7|caQ*p$?48BNTL#Mn)ZMb+8#7)^~u}2m_3=HZz3=CfuO!P5fxo$M`*u)lP!7~gDj6VwUi%ZOP6WbXW zWsgkUVZgF3B28lBo82n)CiWfno9y2?s5zuL%yM|;DC6km*y6az@tG5!(-fz3&K}N_ zT*_S5xjb-nbIo$y;U?u);mOH6EWlwLG0Xvpl;zUwO%St?*{^ z&hVb){mMtor^aWE&m~_iUpL0a8&{?5Z!sNng!q$bI3Fio(68}2Z^%DTpiq!du%O^op+Vt-!c#>O zMe~ZSinEIM6u&FcDrqP=RLWPXSGu6|Nts$%L)n6|d*xi^DdkNSZWY@qBiJf0REbmt zY}QhLUR%$^00Lhar5Jb^v>6zLmDpq%MZ}EEY#B||#LY~NMcCN+7}?p_MU_p})b$vF z2q-4UWMs!UO}SIG9<4xy7XAq{SE|6~(=QYCyySK7LMN z4xlV6qna8cD^NLyFeksX7 Date: Thu, 15 Feb 2024 11:55:29 +0100 Subject: [PATCH 0324/1863] theme: Reduce contrast between copilot chat rows --- src/vs/workbench/contrib/chat/common/chatColors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index a11134cac9d3a..385c0cf20c426 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -5,7 +5,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { localize } from 'vs/nls'; -import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, contrastBorder, editorBackground, editorWidgetBackground, foreground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; export const chatRequestBorder = registerColor( 'chat.requestBorder', @@ -15,7 +15,7 @@ export const chatRequestBorder = registerColor( export const chatRequestBackground = registerColor( 'chat.requestBackground', - { dark: editorBackground, light: editorBackground, hcDark: editorWidgetBackground, hcLight: null }, + { dark: transparent(editorBackground, 0.62), light: transparent(editorBackground, 0.62), hcDark: editorWidgetBackground, hcLight: null }, localize('chat.requestBackground', 'The background color of a chat request.') ); From be73f5ea6db824b05c7a6810da2f9749923b06e4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 15 Feb 2024 12:15:03 +0100 Subject: [PATCH 0325/1863] Fix context menu position when zoomed (#205276) * Add codefeature toggle * Fix button size * Fix context menu position when zoomed Also set toggle disabled color --- src/vs/base/common/network.ts | 5 + .../browser/markdownSettingRenderer.ts | 72 +++++++-- .../browser/markdownSettingRenderer.test.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 153 +++++++++++++++++- 4 files changed, 214 insertions(+), 18 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 5dde8ca080b7f..974d0c217439f 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -116,6 +116,11 @@ 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 89101b7134b7c..0deafe532a4e9 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,12 +14,13 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; -const codeSettingRegex = /^/; +const codeSettingRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; private _updatedSettings = new Map(); // setting ID to user's original setting value private _encounteredSettings = new Map(); // setting ID to setting + private _featuredSettings = new Map(); // setting ID to feature value constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -29,12 +30,20 @@ export class SimpleSettingRenderer { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } + get featuredSettingStates(): Map { + const result = new Map(); + for (const [settingId, value] of this._featuredSettings) { + result.set(settingId, this._configurationService.getValue(settingId) === value); + } + return result; + } + getHtmlRenderer(): (html: string) => string { return (html): string => { const match = codeSettingRegex.exec(html); - if (match && match.length === 3) { - const settingId = match[1]; - const rendered = this.render(settingId, match[2]); + if (match && match.length === 4) { + const settingId = match[2]; + const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); if (rendered) { html = html.replace(codeSettingRegex, rendered); } @@ -47,6 +56,10 @@ 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) { @@ -69,7 +82,7 @@ export class SimpleSettingRenderer { } parseValue(settingId: string, value: string): any { - if (value === 'undefined') { + if (value === 'undefined' || value === '') { return undefined; } const setting = this.getSetting(settingId); @@ -88,13 +101,16 @@ export class SimpleSettingRenderer { } } - private render(settingId: string, newValue: string): string | undefined { + private render(settingId: string, newValue: string, asFeature: boolean): string | undefined { const setting = this.getSetting(settingId); if (!setting) { return ''; } - - return this.renderSetting(setting, newValue); + if (asFeature) { + return this.renderFeature(setting, newValue); + } else { + return this.renderSetting(setting, newValue); + } } private viewInSettingsMessage(settingId: string, alreadyDisplayed: boolean) { @@ -149,7 +165,16 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ``; + } + + 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 { @@ -185,7 +210,7 @@ export class SimpleSettingRenderer { const newSettingValue = this.parseValue(uri.authority, uri.path.substring(1)); const currentSettingValue = this._configurationService.inspect(settingId).userValue; - if (newSettingValue && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { + if ((newSettingValue !== undefined) && newSettingValue === currentSettingValue && this._updatedSettings.has(settingId)) { const restoreMessage = this.restorePreviousSettingMessage(settingId); actions.push({ class: undefined, @@ -197,7 +222,7 @@ export class SimpleSettingRenderer { return this.restoreSetting(settingId); } }); - } else if (newSettingValue) { + } else if (newSettingValue !== undefined) { const setting = this.getSetting(settingId); const trySettingMessage = setting ? this.getSettingMessage(setting, newSettingValue) : undefined; @@ -230,7 +255,7 @@ export class SimpleSettingRenderer { return actions; } - async updateSetting(uri: URI, x: number, y: number) { + private showContextMenu(uri: URI, x: number, y: number) { const actions = this.getActions(uri); if (!actions) { return; @@ -244,4 +269,27 @@ 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) { + return this.showContextMenu(uri, x, y); + } else if (uri.scheme === Schemas.codeFeature) { + return this.setFeatureState(uri); + } + } } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 15db5eac97994..0457fbe84698b 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ``); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index f03e00087d372..1ee92fcfdea53 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -117,7 +117,9 @@ export class ReleaseNotesManager { } else if (e.message.type === 'scroll') { this.scrollPosition = e.message.value.scrollPosition; } else if (e.message.type === 'clickSetting') { - this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), e.message.value.x, e.message.value.y); + const x = this._currentReleaseNotes?.webview.container.offsetLeft + e.message.value.x; + const y = this._currentReleaseNotes?.webview.container.offsetTop + e.message.value.y; + this._simpleSettingRenderer.updateSetting(URI.parse(e.message.value.uri), x, y); } })); @@ -221,7 +223,7 @@ export class ReleaseNotesManager { } private async onDidClickLink(uri: URI) { - if (uri.scheme === Schemas.codeSetting) { + if (uri.scheme === Schemas.codeSetting || uri.scheme === Schemas.codeFeature) { // handled in receive message } else { this.addGAParameters(uri, 'ReleaseNotes') @@ -260,7 +262,7 @@ export class ReleaseNotesManager { color: var(--vscode-button-foreground); background-color: var(--vscode-button-background); width: fit-content; - padding: 1px 1px 1px 1px; + padding: 1px 1px 0px 1px; font-size: 12px; overflow: hidden; text-overflow: ellipsis; @@ -298,6 +300,123 @@ export class ReleaseNotesManager { user-select: none; -webkit-user-select: none; } + + .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")}"; + } + + .codefeature-container { + display: flex; + } + + .codefeature { + position: relative; + display: inline-block; + width: 58px; + height: 30px; + } + + .codefeature-container input { + display: none; + } + + .toggle { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--vscode-disabledForeground); + transition: .4s; + border-radius: 30px; + } + + .toggle:before { + position: absolute; + content: ""; + height: 22px; + width: 22px; + left: 4px; + bottom: 4px; + background-color: var(--vscode-editor-foreground); + transition: .4s; + border-radius: 50%; + } + + input:checked+.codefeature > .toggle:before { + transform: translateX(26px); + } + + input:checked+.codefeature > .toggle { + background-color: var(--vscode-button-background); + } + + .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; } @@ -332,6 +451,13 @@ export class ReleaseNotesManager { 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; + } + } } }); @@ -349,8 +475,14 @@ export class ReleaseNotesManager { window.addEventListener('click', event => { const href = event.target.href ?? event.target.parentElement.href ?? event.target.parentElement.parentElement?.href; - if (href && href.startsWith('${Schemas.codeSetting}')) { - vscode.postMessage({ type: 'clickSetting', value: { uri: href, x: event.screenX, y: event.screenY }}); + if (href && (href.startsWith('${Schemas.codeSetting}') || href.startsWith('${Schemas.codeFeature}'))) { + 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; + } + } } }); @@ -371,6 +503,7 @@ export class ReleaseNotesManager { private onDidChangeActiveWebviewEditor(input: WebviewInput | undefined): void { if (input && input === this._currentReleaseNotes) { this.updateCheckboxWebview(); + this.updateFeaturedSettingsWebview(); } } @@ -382,4 +515,14 @@ export class ReleaseNotesManager { }); } } + + private updateFeaturedSettingsWebview() { + if (this._currentReleaseNotes) { + this._currentReleaseNotes.webview.postMessage({ + type: 'setFeaturedSettings', + value: this._simpleSettingRenderer.featuredSettingStates + }); + } + } } + From 756a21701d34506a0d2f8daa8a477177f879c43a Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 15 Feb 2024 11:50:10 +0100 Subject: [PATCH 0326/1863] rename suggestions: arrow down always goes to first candidate --- src/vs/editor/contrib/rename/browser/rename.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 9b0eef5895534..6b9c5285a27cf 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -399,12 +399,6 @@ registerAction2(class FocusNextRenameSuggestion extends Action2 { precondition: CONTEXT_RENAME_INPUT_VISIBLE, keybinding: [ { - when: CONTEXT_RENAME_INPUT_FOCUSED, - primary: KeyCode.Tab, - weight: KeybindingWeight.EditorContrib + 99, - }, - { - when: CONTEXT_RENAME_INPUT_FOCUSED.toNegated(), primary: KeyCode.Tab, secondary: [KeyCode.DownArrow], weight: KeybindingWeight.EditorContrib + 99, From 95a3805aa71801a4318907f156f607079d9c79c7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 15 Feb 2024 13:02:25 +0100 Subject: [PATCH 0327/1863] Polish change setting button in release notes (#205281) --- .../contrib/markdown/browser/markdownSettingRenderer.ts | 2 +- .../markdown/test/browser/markdownSettingRenderer.test.ts | 2 +- .../workbench/contrib/update/browser/releaseNotesEditor.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 0deafe532a4e9..62bd755a1bfad 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -165,7 +165,7 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ``; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 0457fbe84698b..ed9ac26957794 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ``); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 1ee92fcfdea53..de8129bbdf038 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -262,7 +262,7 @@ export class ReleaseNotesManager { color: var(--vscode-button-foreground); background-color: var(--vscode-button-background); width: fit-content; - padding: 1px 1px 0px 1px; + padding: 0px 1px 1px 0px; font-size: 12px; overflow: hidden; text-overflow: ellipsis; @@ -272,11 +272,11 @@ export class ReleaseNotesManager { text-align: center; cursor: pointer; border: 1px solid var(--vscode-button-border, transparent); - line-height: 12px; + line-height: 9px; outline: 1px solid transparent; display: inline-block; margin-top: 3px; - margin-bottom: -6px !important; + margin-bottom: -4px !important; } .codesetting:hover { background-color: var(--vscode-button-hoverBackground); From fc18e59421cb31b40f646eb64c7c32b395a606c4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 15 Feb 2024 13:05:23 +0100 Subject: [PATCH 0328/1863] Allow to dictate by voice into the text editor (fix #205263) (#205264) --- .../lib/stylelint/vscode-known-variables.json | 2 + .../browser/codeEditor.contribution.ts | 1 + .../browser/dictation/editorDictation.css | 43 +++ .../browser/dictation/editorDictation.ts | 274 ++++++++++++++++++ .../electron-sandbox/inlineChatQuickVoice.ts | 1 + 5 files changed, 321 insertions(+) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css create mode 100644 src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 5e0c031b144f3..e9ec91f29ea6b 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -796,6 +796,8 @@ "--vscode-inline-chat-expanded", "--vscode-inline-chat-quick-voice-height", "--vscode-inline-chat-quick-voice-width", + "--vscode-editor-dictation-widget-height", + "--vscode-editor-dictation-widget-width", "--vscode-interactive-session-foreground", "--vscode-interactive-result-editor-background-color", "--vscode-repl-font-family", diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index b858435378cd3..ed593625401d9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -23,3 +23,4 @@ import './toggleWordWrap'; import './emptyTextEditorHint/emptyTextEditorHint'; import './workbenchReferenceSearch'; import './editorLineNumberMenu'; +import './dictation/editorDictation'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css new file mode 100644 index 0000000000000..321f2b0a27130 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .editor-dictation-widget { + background-color: var(--vscode-editor-background); + padding: 2px; + border-radius: 8px; + display: flex; + align-items: center; + box-shadow: 0 4px 8px var(--vscode-widget-shadow); + z-index: 1000; + min-height: var(--vscode-editor-dictation-widget-height); + line-height: var(--vscode-editor-dictation-widget-height); + max-width: var(--vscode-editor-dictation-widget-width); +} + +.monaco-editor .editor-dictation-widget .codicon.codicon-mic-filled { + display: flex; + align-items: center; + width: 16px; + height: 16px; +} + +.monaco-editor .editor-dictation-widget.recording .codicon.codicon-mic-filled { + color: var(--vscode-activityBarBadge-background); + animation: editor-dictation-animation 1s infinite; +} + +@keyframes editor-dictation-animation { + 0% { + color: var(--vscode-editorCursor-background); + } + + 50% { + color: var(--vscode-activityBarBadge-background); + } + + 100% { + color: var(--vscode-editorCursor-background); + } +} diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts new file mode 100644 index 0000000000000..0150448c6bd92 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -0,0 +1,274 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./editorDictation'; +import { localize2 } from 'vs/nls'; +import { IDimension, h, reset } from 'vs/base/browser/dom'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { assertIsDefined } from 'vs/base/common/types'; + +const EDITOR_DICTATION_IN_PROGRESS = new RawContextKey('editorDictation.inProgress', false); +const VOICE_CATEGORY = localize2('voiceCategory', "Voice"); + +export class EditorDictationStartAction extends EditorAction2 { + + constructor() { + super({ + id: 'workbench.action.editorDictation.start', + title: localize2('startDictation', "Start Dictation in Editor"), + category: VOICE_CATEGORY, + precondition: ContextKeyExpr.and(HasSpeechProvider, EDITOR_DICTATION_IN_PROGRESS.toNegated(), EditorContextKeys.readOnly.toNegated()), + f1: true + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id); + if (holdMode) { + let shouldCallStop = false; + + const handle = setTimeout(() => { + shouldCallStop = true; + }, 500); + + holdMode.finally(() => { + clearTimeout(handle); + + if (shouldCallStop) { + EditorDictation.get(editor)?.stop(); + } + }); + } + + EditorDictation.get(editor)?.start(); + } +} + +export class EditorDictationStopAction extends EditorAction2 { + + constructor() { + super({ + id: 'workbench.action.editorDictation.stop', + title: localize2('stopDictation', "Stop Dictation in Editor"), + category: VOICE_CATEGORY, + precondition: EDITOR_DICTATION_IN_PROGRESS, + f1: true, + keybinding: { + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + 100 + } + }); + } + + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void { + EditorDictation.get(editor)?.stop(); + } +} + +export class DictationWidget extends Disposable implements IContentWidget { + + readonly suppressMouseDown = true; + readonly allowEditorOverflow = true; + + private readonly domNode = document.createElement('div'); + private readonly elements = h('.editor-dictation-widget@main', [h('span@mic')]); + + constructor(private readonly editor: ICodeEditor) { + super(); + + this.domNode.appendChild(this.elements.root); + this.domNode.style.zIndex = '1000'; + + reset(this.elements.mic, renderIcon(Codicon.micFilled)); + } + + getId(): string { + return 'editorDictation'; + } + + getDomNode(): HTMLElement { + return this.domNode; + } + + getPosition(): IContentWidgetPosition | null { + if (!this.editor.hasModel()) { + return null; + } + + const selection = this.editor.getSelection(); + + return { + position: selection.getPosition(), + preference: [ + selection.getPosition().equals(selection.getStartPosition()) ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW, + ContentWidgetPositionPreference.EXACT + ] + }; + } + + beforeRender(): IDimension | null { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + const width = this.editor.getLayoutInfo().contentWidth * 0.7; + + this.elements.main.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); + this.elements.main.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); + + return null; + } + + show() { + this.editor.addContentWidget(this); + } + + layout(): void { + this.editor.layoutContentWidget(this); + } + + active(): void { + this.elements.main.classList.add('recording'); + } + + hide() { + this.elements.main.classList.remove('recording'); + this.editor.removeContentWidget(this); + } +} + +export class EditorDictation extends Disposable implements IEditorContribution { + + static readonly ID = 'editorDictation'; + + static get(editor: ICodeEditor): EditorDictation | null { + return editor.getContribution(EditorDictation.ID); + } + + private readonly widget = this._register(new DictationWidget(this.editor)); + private readonly editorDictationInProgress = EDITOR_DICTATION_IN_PROGRESS.bindTo(this.contextKeyService); + + private sessionDisposables = this._register(new MutableDisposable()); + + constructor( + private readonly editor: ICodeEditor, + @ISpeechService private readonly speechService: ISpeechService, + @IContextKeyService private readonly contextKeyService: IContextKeyService + ) { + super(); + } + + start() { + const disposables = new DisposableStore(); + this.sessionDisposables.value = disposables; + + this.widget.show(); + disposables.add(toDisposable(() => this.widget.hide())); + + this.editorDictationInProgress.set(true); + disposables.add(toDisposable(() => this.editorDictationInProgress.reset())); + + const collection = this.editor.createDecorationsCollection(); + disposables.add(toDisposable(() => collection.clear())); + + let previewStart: Position | undefined = undefined; + + let lastReplaceTextLength = 0; + const replaceText = (text: string, isPreview: boolean) => { + if (!previewStart) { + previewStart = assertIsDefined(this.editor.getPosition()); + } + + this.editor.executeEdits(EditorDictation.ID, [ + EditOperation.replace(Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + lastReplaceTextLength)), text) + ], [ + Selection.fromPositions(new Position(previewStart.lineNumber, previewStart.column + text.length)) + ]); + + if (isPreview) { + collection.set([ + { + range: Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + text.length)), + options: { + description: 'editor-dictation-preview', + inlineClassName: 'ghost-text-decoration-preview' + } + } + ]); + } else { + collection.clear(); + } + + lastReplaceTextLength = text.length; + if (!isPreview) { + previewStart = undefined; + lastReplaceTextLength = 0; + } + + this.widget.layout(); + }; + + const cts = new CancellationTokenSource(); + disposables.add(toDisposable(() => cts.dispose(true))); + + const session = disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + disposables.add(session.onDidChange(e => { + if (cts.token.isCancellationRequested) { + return; + } + + switch (e.status) { + case SpeechToTextStatus.Started: + this.widget.active(); + break; + case SpeechToTextStatus.Stopped: + disposables.dispose(); + break; + case SpeechToTextStatus.Recognizing: { + if (!e.text) { + return; + } + + replaceText(e.text, true); + break; + } + case SpeechToTextStatus.Recognized: { + if (!e.text) { + return; + } + + replaceText(`${e.text} `, false); + break; + } + } + })); + } + + stop(): void { + this.sessionDisposables.clear(); + } +} + +registerEditorContribution(EditorDictation.ID, EditorDictation, EditorContributionInstantiation.Lazy); +registerAction2(EditorDictationStartAction); +registerAction2(EditorDictationStopAction); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 60fea0a352c29..ad596b50fe8dd 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -267,6 +267,7 @@ export class InlineChatQuickVoice implements IEditorContribution { const done = (abort: boolean) => { cts.dispose(true); + session.dispose(); listener.dispose(); this._widget.hide(); this._ctxQuickChatInProgress.reset(); From b71d2e5b0ba6d84ae5cec7591e315b2d3049f10c Mon Sep 17 00:00:00 2001 From: Andy Hippo Date: Thu, 15 Feb 2024 13:21:56 +0100 Subject: [PATCH 0329/1863] Fix memory leak in comments browser (#205162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix memory leak in comments browser * 💄 --------- Co-authored-by: Alex Ross --- .../contrib/comments/browser/commentNode.ts | 15 +++++++++------ .../contrib/comments/browser/commentReply.ts | 6 +++++- .../comments/browser/commentThreadWidget.ts | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 3f72b63dad9d6..7085518f94ad8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import * as languages from 'vs/editor/common/languages'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IActionRunner, IAction, Separator, ActionRunner } from 'vs/base/common/actions'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; @@ -576,12 +576,10 @@ export class CommentNode extends Disposable { this._commentEditorModel?.dispose(); - this._commentEditorDisposables.forEach(dispose => dispose.dispose()); + dispose(this._commentEditorDisposables); this._commentEditorDisposables = []; - if (this._commentEditor) { - this._commentEditor.dispose(); - this._commentEditor = null; - } + this._commentEditor?.dispose(); + this._commentEditor = null; this._commentEditContainer!.remove(); } @@ -766,6 +764,11 @@ export class CommentNode extends Disposable { }, 3000); } } + + override dispose(): void { + super.dispose(); + dispose(this._commentEditorDisposables); + } } function fillInActions(groups: [string, Array][], target: IAction[] | { primary: IAction[]; secondary: IAction[] }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 4bf059c42677f..21aeb3f22d5a4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; import { IAction } from 'vs/base/common/actions'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -369,4 +369,8 @@ export class CommentReply extends Disposable { }); } + override dispose(): void { + super.dispose(); + dispose(this._commentThreadDisposables); + } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 548157717f331..bcd9366e5241c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -261,6 +261,7 @@ export class CommentThreadWidget extends override dispose() { super.dispose(); + dispose(this._commentThreadDisposables); this.updateCurrentThread(false, false); } From ed22015fc4c567bf2f1a0b13e2bcc75369d9f572 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 15 Feb 2024 13:28:05 +0100 Subject: [PATCH 0330/1863] Update codicons --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79468 -> 79572 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 33316df13f2b83c5651215ccccfa5d5e311152fa..b34cfdc245c6a94f79b0f22da57ca8fe64b9721d 100644 GIT binary patch delta 5059 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 Rv@eF$-3|@EoiE%-{0m|KT(bZG From f396fe0e2b3e60889883e05c1d09fdf11f42c3d6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:26:25 +0100 Subject: [PATCH 0331/1863] Git - refresh decorators when incoming/outgoing changes (#205282) --- extensions/git/src/decorationProvider.ts | 4 ++-- extensions/git/src/historyProvider.ts | 19 +++---------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 943af377eb735..9e3e356628d06 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -163,10 +163,10 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider constructor(private readonly repository: Repository) { this.disposables.push(window.registerFileDecorationProvider(this)); - repository.historyProvider.onDidChangeCurrentHistoryItemGroupBase(this.onDidChangeCurrentHistoryItemGroupBase, this, this.disposables); + repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); } - private async onDidChangeCurrentHistoryItemGroupBase(): Promise { + private async onDidChangeCurrentHistoryItemGroup(): Promise { const newDecorations = new Map(); await this.collectIncomingChangesFileDecorations(newDecorations); const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 64230ecf46bfb..d7f547b6b038c 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -17,9 +17,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; - private readonly _onDidChangeCurrentHistoryItemGroupBase = new EventEmitter(); - readonly onDidChangeCurrentHistoryItemGroupBase: Event = this._onDidChangeCurrentHistoryItemGroupBase.event; - private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; @@ -29,8 +26,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { this._currentHistoryItemGroup = value; this._onDidChangeCurrentHistoryItemGroup.fire(); - - this.logger.trace('GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup:', JSON.stringify(value)); } private historyItemDecorations = new Map(); @@ -59,12 +54,13 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return; } + this._HEAD = this.repository.HEAD; + // Check if HEAD does not support incoming/outgoing (detached commit, tag) if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit || this.repository.HEAD.type === RefType.Tag) { this.logger.trace('GitHistoryProvider:onDidRunGitStatus - HEAD does not support incoming/outgoing'); this.currentHistoryItemGroup = undefined; - this._HEAD = this.repository.HEAD; return; } @@ -78,16 +74,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } : undefined }; - // Check if Upstream has changed - if (force || - this._HEAD?.upstream?.name !== this.repository.HEAD?.upstream?.name || - this._HEAD?.upstream?.remote !== this.repository.HEAD?.upstream?.remote || - this._HEAD?.upstream?.commit !== this.repository.HEAD?.upstream?.commit) { - this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - Upstream has changed (${force})`); - this._onDidChangeCurrentHistoryItemGroupBase.fire(); - } - - this._HEAD = this.repository.HEAD; + this.logger.trace(`GitHistoryProvider:onDidRunGitStatus - currentHistoryItemGroup (${force}): ${JSON.stringify(this.currentHistoryItemGroup)}`); } async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { From 4f9a706c7913884c65756edbf441cd7f801442e8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:33:02 +0100 Subject: [PATCH 0332/1863] Git - add command to close all unmodified editors (#205278) * Git - add command to close all unmodified tabs * Fix compilation error --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f35aa7989ad0d..4e26cff75f4ef 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -789,6 +789,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.closeAllUnmodifiedEditors", + "title": "%command.closeAllUnmodifiedEditors%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.api.getRepositories", "title": "%command.api.getRepositories%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 28ae736038ffd..f6bbb18454ddc 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -33,6 +33,7 @@ "command.cleanAllTracked": "Discard All Tracked Changes", "command.cleanAllUntracked": "Discard All Untracked Changes", "command.closeAllDiffEditors": "Close All Diff Editors", + "command.closeAllUnmodifiedEditors": "Close All Unmodified Editors", "command.commit": "Commit", "command.commitAmend": "Commit (Amend)", "command.commitSigned": "Commit (Signed Off)", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3f46f698ce22f..7f7f769fec5fa 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -3994,6 +3994,37 @@ export class CommandCenter { repository.closeDiffEditors(undefined, undefined, true); } + @command('git.closeAllUnmodifiedEditors') + closeUnmodifiedEditors(): void { + const editorTabsToClose: Tab[] = []; + + // Collect all modified files + const modifiedFiles: string[] = []; + for (const repository of this.model.repositories) { + modifiedFiles.push(...repository.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath)); + modifiedFiles.push(...repository.mergeGroup.resourceStates.map(r => r.resourceUri.fsPath)); + } + + // Collect all editor tabs that are not dirty and not modified + for (const tab of window.tabGroups.all.map(g => g.tabs).flat()) { + if (tab.isDirty) { + continue; + } + + if (tab.input instanceof TabInputText || tab.input instanceof TabInputNotebook) { + const { uri } = tab.input; + if (!modifiedFiles.find(p => pathEquals(p, uri.fsPath))) { + editorTabsToClose.push(tab); + } + } + } + + // Close editors + window.tabGroups.close(editorTabsToClose, true); + } + @command('git.openRepositoriesInParentFolders') async openRepositoriesInParentFolders(): Promise { const parentRepositories: string[] = []; From 80b556e8fbc741214c54e53329442d2de782bd8f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:54:17 +0100 Subject: [PATCH 0333/1863] SCM - do not show action for selected rows (#205292) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index c1c3290d585a5..fee45b4e002a3 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -267,20 +267,15 @@ } .scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions, -.scm-view .monaco-list .monaco-list-row.selected .resource-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource-group > .actions, .scm-view .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions, -.scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, .scm-view .monaco-list .monaco-list-row:hover .separator-container > .actions, -.scm-view .monaco-list .monaco-list-row.selected .separator-container > .actions, .scm-view .monaco-list .monaco-list-row.focused .separator-container > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item-group > .actions, -.scm-view .monaco-list .monaco-list-row.selected .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item-group > .actions, .scm-view .monaco-list .monaco-list-row:hover .history-item > .actions, -.scm-view .monaco-list .monaco-list-row.selected .history-item > .actions, .scm-view .monaco-list .monaco-list-row.focused .history-item > .actions { display: block; } From 7215958b3c57945b49d3b70afdba7fb47319ca85 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 15 Feb 2024 15:57:24 +0100 Subject: [PATCH 0334/1863] add commands to navigate between hunks (#205293) https://github.com/microsoft/vscode/issues/203349 --- .../contrib/zoneWidget/browser/zoneWidget.ts | 5 +++ .../browser/inlineChat.contribution.ts | 2 + .../inlineChat/browser/inlineChatActions.ts | 40 +++++++++++++++++++ .../browser/inlineChatController.ts | 5 +++ .../browser/inlineChatStrategies.ts | 33 ++++++++++++++- 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index d51aaf5eb5c49..2ef43ea18a454 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -331,6 +331,11 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.editor.changeViewZones(accessor => { accessor.layoutZone(this._viewZone!.id); }); + this._positionMarkerId.set([{ + range: Range.isIRange(rangeOrPos) ? rangeOrPos : Range.fromPositions(rangeOrPos), + options: ModelDecorationOptions.EMPTY + }]); + } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index cc8dc4ca93d70..ea70b146a8c67 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -42,6 +42,8 @@ registerAction2(InlineChatActions.DiscardAction); registerAction2(InlineChatActions.DiscardToClipboardAction); registerAction2(InlineChatActions.DiscardUndoToNewFileAction); registerAction2(InlineChatActions.CancelSessionAction); +registerAction2(InlineChatActions.MoveToNextHunk); +registerAction2(InlineChatActions.MoveToPreviousHunk); registerAction2(InlineChatActions.ArrowOutUpAction); registerAction2(InlineChatActions.ArrowOutDownAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 85033c6416685..8deb70a073e2b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -640,6 +640,46 @@ export class ConfigureInlineChatAction extends AbstractInlineChatAction { } } +export class MoveToNextHunk extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.moveToNextHunk', + title: localize2('moveToNextHunk', 'Move to Next Change'), + precondition: CTX_INLINE_CHAT_VISIBLE, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.F7 + } + }); + } + + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + ctrl.moveHunk(true); + } +} + +export class MoveToPreviousHunk extends AbstractInlineChatAction { + + constructor() { + super({ + id: 'inlineChat.moveToPreviousHunk', + title: localize2('moveToPreviousHunk', 'Move to Previous Change'), + f1: true, + precondition: CTX_INLINE_CHAT_VISIBLE, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift | KeyCode.F7 + } + }); + } + + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + ctrl.moveHunk(false); + } +} + export class CopyRecordings extends AbstractInlineChatAction { constructor() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 781e4477dc966..ce432473cfbb5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1057,6 +1057,11 @@ export class InlineChatController implements IEditorContribution { return this._zone.value.widget.hasFocus(); } + moveHunk(next: boolean) { + this.focus(); + this._strategy?.move?.(next); + } + populateHistory(up: boolean) { const len = InlineChatController._promptHistory.length; if (len === 0) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 56777b09ba229..78cfd2a3bc52f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -123,6 +123,8 @@ export abstract class EditModeStrategy { abstract renderChanges(response: ReplyResponse): Promise; + move?(next: boolean): void; + abstract hasFocus(): boolean; getWholeRangeDecoration(): IModelDeltaDecoration[] { @@ -401,6 +403,7 @@ type HunkDisplayData = { discardHunk: () => void; toggleDiff?: () => any; remove(): void; + move: (next: boolean) => void; }; @@ -429,7 +432,6 @@ export class LiveStrategy extends EditModeStrategy { private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; - override acceptHunk: () => Promise = () => super.acceptHunk(); override discardHunk: () => Promise = () => super.discardHunk(); @@ -617,6 +619,33 @@ export class LiveStrategy extends EditModeStrategy { }); }; + const move = (next: boolean) => { + assertType(widgetData); + + const candidates: Position[] = []; + for (const item of this._session.hunkData.getInfo()) { + if (item.getState() === HunkState.Pending) { + candidates.push(item.getRangesN()[0].getStartPosition().delta(-1)); + } + } + if (candidates.length < 2) { + return; + } + for (let i = 0; i < candidates.length; i++) { + if (candidates[i].equals(widgetData.position)) { + let newPos: Position; + if (next) { + newPos = candidates[(i + 1) % candidates.length]; + } else { + newPos = candidates[(i + candidates.length - 1) % candidates.length]; + } + this._zone.updatePositionAndHeight(newPos); + renderHunks(); + break; + } + } + }; + const zoneLineNumber = this._zone.position!.lineNumber; const myDistance = zoneLineNumber <= hunkRanges[0].startLineNumber ? hunkRanges[0].startLineNumber - zoneLineNumber @@ -632,6 +661,7 @@ export class LiveStrategy extends EditModeStrategy { discardHunk, toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined, remove, + move }; this._hunkDisplayData.set(hunkData, data); @@ -674,6 +704,7 @@ export class LiveStrategy extends EditModeStrategy { this.toggleDiff = widgetData.toggleDiff; this.acceptHunk = async () => widgetData!.acceptHunk(); this.discardHunk = async () => widgetData!.discardHunk(); + this.move = next => widgetData!.move(next); } else if (this._hunkDisplayData.size > 0) { // everything accepted or rejected From b01c303b90fe931c2f4e816172fa8fdaf13ee963 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 10:50:32 -0600 Subject: [PATCH 0335/1863] 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 693fde05babfd..678c8177976ee 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 641737229dcb2..7aef66f56313c 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 515d7b895e816..944fba8dbba1d 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 a485dd40c4faa..bbe822e01a67a 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 0336/1863] 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 70804842c3eaa..0c2dba5003528 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 5bbe76db85f29..2fe29ea5d8ab5 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 0337/1863] 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 944fba8dbba1d..86c17c99d22b0 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 bbe822e01a67a..67d671aae2860 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 0338/1863] 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 633b6778dfe35..a916f2dd0d790 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 67d671aae2860..9f4f960ee5eba 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 0339/1863] 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 678c8177976ee..693fde05babfd 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 7aef66f56313c..641737229dcb2 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 86c17c99d22b0..54430cd8feef8 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 9f4f960ee5eba..39892d8a3810b 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 0340/1863] 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 641737229dcb2..a1561e9b63cb0 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 54430cd8feef8..6daeb2bd3cb36 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 0341/1863] 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 6daeb2bd3cb36..bfafbb102fc44 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 0342/1863] 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 f75c9c3d9a7da..d409361d517a5 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 2fe29ea5d8ab5..d851609de14ad 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 a1561e9b63cb0..901f536f0bfd5 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 bfafbb102fc44..903c81b74fbfa 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 39892d8a3810b..53a67093ffb61 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 0343/1863] 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 903c81b74fbfa..d0d9590813ef1 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 0344/1863] 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 d0d9590813ef1..6f292f51fde5e 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 0345/1863] 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 6f292f51fde5e..2585ae766da74 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 0346/1863] 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 a916f2dd0d790..1feb4831c23c1 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 901f536f0bfd5..50c1587b12441 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 53a67093ffb61..637e2f77104d9 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 0347/1863] 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 50c1587b12441..19d9fdf140c3f 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 e4e853fecf4e83033bade6bad9ea74c6f58dd1ef Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:25:45 +0100 Subject: [PATCH 0348/1863] Workbench hover to align and correctly apply delay setting (#204871) * Workbench hover * :lipstick: * placement element for tabs and breadcrumbs * enableInstantHoverAfterRecentlyShown * :lipstick: * adobt custom hover in all toolbars and labels * dropdown hover * widgets * dispose hover delegate uf own instance * testing hover delegate (might be a better way of doing this?) * nullHoverDelegateFactory * registering the disposables and handing down options * Hygiene * fix quickinput --- .../browser/ui/actionbar/actionViewItems.ts | 17 +++--- src/vs/base/browser/ui/actionbar/actionbar.ts | 6 +- src/vs/base/browser/ui/button/button.ts | 15 ++++- src/vs/base/browser/ui/dropdown/dropdown.ts | 10 +++- .../ui/dropdown/dropdownActionViewItem.ts | 8 ++- src/vs/base/browser/ui/hover/hoverDelegate.ts | 35 +++++++++++ .../browser/ui/iconLabel/iconHoverDelegate.ts | 2 + src/vs/base/browser/ui/iconLabel/iconLabel.ts | 17 +++--- src/vs/base/browser/ui/inputbox/inputBox.ts | 9 ++- .../browser/ui/selectBox/selectBoxCustom.ts | 13 +++-- src/vs/base/browser/ui/table/tableWidget.ts | 13 +++-- src/vs/base/browser/ui/toggle/toggle.ts | 7 ++- src/vs/base/browser/ui/toolbar/toolbar.ts | 11 +++- .../diffEditorItemTemplate.ts | 2 +- .../quickInput/standaloneQuickInputService.ts | 3 - .../browser/standaloneCodeEditor.ts | 4 ++ .../dropdownWithPrimaryActionViewItem.ts | 12 ++-- .../browser/menuEntryActionViewItem.ts | 2 +- src/vs/platform/hover/browser/hover.ts | 58 ++++++++++++++++++- .../platform/quickinput/browser/quickInput.ts | 36 ++++-------- .../quickinput/browser/quickInputService.ts | 4 +- src/vs/workbench/browser/composite.ts | 3 +- src/vs/workbench/browser/panecomposite.ts | 5 +- .../workbench/browser/parts/compositePart.ts | 16 +++-- .../parts/editor/breadcrumbsControl.ts | 16 +---- .../browser/parts/editor/editorTabsControl.ts | 28 ++++----- .../parts/editor/multiEditorTabsControl.ts | 6 +- .../browser/parts/globalCompositeBar.ts | 5 ++ .../browser/parts/paneCompositePart.ts | 7 ++- .../browser/parts/statusbar/statusbarPart.ts | 56 +++++------------- .../browser/parts/titlebar/titlebarPart.ts | 50 +++++----------- .../workbench/browser/parts/views/treeView.ts | 9 +-- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 7 ++- src/vs/workbench/browser/workbench.ts | 6 ++ .../browser/outline/documentSymbolsOutline.ts | 2 +- .../browser/outline/documentSymbolsTree.ts | 18 ++---- .../contrib/debug/browser/callStackView.ts | 4 +- .../debug/browser/debugActionViewItems.ts | 5 +- .../contrib/debug/browser/debugToolBar.ts | 13 +++-- .../contrib/debug/browser/debugViewlet.ts | 9 +-- .../extensions/browser/extensionsViewlet.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 4 +- .../browser/view/cellParts/cellToolbars.ts | 8 +-- .../contrib/remote/browser/tunnelView.ts | 23 ++++---- .../contrib/scm/browser/scmViewPane.ts | 6 +- src/vs/workbench/contrib/scm/browser/util.ts | 12 ++-- .../terminal/browser/terminalEditor.ts | 7 ++- .../contrib/terminal/browser/terminalView.ts | 10 ++-- .../testing/browser/testingExplorerView.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 11 +--- .../parts/titlebar/titlebarPart.ts | 10 +--- .../quickinput/browser/quickInputService.ts | 4 +- .../test/browser/workbenchTestServices.ts | 24 +------- 54 files changed, 356 insertions(+), 318 deletions(-) create mode 100644 src/vs/base/browser/ui/hover/hoverDelegate.ts diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 906c437b3259c..804b0c386acdb 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -9,6 +9,7 @@ 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 { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -224,16 +225,14 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - if (!this.options.hoverDelegate) { - this.element.title = title; + + this.element.title = ''; + if (!this.customHover) { + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this.customHover = setupCustomHover(hoverDelegate, this.element, title); + this._store.add(this.customHover); } else { - this.element.title = ''; - if (!this.customHover) { - this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title); - this._store.add(this.customHover); - } else { - this.customHover.update(title); - } + 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 fafc3417f8b98..9ba16500c8ff0 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -6,6 +6,7 @@ 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 { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -67,6 +68,7 @@ export interface IActionOptions extends IActionViewItemOptions { export class ActionBar extends Disposable implements IActionRunner { private readonly options: IActionBarOptions; + private readonly _hoverDelegate: IHoverDelegate; private _actionRunner: IActionRunner; private readonly _actionRunnerDisposables = this._register(new DisposableStore()); @@ -117,6 +119,8 @@ 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)); + if (this.options.actionRunner) { this._actionRunner = this.options.actionRunner; } else { @@ -358,7 +362,7 @@ export class ActionBar extends Disposable implements IActionRunner { let item: IActionViewItem | undefined; - const viewItemOptions = { hoverDelegate: this.options.hoverDelegate, ...options }; + const viewItemOptions = { hoverDelegate: this._hoverDelegate, ...options }; if (this.options.actionViewItemProvider) { item = this.options.actionViewItemProvider(action, viewItemOptions); } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 3b1ad7915bb20..4675ee8c5eed9 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -9,6 +9,8 @@ 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 { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; 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'; @@ -74,6 +76,7 @@ export class Button extends Disposable implements IButton { protected _label: string | IMarkdownString = ''; protected _labelElement: HTMLElement | undefined; protected _labelShortElement: HTMLElement | undefined; + private _hover: ICustomHover | undefined; private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } @@ -240,10 +243,16 @@ export class Button extends Disposable implements IButton { } } + let title: string = ''; if (typeof this.options.title === 'string') { - this._element.title = this.options.title; + title = this.options.title; } else if (this.options.title) { - this._element.title = renderStringAsPlaintext(value); + title = renderStringAsPlaintext(value); + } + if (!this._hover) { + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._element, title)); + } else { + this._hover.update(title); } if (typeof this.options.ariaLabel === 'string') { @@ -348,7 +357,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.separator.style.backgroundColor = options.buttonSeparator ?? ''; this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); - this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.dropdownButton.element, localize("button dropdown more actions", 'More Actions...'))); this.dropdownButton.element.setAttribute('aria-haspopup', 'true'); this.dropdownButton.element.setAttribute('aria-expanded', 'false'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 8fd8867058a95..88dfaf2c5b14d 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -8,6 +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 { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -34,6 +36,8 @@ class BaseDropdown extends ActionRunner { private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + private hover: ICustomHover | undefined; + constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -101,7 +105,11 @@ class BaseDropdown extends ActionRunner { set tooltip(tooltip: string) { if (this._label) { - this._label.title = tooltip; + if (!this.hover) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._label, tooltip)); + } else { + 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 a5fee4835b36f..419658a21bbc4 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -19,6 +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'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -90,7 +92,9 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.setAttribute('role', 'button'); this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); - this.element.title = this._action.label || ''; + if (this._action.label) { + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, this._action.label)); + } this.element.ariaLabel = this._action.label || ''; return null; @@ -203,7 +207,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { separator.classList.toggle('prominent', menuActionClassNames.includes('prominent')); append(this.element, separator); - this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] })); + this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames], hoverDelegate: this.options.hoverDelegate })); this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts new file mode 100644 index 0000000000000..6682d739c269d --- /dev/null +++ b/src/vs/base/browser/ui/hover/hoverDelegate.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/iconLabel/iconHoverDelegate'; +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; +export function getDefaultHoverDelegate(placement: 'mouse' | '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); + } + + if (placement === 'element') { + return defaultHoverDelegateElement.value; + } + return defaultHoverDelegateMouse.value; +} diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 74fcd97d4a95e..d6d9096b5f1de 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -64,6 +64,8 @@ export interface IHoverDelegate { placement?: 'mouse' | 'element'; } +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 f95a11c9c4448..6a7edc12dcfd0 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -7,11 +7,12 @@ 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 { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; 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'; export interface IIconLabelCreationOptions { readonly supportHighlights?: boolean; @@ -94,7 +95,7 @@ export class IconLabel extends Disposable { private readonly labelContainer: HTMLElement; - private readonly hoverDelegate: IHoverDelegate | undefined; + private readonly hoverDelegate: IHoverDelegate; private readonly customHovers: Map = new Map(); constructor(container: HTMLElement, options?: IIconLabelCreationOptions) { @@ -113,7 +114,7 @@ export class IconLabel extends Disposable { this.nameNode = new Label(this.nameContainer); } - this.hoverDelegate = options?.hoverDelegate; + this.hoverDelegate = options?.hoverDelegate ?? getDefaultHoverDelegate('mouse'); } get element(): HTMLElement { @@ -186,13 +187,9 @@ export class IconLabel extends Disposable { return; } - if (!this.hoverDelegate) { - setupNativeHover(htmlElement, tooltip); - } else { - const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); - if (hoverDisposable) { - this.customHovers.set(htmlElement, hoverDisposable); - } + const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); + if (hoverDisposable) { + this.customHovers.set(htmlElement, hoverDisposable); } } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e4c89dd3affb6..a23848dd914e9 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,6 +11,8 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, 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 { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; @@ -111,6 +113,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; + private hover: ICustomHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -230,7 +233,11 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - this.input.title = tooltip; + if (!this.hover) { + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.input, tooltip)); + } else { + this.hover.update(tooltip); + } } public setAriaLabel(label: string): void { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 83073ee2666f7..ca6aaa3350556 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -9,6 +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 { 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'; @@ -101,6 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _cachedMaxDetailsHeight?: number; + private _hover: ICustomHover; private _sticky: boolean = false; // for dev purposes only @@ -131,6 +134,8 @@ 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); @@ -199,7 +204,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi selected: e.target.value }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } })); @@ -309,7 +314,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.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } } @@ -837,7 +842,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi }); if (!!this.options[this.selected] && !!this.options[this.selected].text) { - this.selectElement.title = this.options[this.selected].text; + this._hover.update(this.options[this.selected].text); } } @@ -936,7 +941,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.selectElement.title = this.options[this.selected].text; + this._hover.update(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 6e20fd6e34a68..536fb25608e3a 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ 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 { 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'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import 'vs/css!./table'; @@ -115,7 +117,7 @@ function asListVirtualDelegate(delegate: ITableVirtualDelegate): ILi }; } -class ColumnHeader implements IView { +class ColumnHeader extends Disposable implements IView { readonly element: HTMLElement; @@ -127,7 +129,10 @@ class ColumnHeader implements IView { readonly onDidLayout = this._onDidLayout.event; constructor(readonly column: ITableColumn, private index: number) { - this.element = $('.monaco-table-th', { 'data-col-index': index, title: column.tooltip }, column.label); + super(); + + this.element = $('.monaco-table-th', { 'data-col-index': index }, column.label); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, column.tooltip)); } layout(size: number): void { @@ -191,7 +196,7 @@ export class Table implements ISpliceable, IDisposable { ) { this.domNode = append(container, $(`.monaco-table.${this.domId}`)); - const headers = columns.map((c, i) => new ColumnHeader(c, i)); + const headers = columns.map((c, i) => this.disposables.add(new ColumnHeader(c, i))); const descriptor: ISplitViewDescriptor = { size: headers.reduce((a, b) => a + b.column.weight, 0), views: headers.map(view => ({ size: view.column.weight, view })) diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 4146f24d141a7..54b9130d9e4f5 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -13,6 +13,8 @@ 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'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; @@ -107,6 +109,7 @@ export class Toggle extends Widget { readonly domNode: HTMLElement; private _checked: boolean; + private _hover: ICustomHover; constructor(opts: IToggleOpts) { super(); @@ -127,7 +130,7 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this.domNode.title = this._opts.title; + this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; @@ -213,7 +216,7 @@ export class Toggle extends Widget { } setTitle(newTitle: string): void { - this.domNode.title = newTitle; + this._hover.update(newTitle); this.domNode.setAttribute('aria-label', newTitle); } } diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 2f46a69235770..f92369cddfb94 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -15,6 +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'; @@ -30,6 +32,7 @@ export interface IToolBarOptions { moreIcon?: ThemeIcon; allowContextMenu?: boolean; skipTelemetry?: boolean; + hoverDelegate?: IHoverDelegate; /** * If true, toggled primary items are highlighted with a background color. @@ -57,6 +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)); this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; @@ -72,6 +76,7 @@ export class ToolBar extends Disposable { actionRunner: options.actionRunner, allowContextMenu: options.allowContextMenu, highlightToggledItems: options.highlightToggledItems, + hoverDelegate: options.hoverDelegate, actionViewItemProvider: (action, viewItemOptions) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( @@ -86,7 +91,8 @@ export class ToolBar extends Disposable { anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, skipTelemetry: this.options.skipTelemetry, - isMenu: true + isMenu: true, + hoverDelegate: this.options.hoverDelegate } ); this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); @@ -115,7 +121,8 @@ export class ToolBar extends Disposable { classNames: action.class, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, - skipTelemetry: this.options.skipTelemetry + skipTelemetry: this.options.skipTelemetry, + hoverDelegate: this.options.hoverDelegate } ); result.setActionContext(this.actionBar.context); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index da71ca719f758..8c44f6e41819e 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -148,7 +148,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< shouldForwardArgs: true, }, toolbarOptions: { primaryGroup: g => g.startsWith('navigation') }, - actionViewItemProvider: action => createActionViewItem(_instantiationService, action), + actionViewItemProvider: (action, options) => createActionViewItem(_instantiationService, action, options), })); } diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 75a3520379b08..0c94d2824e984 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -20,7 +20,6 @@ import { QuickInputService } from 'vs/platform/quickinput/browser/quickInputServ import { createSingleCallFunction } from 'vs/base/common/functional'; import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; class EditorScopedQuickInputService extends QuickInputService { @@ -33,7 +32,6 @@ class EditorScopedQuickInputService extends QuickInputService { @IThemeService themeService: IThemeService, @ICodeEditorService codeEditorService: ICodeEditorService, @IConfigurationService configurationService: IConfigurationService, - @IHoverService hoverService: IHoverService, ) { super( instantiationService, @@ -41,7 +39,6 @@ class EditorScopedQuickInputService extends QuickInputService { themeService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService), configurationService, - hoverService ); // Use the passed in code editor as host for the quick input widget diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 739e76f97c22f..479bb75745c4e 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -39,6 +39,8 @@ 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 { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; /** * Description of an action contribution @@ -289,6 +291,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon } createAriaDomNode(options.ariaContainerElement); + + setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, enableInstantHover, {})); } public addCommand(keybinding: number, handler: ICommandHandler, context?: string): string | null { diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index 99e25133cd8b8..cfcc0e2c73b4f 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -19,10 +19,12 @@ 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'; export interface IDropdownWithPrimaryActionViewItemOptions { actionRunner?: IActionRunner; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; + hoverDelegate?: IHoverDelegate; } export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { @@ -48,8 +50,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { @IThemeService _themeService: IThemeService, @IAccessibilityService _accessibilityService: IAccessibilityService ) { - super(null, primaryAction); - this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider, _accessibilityService); + super(null, primaryAction, { hoverDelegate: _options?.hoverDelegate }); + this._primaryAction = new MenuEntryActionViewItem(primaryAction, { hoverDelegate: _options?.hoverDelegate }, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider, _accessibilityService); if (_options?.actionRunner) { this._primaryAction.actionRunner = _options.actionRunner; } @@ -58,7 +60,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { menuAsChild: true, classNames: className ? ['codicon', 'codicon-chevron-down', className] : ['codicon', 'codicon-chevron-down'], actionRunner: this._options?.actionRunner, - keybindingProvider: this._options?.getKeyBinding + keybindingProvider: this._options?.getKeyBinding, + hoverDelegate: _options?.hoverDelegate }); } @@ -130,7 +133,8 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { this._dropdown.dispose(); this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { menuAsChild: true, - classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'] + classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'], + hoverDelegate: this._options?.hoverDelegate }); if (this._dropdownContainer) { this._dropdown.render(this._dropdownContainer); diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 8374e100d3cb6..c1edd287bea6f 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -515,7 +515,7 @@ class SubmenuEntrySelectActionViewItem extends SelectActionViewItem { /** * Creates action view items for menu actions or submenu actions. */ -export function createActionViewItem(instaService: IInstantiationService, action: IAction, options?: IDropdownMenuActionViewItemOptions | IMenuEntryActionViewItemOptions): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem { +export function createActionViewItem(instaService: IInstantiationService, action: IAction, options: IDropdownMenuActionViewItemOptions | IMenuEntryActionViewItemOptions | undefined): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem { if (action instanceof MenuItemAction) { return instaService.createInstance(MenuEntryActionViewItem, action, options); } else if (action instanceof SubmenuItemAction) { diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 5f3bb1e331712..5bbd3ff00b978 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IHoverService = createDecorator('hoverService'); @@ -229,3 +230,56 @@ export interface IHoverTarget extends IDisposable { */ y?: number; } + +export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate { + + private lastHoverHideTime = Number.MAX_VALUE; + private timeLimit = 200; + + + private _delay: number; + get delay(): number { + if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { + return 0; // show instantly when a hover was recently shown + } + return this._delay; + } + + constructor( + public readonly placement: 'mouse' | 'element', + private readonly instantHover: boolean, + private overrideOptions: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial) = {}, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IHoverService private readonly hoverService: IHoverService, + ) { + super(); + + this._delay = this.configurationService.getValue('workbench.hover.delay'); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.hover.delay')) { + this._delay = this.configurationService.getValue('workbench.hover.delay'); + } + })); + } + + showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + const overrideOptions = typeof this.overrideOptions === 'function' ? this.overrideOptions(options, focus) : this.overrideOptions; + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + }, + ...overrideOptions + }, focus); + } + + setOptions(options: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial)): void { + this.overrideOptions = options; + } + + onDidHideHover(): void { + if (this.instantHover) { + this.lastHoverHideTime = Date.now(); + } + } +} diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 79e8b3d6aa150..f23f8447a02b4 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, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; 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'; @@ -31,7 +31,7 @@ import { QuickInputBox } from './quickInputBox'; import { QuickInputList, QuickInputListFocus } from './quickInputList'; import { quickInputButtonToAction, renderQuickInputDescription } from './quickInputUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverOptions, IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; export interface IQuickInputOptions { idPrefix: string; @@ -1258,24 +1258,16 @@ export class QuickWidget extends QuickInput implements IQuickWidget { } } -export class QuickInputHoverDelegate implements IHoverDelegate { - private lastHoverHideTime = 0; - readonly placement = 'element'; - - get delay() { - if (Date.now() - this.lastHoverHideTime < 200) { - return 0; // show instantly when a hover was recently shown - } - - return this.configurationService.getValue('workbench.hover.delay'); - } +export class QuickInputHoverDelegate extends WorkbenchHoverDelegate { constructor( - private readonly configurationService: IConfigurationService, - private readonly hoverService: IHoverService - ) { } + @IConfigurationService configurationService: IConfigurationService, + @IHoverService hoverService: IHoverService + ) { + super('mouse', true, (options) => this.getOverrideOptions(options), configurationService, hoverService); + } - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { + private getOverrideOptions(options: IHoverDelegateOptions): Partial { // Only show the hover hint if the content is of a decent size const showHoverHint = ( options.content instanceof HTMLElement @@ -1284,8 +1276,8 @@ export class QuickInputHoverDelegate implements IHoverDelegate { ? options.content : options.content.value ).includes('\n'); - return this.hoverService.showHover({ - ...options, + + return { persistence: { hideOnKeyDown: false, }, @@ -1293,10 +1285,6 @@ export class QuickInputHoverDelegate implements IHoverDelegate { showHoverHint, skipFadeInAnimation: true, }, - }, focus); - } - - onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); + }; } } diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index c36470d2a95e4..c2d178c4c463a 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -21,7 +21,6 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { IQuickInputOptions, IQuickInputStyles, QuickInputHoverDelegate } from './quickInput'; import { QuickInputController, IQuickInputControllerHost } from 'vs/platform/quickinput/browser/quickInputController'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { getWindow } from 'vs/base/browser/dom'; export class QuickInputService extends Themable implements IQuickInputService { @@ -64,7 +63,6 @@ export class QuickInputService extends Themable implements IQuickInputService { @IThemeService themeService: IThemeService, @ILayoutService protected readonly layoutService: ILayoutService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IHoverService private readonly hoverService: IHoverService ) { super(themeService); } @@ -92,7 +90,7 @@ export class QuickInputService extends Themable implements IQuickInputService { options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, styles: this.computeStyles(), - hoverDelegate: new QuickInputHoverDelegate(this.configurationService, this.hoverService) + hoverDelegate: this.instantiationService.createInstance(QuickInputHoverDelegate) }; const controller = this._register(new QuickInputController({ diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index ab28dbb003b30..59eba8e11ffa6 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -17,6 +17,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; /** * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite @@ -204,7 +205,7 @@ export abstract class Composite extends Component implements IComposite { * of an action. Returns undefined to indicate that the action is not rendered through * an action item. */ - getActionViewItem(action: IAction): IActionViewItem | undefined { + getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { return undefined; } diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index b829c9f3d7761..89e701e2d0bec 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -22,6 +22,7 @@ import { IView } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { VIEWPANE_FILTER_ACTION } from 'vs/workbench/browser/parts/views/viewPane'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export abstract class PaneComposite extends Composite implements IPaneComposite { @@ -140,8 +141,8 @@ export abstract class PaneComposite extends Composite implements IPaneComposite return menuActions.length ? menuActions : viewPaneActions; } - override getActionViewItem(action: IAction): IActionViewItem | undefined { - return this.viewPaneContainer?.getActionViewItem(action); + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { + return this.viewPaneContainer?.getActionViewItem(action, options); } override getTitle(): string { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index de055f7b032ba..b4d406a636487 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -32,6 +32,9 @@ import { AbstractProgressScope, ScopedProgressIndicator } from 'vs/workbench/ser 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'; export interface ICompositeTitleLabel { @@ -59,6 +62,7 @@ export abstract class CompositePart extends Part { protected toolBar: WorkbenchToolBar | undefined; protected titleLabelElement: HTMLElement | undefined; + protected readonly hoverDelegate: IHoverDelegate; private readonly mapCompositeToCompositeContainer = new Map(); private readonly mapActionsBindingToComposite = new Map void>(); @@ -92,6 +96,7 @@ export abstract class CompositePart extends Part { super(id, options, themeService, storageService, layoutService); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); + this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); } protected openComposite(id: string, focus?: boolean): Composite | undefined { @@ -396,12 +401,13 @@ export abstract class CompositePart extends Part { // Toolbar this.toolBar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, titleActionsContainer, { - actionViewItemProvider: action => this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions..."), - telemetrySource: this.nameForTelemetry + telemetrySource: this.nameForTelemetry, + hoverDelegate: this.hoverDelegate })); this.collectCompositeActions()(); @@ -438,14 +444,14 @@ export abstract class CompositePart extends Part { titleLabel.updateStyles(); } - protected actionViewItemProvider(action: IAction): IActionViewItem | undefined { + protected actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { // Check Active Composite if (this.activeComposite) { - return this.activeComposite.getActionViewItem(action); + return this.activeComposite.getActionViewItem(action, options); } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } protected actionsContextProvider(): unknown { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index b8b74e5aec1d4..2c88a644bd5d4 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 { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; class OutlineItem extends BreadcrumbsItem { @@ -207,7 +207,7 @@ export class BreadcrumbsControl { @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, - @IHoverService private readonly hoverService: IHoverService + @IInstantiationService instantiationService: IInstantiationService, ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); @@ -230,17 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = { - delay: 500, - showHover: (options: IHoverOptions) => { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + this._hoverDelegate = getDefaultHoverDelegate('element'); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 651a9b3ff16e4..782ad9b007d97 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -45,7 +45,8 @@ 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 { IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -123,6 +124,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC private renderDropdownAsChildElement: boolean; + private readonly tabsHoverDelegate: IHoverDelegate; + constructor( protected readonly parent: HTMLElement, protected readonly editorPartsView: IEditorPartsView, @@ -138,7 +141,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IHostService private readonly hostService: IHostService, - @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -162,6 +164,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.renderDropdownAsChildElement = false; + this.tabsHoverDelegate = getDefaultHoverDelegate('element'); + this.create(parent); } @@ -205,7 +209,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC // Toolbar Widget this.editorActionsToolbar = this.editorActionsToolbarDisposables.add(this.instantiationService.createInstance(WorkbenchToolBar, container, { - actionViewItemProvider: action => this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, ariaLabel: localize('ariaLabelEditorActions', "Editor actions"), getKeyBinding: action => this.getKeybinding(action), @@ -231,12 +235,12 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC })); } - private actionViewItemProvider(action: IAction): IActionViewItem | undefined { + private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { const activeEditorPane = this.groupView.activeEditorPane; // Check Active Editor if (activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action); + const result = activeEditorPane.getActionViewItem(action, options); if (result) { return result; @@ -244,7 +248,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } // Check extensions - return createActionViewItem(this.instantiationService, action, { menuAsChild: this.renderDropdownAsChildElement }); + return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: this.renderDropdownAsChildElement }); } protected updateEditorActionsToolbar(): void { @@ -452,17 +456,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } protected getHoverDelegate(): IHoverDelegate { - return { - delay: 500, - showHover: options => { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + return this.tabsHoverDelegate; } protected updateTabHeight(): void { diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 2f613acd93fad..0043df4e61597 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -56,8 +56,6 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -152,10 +150,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, @IHostService hostService: IHostService, - @IDecorationsService decorationsService: IDecorationsService, - @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, hoverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 263ef8d961614..550866d685b00 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -42,6 +42,7 @@ import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userD import { isString } from 'vs/base/common/types'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class GlobalCompositeBar extends Disposable { @@ -612,6 +613,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV constructor( hoverOptions: IActivityHoverOptions, + options: IBaseActionViewItemOptions, @IThemeService themeService: IThemeService, @ILifecycleService lifecycleService: ILifecycleService, @IHoverService hoverService: IHoverService, @@ -629,6 +631,7 @@ export class SimpleAccountActivityActionViewItem extends AccountsActivityActionV @IInstantiationService instantiationService: IInstantiationService ) { super(() => [], { + ...options, colors: theme => ({ badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), @@ -643,6 +646,7 @@ export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionView constructor( hoverOptions: IActivityHoverOptions, + options: IBaseActionViewItemOptions, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, @@ -656,6 +660,7 @@ export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionView @IActivityService activityService: IActivityService, ) { super(() => [], { + ...options, colors: theme => ({ badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index ebee8aa4b4216..82d20fd96663a 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -302,11 +302,12 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.actionViewItemProvider(action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), - toggleMenuTitle: localize('moreActions', "More Actions...") + toggleMenuTitle: localize('moreActions', "More Actions..."), + hoverDelegate: this.hoverDelegate })); this.updateGlobalToolbarActions(); @@ -503,7 +504,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart event, getActions: () => activePaneCompositeActions, - getActionViewItem: action => this.actionViewItemProvider(action), + getActionViewItem: (action, options) => this.actionViewItemProvider(action, options), actionRunner: activePaneComposite.getActionRunner(), skipTelemetry: true }); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 940b074561a5e..e8a133fd4b4b7 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -27,9 +27,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { hash } from 'vs/base/common/hash'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; import { HideStatusbarEntryAction, ToggleStatusbarEntryVisibilityAction } from 'vs/workbench/browser/parts/statusbar/statusbarActions'; import { IStatusbarViewModelEntry, StatusbarViewModel } from 'vs/workbench/browser/parts/statusbar/statusbarModel'; import { StatusbarEntryItem } from 'vs/workbench/browser/parts/statusbar/statusbarItem'; @@ -138,39 +136,8 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; - private readonly hoverDelegate = new class implements IHoverDelegate { - private lastHoverHideTime = 0; - - readonly placement = 'element'; - - get delay() { - if (Date.now() - this.lastHoverHideTime < 200) { - return 0; // show instantly when a hover was recently shown - } - - return this.configurationService.getValue('workbench.hover.delay'); - } - - constructor( - private readonly configurationService: IConfigurationService, - private readonly hoverService: IHoverService - ) { } - - showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { - return this.hoverService.showHover({ - ...options, - persistence: { - hideOnKeyDown: true, - sticky: focus - } - }, focus); - } - - onDidHideHover(): void { - this.lastHoverHideTime = Date.now(); - } - }(this.configurationService, this.hoverService); + private readonly hoverDelegate: WorkbenchHoverDelegate; private readonly compactEntriesDisposable = this._register(new MutableDisposable()); private readonly styleOverrides = new Set(); @@ -184,11 +151,18 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHoverService private readonly hoverService: IHoverService, - @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); + this.hoverDelegate = this._register(instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( + { + persistence: { + hideOnKeyDown: true, + sticky: focus + } + } + ))); + this.registerListeners(); } @@ -667,10 +641,8 @@ export class MainStatusbarPart extends StatusbarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IHoverService hoverService: IHoverService, - @IConfigurationService configurationService: IConfigurationService ) { - super(Parts.STATUSBAR_PART, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService, hoverService, configurationService); + super(Parts.STATUSBAR_PART, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService); } } @@ -694,11 +666,9 @@ export class AuxiliaryStatusbarPart extends StatusbarPart implements IAuxiliaryS @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IHoverService hoverService: IHoverService, - @IConfigurationService configurationService: IConfigurationService ) { const id = AuxiliaryStatusbarPart.COUNTER++; - super(`workbench.parts.auxiliaryStatus.${id}`, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService, hoverService, configurationService); + super(`workbench.parts.auxiliaryStatus.${id}`, instantiationService, themeService, contextService, storageService, layoutService, contextMenuService, contextKeyService); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index cbe98d2e85228..05ad43933f8bd 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -33,8 +33,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/commandCenterControl'; -import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; @@ -54,6 +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 { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; export interface ITitleVariable { readonly name: string; @@ -191,28 +192,6 @@ export class BrowserTitleService extends MultiWindowParts i //#endregion } -class TitlebarPartHoverDelegate implements IHoverDelegate { - - readonly showHover = this.hoverService.showHover.bind(this.hoverService); - readonly placement = 'element'; - - private lastHoverHideTime: number = 0; - get delay(): number { - return Date.now() - this.lastHoverHideTime < 200 - ? 0 // show instantly when a hover was recently shown - : this.configurationService.getValue('workbench.hover.delay'); - } - - constructor( - @IHoverService private readonly hoverService: IHoverService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { } - - onDidHideHover() { - this.lastHoverHideTime = Date.now(); - } -} - export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#region IView @@ -264,7 +243,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { private readonly editorToolbarMenuDisposables = this._register(new DisposableStore()); private readonly layoutToolbarMenuDisposables = this._register(new DisposableStore()); - private readonly hoverDelegate = new TitlebarPartHoverDelegate(this.hoverService, this.configurationService); + private readonly hoverDelegate: IHoverDelegate; private readonly titleDisposables = this._register(new DisposableStore()); private titleBarStyle: TitlebarStyle = getTitleBarStyle(this.configurationService); @@ -290,7 +269,6 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @IHoverService private readonly hoverService: IHoverService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService private readonly menuService: IMenuService, @@ -304,6 +282,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, targetWindow, editorGroupsContainer)); + this.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.registerListeners(getWindowId(targetWindow)); } @@ -537,22 +517,22 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - private actionViewItemProvider(action: IAction): IActionViewItem | undefined { + private actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { // --- Activity Actions if (!this.isAuxiliary) { if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }); + return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }, options); } if (action.id === ACCOUNTS_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }); + return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }, options); } } // --- Editor Actions const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane; if (activeEditorPane && activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action); + const result = activeEditorPane.getActionViewItem(action, { hoverDelegate: this.hoverDelegate }); if (result) { return result; @@ -560,7 +540,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Check extensions - return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate, menuAsChild: false }); + return createActionViewItem(this.instantiationService, action, { ...options, hoverDelegate: this.hoverDelegate, menuAsChild: false }); } private getKeybinding(action: IAction): ResolvedKeybinding | undefined { @@ -585,7 +565,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { anchorAlignmentProvider: () => AnchorAlignment.RIGHT, telemetrySource: 'titlePart', highlightToggledItems: this.editorActionsEnabled, // Only show toggled state for editor actions (Layout actions are not shown as toggled) - actionViewItemProvider: action => this.actionViewItemProvider(action) + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options) })); if (this.editorActionsEnabled) { @@ -820,13 +800,12 @@ export class MainBrowserTitlebarPart extends BrowserTitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, ) { - super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); } } @@ -854,14 +833,13 @@ export class AuxiliaryBrowserTitlebarPart extends BrowserTitlebarPart implements @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService, ) { const id = AuxiliaryBrowserTitlebarPart.COUNTER++; - super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); } override get preventZoom(): boolean { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 19bfe50a6e2d3..d142226edb744 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -8,7 +8,7 @@ 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, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData, ListViewTargetSector } from 'vs/base/browser/ui/list/listView'; @@ -73,6 +73,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class TreeViewPane extends ViewPane { @@ -1106,15 +1107,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - }; + this._hoverDelegate = getDefaultHoverDelegate('mouse'); this._register(this.themeService.onDidFileIconThemeChange(() => this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index aa66323770b2d..978baf437cd54 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -432,7 +432,7 @@ export abstract class ViewPane extends Pane implements IView { actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded); this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, { orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: action => this.getActionViewItem(action), + actionViewItemProvider: (action, options) => this.getActionViewItem(action, options), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), renderDropdownAsChildElement: true, diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1c7201e246bf8..f575fa9524259 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -40,6 +40,7 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -590,11 +591,11 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return undefined; } - getActionViewItem(action: IAction): IActionViewItem | undefined { + getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { if (this.isViewMergedWithContainer()) { - return this.paneItems[0].pane.getActionViewItem(action); + return this.paneItems[0].pane.getActionViewItem(action, options); } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } focus(): void { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 9ac40779c6e05..8a311d4bb0ee8 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -43,6 +43,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; 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'; export interface IWorkbenchOptions { @@ -152,6 +154,10 @@ export class Workbench extends Layout { const dialogService = accessor.get(IDialogService); const notificationService = accessor.get(INotificationService) as NotificationService; + // Default Hover Delegate must be registered before creating any workbench/layout components + // as these possibly will use the default hover delegate + setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, enableInstantHover, {})); + // Layout this.initLayout(accessor); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 10c5c92bd461c..557d9a025ec4d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -144,7 +144,7 @@ class DocumentSymbolsOutline implements IOutline { this._breadcrumbsDataSource = new DocumentSymbolBreadcrumbsSource(_editor, textResourceConfigurationService); const delegate = new DocumentSymbolVirtualDelegate(); - const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true)]; + const renderers = [new DocumentSymbolGroupRenderer(), instantiationService.createInstance(DocumentSymbolRenderer, true, target)]; const treeDataSource: IDataSource = { getChildren: (parent) => { if (parent instanceof OutlineElement || parent instanceof OutlineGroup) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 8434fa5acf3c4..450c690d6a863 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -21,11 +21,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; +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 { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -121,21 +121,11 @@ export class DocumentSymbolRenderer implements ITreeRenderer { - return hoverService.showHover({ - ...options, - persistence: { - hideOnHover: true - } - }); - } - }; + this._hoverDelegate = getDefaultHoverDelegate(target === OutlineTarget.Breadcrumbs ? 'element' : 'mouse'); } renderTemplate(container: HTMLElement): DocumentSymbolTemplate { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c930fbdf0e29d..4145d4ae91abf 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -541,10 +541,10 @@ class SessionsRenderer implements ICompressibleTreeRenderer { + actionViewItemProvider: (action, options) => { if ((action.id === STOP_ID || action.id === DISCONNECT_ID) && action instanceof MenuItemAction) { stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor, options)); if (item) { return item; } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index db14887850c1c..433a562e4bfed 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -18,7 +18,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { BaseActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -40,6 +40,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { constructor( private context: unknown, action: IAction, + options: IBaseActionViewItemOptions, @IDebugService private readonly debugService: IDebugService, @IConfigurationService private readonly configurationService: IConfigurationService, @ICommandService private readonly commandService: ICommandService, @@ -47,7 +48,7 @@ export class StartDebugActionViewItem extends BaseActionViewItem { @IContextViewService contextViewService: IContextViewService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { - super(context, action); + super(context, action, options); this.toDispose = []; this.selectBox = new SelectBox([], -1, contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); this.selectBox.setFocusable(false); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index c46cd2d9a6f4f..0b9fb05501dd6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -16,7 +16,7 @@ import 'vs/css!./media/debugToolBar'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action'; -import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; +import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -40,6 +40,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { CodeWindow, mainWindow } from 'vs/base/browser/window'; import { clamp } from 'vs/base/common/numbers'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -89,18 +90,18 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IBaseActionViewItemOptions) => { if (action.id === FOCUS_SESSION_ID) { return this.instantiationService.createInstance(FocusSessionActionViewItem, action, undefined); } else if (action.id === STOP_ID || action.id === DISCONNECT_ID) { this.stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor, { hoverDelegate: options.hoverDelegate })); if (item) { return item; } } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } })); @@ -337,7 +338,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } -export function createDisconnectMenuItemAction(action: MenuItemAction, disposables: DisposableStore, accessor: ServicesAccessor): IActionViewItem | undefined { +export function createDisconnectMenuItemAction(action: MenuItemAction, disposables: DisposableStore, accessor: ServicesAccessor, options: IDropdownWithPrimaryActionViewItemOptions): IActionViewItem | undefined { const menuService = accessor.get(IMenuService); const contextKeyService = accessor.get(IContextKeyService); const instantiationService = accessor.get(IInstantiationService); @@ -358,7 +359,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl secondary, 'debug-stop-actions', contextMenuService, - {}); + options); return item; } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 24c202b7c346f..6a51b8f273ae5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -33,6 +33,7 @@ import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -93,9 +94,9 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { if (action.id === DEBUG_START_COMMAND_ID) { - this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action); + this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action, options); return this.startDebugActionViewItem; } if (action.id === FOCUS_SESSION_ID) { @@ -104,13 +105,13 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (action.id === STOP_ID || action.id === DISCONNECT_ID) { this.stopActionViewItemDisposables.clear(); - const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor)); + const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor, { hoverDelegate: options.hoverDelegate })); if (item) { return item; } } - return createActionViewItem(this.instantiationService, action); + return createActionViewItem(this.instantiationService, action, options); } focusView(id: string): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 53819a2eb531b..38c0e666af197 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -590,7 +590,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE toolbarOptions: { primaryGroup: () => true, }, - actionViewItemProvider: action => createActionViewItem(this.instantiationService, action) + actionViewItemProvider: (action, options) => createActionViewItem(this.instantiationService, action, options) })); // Register DragAndDrop support diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index ffd76aca945c1..f9c766ca849e1 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -201,8 +201,8 @@ export class InteractiveEditor extends EditorPane { const menu = this._register(this._menuService.createMenu(MenuId.InteractiveInputExecute, this._contextKeyService)); this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, { getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id), - actionViewItemProvider: action => { - return createActionViewItem(this._instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(this._instantiationService, action, options); }, renderDropdownAsChildElement: true })); 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 3a38688600a51..a7b79d856849b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -167,8 +167,8 @@ export class CellTitleToolbarPart extends CellOverlayPart { } const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { - actionViewItemProvider: action => { - return createActionViewItem(this.instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(this.instantiationService, action, options); }, renderDropdownAsChildElement: true })); @@ -275,8 +275,8 @@ function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, const instantiationService = accessor.get(IInstantiationService); const toolbar = new ToolBar(container, contextMenuService, { getKeyBinding: action => keybindingService.lookupKeybinding(action.id), - actionViewItemProvider: action => { - return createActionViewItem(instantiationService, action); + actionViewItemProvider: (action, options) => { + return createActionViewItem(instantiationService, action, options); }, renderDropdownAsChildElement: true }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 0753f9429c5e1..3559f22390ef3 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 { IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; 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'; export const openPreviewEnabledContext = new RawContextKey('openPreviewEnabled', false); @@ -342,6 +342,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer void; private _actionRunner: ActionRunner | undefined; + private readonly _hoverDelegate: IHoverDelegate; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -351,8 +352,11 @@ class ActionBarRenderer extends Disposable implements ITableRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - } + hoverDelegate: this._hoverDelegate }); const actionsContainer = dom.append(cell, dom.$('.actions')); const actionBar = new ActionBar(actionsContainer, { - actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService) + actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService), + hoverDelegate: this._hoverDelegate }); return { label, icon, actionBar, container: cell, elementDisposable: Disposable.None }; } @@ -781,7 +783,6 @@ export class TunnelPanel extends ViewPane { @ITelemetryService telemetryService: ITelemetryService, @ITunnelService private readonly tunnelService: ITunnelService, @IContextViewService private readonly contextViewService: IContextViewService, - @IHoverService private readonly hoverService: IHoverService ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); @@ -865,7 +866,7 @@ export class TunnelPanel extends ViewPane { const actionBarRenderer = new ActionBarRenderer(this.instantiationService, this.contextKeyService, this.menuService, this.contextViewService, this.remoteExplorerService, this.commandService, - this.configurationService, this.hoverService); + this.configurationService); const columns = [new IconColumn(), new PortColumn(), new LocalAddressColumn(), new RunningProcessColumn()]; if (this.tunnelService.canChangePrivacy) { columns.push(new PrivacyColumn()); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a42ecfd83742f..ca7a033fdcd62 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2492,12 +2492,12 @@ class SCMInputWidget { // Toolbar this.toolbar = instantiationService2.createInstance(SCMInputWidgetToolbar, this.toolbarContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction && this.toolbar.dropdownActions.length > 1) { - return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, this.toolbar.dropdownAction, this.toolbar.dropdownActions, '', this.contextMenuService, { actionRunner: this.toolbar.actionRunner }); + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, this.toolbar.dropdownAction, this.toolbar.dropdownActions, '', this.contextMenuService, { actionRunner: this.toolbar.actionRunner, hoverDelegate: options.hoverDelegate }); } - return createActionViewItem(instantiationService, action); + return createActionViewItem(instantiationService, action, options); }, menuOptions: { shouldForwardArgs: true diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index be5afbb292f93..d44c36c332bef 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -12,7 +12,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Action, IAction } from 'vs/base/common/actions'; import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { equals } from 'vs/base/common/arrays'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Command } from 'vs/editor/common/languages'; @@ -149,8 +149,8 @@ export class StatusBarAction extends Action { class StatusBarActionViewItem extends ActionViewItem { - constructor(action: StatusBarAction) { - super(null, action, {}); + constructor(action: StatusBarAction, options: IBaseActionViewItemOptions) { + super(null, action, options); } protected override updateLabel(): void { @@ -161,11 +161,11 @@ class StatusBarActionViewItem extends ActionViewItem { } export function getActionViewItemProvider(instaService: IInstantiationService): IActionViewItemProvider { - return action => { + return (action, options) => { if (action instanceof StatusBarAction) { - return new StatusBarActionViewItem(action); + return new StatusBarActionViewItem(action, options); } - return createActionViewItem(instaService, action); + return createActionViewItem(instaService, action, options); }; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 63ae90748b4f0..6835b5e9c353f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -29,6 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { openContextMenu } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; import { ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class TerminalEditor extends EditorPane { @@ -203,18 +204,18 @@ export class TerminalEditor extends EditorPane { this._editorInput?.terminalInstance?.setVisible(visible && this._workbenchLayoutService.isVisible(Parts.EDITOR_PART, dom.getWindow(this._editorInstanceElement))); } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TerminalCommandId.CreateTerminalEditor: { if (action instanceof MenuItemAction) { const location = { viewColumn: ACTIVE_GROUP }; const actions = getTerminalActionBarArgs(location, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu); - const button = this._instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}); + const button = this._instantiationService.createInstance(DropdownWithPrimaryActionViewItem, action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, { hoverDelegate: options.hoverDelegate }); return button; } } } - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } private _getDefaultProfileName(): string { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index c5d7591819ee4..31ed968488e9a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -23,7 +23,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ITerminalProfileResolverService, ITerminalProfileService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalSettingId, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal'; -import { ActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -236,7 +236,7 @@ export class TerminalViewPane extends ViewPane { this._terminalTabbedView?.layout(width, height); } - override getActionViewItem(action: Action): IActionViewItem | undefined { + override getActionViewItem(action: Action, options: IBaseActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TerminalCommandId.Split: { // Split needs to be special cased to force splitting within the panel, not the editor @@ -257,7 +257,7 @@ export class TerminalViewPane extends ViewPane { return; } }; - return new ActionViewItem(action, panelOnlySplitAction, { icon: true, label: false, keybinding: this._getKeybindingLabel(action) }); + return new ActionViewItem(action, panelOnlySplitAction, { ...options, icon: true, label: false, keybinding: this._getKeybindingLabel(action) }); } case TerminalCommandId.SwitchTerminal: { return this._instantiationService.createInstance(SwitchTerminalActionViewItem, action); @@ -273,13 +273,13 @@ export class TerminalViewPane extends ViewPane { if (action instanceof MenuItemAction) { const actions = getTerminalActionBarArgs(TerminalLocation.Panel, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu); this._newDropdown?.dispose(); - this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService, this._accessibilityService); + this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, { hoverDelegate: options.hoverDelegate }, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService, this._accessibilityService); this._updateTabActionBar(this._terminalProfileService.availableProfiles); return this._newDropdown; } } } - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } private _getDefaultProfileName(): string { diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 4110388332e11..ba7f2800830cc 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -473,7 +473,7 @@ class ResultSummaryView extends Disposable { })); const ab = this._register(new ActionBar(this.elements.rerun, { - actionViewItemProvider: action => createActionViewItem(instantiationService, action), + actionViewItemProvider: (action, options) => createActionViewItem(instantiationService, action, options), })); ab.push(instantiationService.createInstance(MenuItemAction, { ...new ReRunLastRun().desc, icon: icons.testingRerunIcon }, diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index d919f15af47d5..70afef1e015d9 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 { IHoverService } from 'vs/platform/hover/browser/hover'; -import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; 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'; const ItemHeight = 22; @@ -1147,14 +1147,9 @@ class TimelineTreeRenderer implements ITreeRenderer this.hoverService.showHover(options), - delay: this.configurationService.getValue('workbench.hover.delay') - }; + this._hoverDelegate = getDefaultHoverDelegate('element'); } private uri: URI | undefined; diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index fbf85313f6c7f..5d989fc5e1b7e 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -23,7 +23,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titlebar/menubarControl'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; import { IEditorGroupsContainer, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -72,13 +71,12 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorGroupService, editorService, menuService, keybindingService); this.bigSurOrNewer = isBigSurOrNewer(environmentService.os.release); } @@ -286,13 +284,12 @@ export class MainNativeTitlebarPart extends NativeTitlebarPart { @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { - super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService); } } @@ -316,14 +313,13 @@ export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements I @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @IKeybindingService keybindingService: IKeybindingService ) { const id = AuxiliaryNativeTitlebarPart.COUNTER++; - super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, hoverService, editorGroupService, editorService, menuService, keybindingService); + super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService); } override get preventZoom(): boolean { diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 463bea822b2b4..8e09c68166029 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -14,7 +14,6 @@ import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinp import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { InQuickPickContextKey } from 'vs/workbench/browser/quickaccess'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; export class QuickInputService extends BaseQuickInputService { @@ -27,9 +26,8 @@ export class QuickInputService extends BaseQuickInputService { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @ILayoutService layoutService: ILayoutService, - @IHoverService hoverService: IHoverService ) { - super(instantiationService, contextKeyService, themeService, layoutService, configurationService, hoverService); + super(instantiationService, contextKeyService, themeService, layoutService, configurationService); this.registerListeners(); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 10461c8e293be..23355faf76d73 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -163,13 +163,11 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; -import { IHoverOptions, IHoverService } from 'vs/platform/hover/browser/hover'; import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { EditorParts } from 'vs/workbench/browser/parts/editor/editorParts'; import { mainWindow } from 'vs/base/browser/window'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IEditorPaneService } from 'vs/workbench/services/editor/common/editorPaneService'; import { EditorPaneService } from 'vs/workbench/services/editor/browser/editorPaneService'; @@ -334,8 +332,7 @@ export function workbenchInstantiationService( instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IPaneCompositePartService, disposables.add(new TestPaneCompositeService())); instantiationService.stub(IListService, new TestListService()); - const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService, hoverService))); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); instantiationService.stub(IWorkspaceTrustRequestService, disposables.add(new TestWorkspaceTrustRequestService(false))); @@ -758,25 +755,6 @@ export class TestSideBarPart implements IPaneCompositePart { layout(width: number, height: number, top: number, left: number): void { } } -class TestHoverService implements IHoverService { - private currentHover: IHoverWidget | undefined; - _serviceBrand: undefined; - showHover(options: IHoverOptions, focus?: boolean | undefined): IHoverWidget | undefined { - this.currentHover = new class implements IHoverWidget { - private _isDisposed = false; - get isDisposed(): boolean { return this._isDisposed; } - dispose(): void { - this._isDisposed = true; - } - }; - return this.currentHover; - } - showAndFocusLastHover(): void { } - hideHover(): void { - this.currentHover?.dispose(); - } -} - export class TestPanelPart implements IPaneCompositePart { declare readonly _serviceBrand: undefined; 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 0349/1863] 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 637e2f77104d9..bc7442b5f488e 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 0350/1863] 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 bc7442b5f488e..d65b9a0ffc3ef 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 0351/1863] 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 d65b9a0ffc3ef..c049e71d0138a 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 0352/1863] 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 19d9fdf140c3f..3ad13dbb89835 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 0353/1863] 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 c049e71d0138a..91bec4acda08e 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 d30f7018d2ba0b4fe35816989363e6f5b84f7361 Mon Sep 17 00:00:00 2001 From: Anthony Kim <62267334+anthonykim1@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:39:05 -0800 Subject: [PATCH 0354/1863] Add Python Shell Type and .python_history (#204680) * Add Python Shell type and history * remove one comment * remove unncessary * Why can't I manually override setShellType * Detect python shell type on Windows * fire shellType in _sendProcessTitle * Detect python shell type on Windows * delete comment * remove more comments * remove comment * remove dup * clean up * more comprehensive regex for windows python * follow previous style for map key name * remove unused variable * remove comment * last cleanup * More cleanup * more clean up * re-arrange * remove unused --------- Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/platform/terminal/common/terminal.ts | 4 ++- .../platform/terminal/node/terminalProcess.ts | 8 +++++- .../terminal/node/windowsShellHelper.ts | 3 +++ .../terminal/browser/terminalInstance.ts | 1 + .../contrib/terminal/common/history.ts | 27 +++++++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ffe0e56a1890a..a0b94d2a7f755 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -140,12 +140,14 @@ export const enum PosixShellType { Csh = 'csh', Ksh = 'ksh', Zsh = 'zsh', + Python = 'python' } export const enum WindowsShellType { CommandPrompt = 'cmd', PowerShell = 'pwsh', Wsl = 'wsl', - GitBash = 'gitbash' + GitBash = 'gitbash', + Python = 'python' } export type TerminalShellType = PosixShellType | WindowsShellType; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 023b6dc66e05d..79be9013c8115 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -73,6 +73,7 @@ const posixShellTypeMap = new Map([ ['ksh', PosixShellType.Ksh], ['sh', PosixShellType.Sh], ['pwsh', PosixShellType.PowerShell], + ['python', PosixShellType.Python], ['zsh', PosixShellType.Zsh] ]); @@ -404,7 +405,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: this._currentTitle }); // If fig is installed it may change the title of the process const sanitizedTitle = this.currentTitle.replace(/ \(figterm\)$/g, ''); - this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: posixShellTypeMap.get(sanitizedTitle) }); + + if (sanitizedTitle.toLowerCase().startsWith('python')) { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: PosixShellType.Python }); + } else { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.ShellType, value: posixShellTypeMap.get(sanitizedTitle) }); + } } shutdown(immediate: boolean): void { diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index 1def6545d6908..e9fcb6f8130d7 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -152,6 +152,9 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe case 'sles-12.exe': return WindowsShellType.Wsl; default: + if (executable.match(/python(\d(\.\d{0,2})?)?\.exe/)) { + return WindowsShellType.Python; + } return undefined; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e46e771f2a898..8b11cc8d9e87a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -118,6 +118,7 @@ const shellIntegrationSupportedShellTypes = [ PosixShellType.Bash, PosixShellType.Zsh, PosixShellType.PowerShell, + PosixShellType.Python, WindowsShellType.PowerShell ]; diff --git a/src/vs/workbench/contrib/terminal/common/history.ts b/src/vs/workbench/contrib/terminal/common/history.ts index cf0d940e716a1..ca2588453d2ab 100644 --- a/src/vs/workbench/contrib/terminal/common/history.ts +++ b/src/vs/workbench/contrib/terminal/common/history.ts @@ -92,6 +92,9 @@ export async function getShellFileHistory(accessor: ServicesAccessor, shellType: case PosixShellType.Fish: result = await fetchFishHistory(accessor); break; + case PosixShellType.Python: + result = await fetchPythonHistory(accessor); + break; default: return []; } if (result === undefined) { @@ -295,6 +298,30 @@ export async function fetchZshHistory(accessor: ServicesAccessor) { return result.values(); } + +export async function fetchPythonHistory(accessor: ServicesAccessor): Promise | undefined> { + const fileService = accessor.get(IFileService); + const remoteAgentService = accessor.get(IRemoteAgentService); + + const content = await fetchFileContents(env['HOME'], '.python_history', false, fileService, remoteAgentService); + + if (content === undefined) { + return undefined; + } + + // Python history file is a simple text file with one command per line + const fileLines = content.split('\n'); + const result: Set = new Set(); + + fileLines.forEach(line => { + if (line.trim().length > 0) { + result.add(line.trim()); + } + }); + + return result.values(); +} + export async function fetchPwshHistory(accessor: ServicesAccessor) { const fileService: Pick = accessor.get(IFileService); const remoteAgentService: Pick = accessor.get(IRemoteAgentService); 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 0355/1863] 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 91bec4acda08e..32e1e685ba859 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 0356/1863] 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 2585ae766da74..8f9c3defee098 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 0357/1863] 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 32e1e685ba859..014f746c786db 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 0358/1863] 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 014f746c786db..8448d7e5c61c4 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 0359/1863] 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 8f9c3defee098..e609ce9067f95 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 0360/1863] 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 e609ce9067f95..4c0ef9145d8f6 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 0361/1863] 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 8448d7e5c61c4..a4bf3b55990e9 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 0362/1863] 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 a4bf3b55990e9..499d7e375da55 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 0363/1863] 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 4c0ef9145d8f6..c96dd7326eb22 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 0364/1863] 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 3ad13dbb89835..1a813210025c8 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 0365/1863] 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 c96dd7326eb22..9c274d92b9ab9 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 0366/1863] 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 1feb4831c23c1..25d492a063c3d 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 233fd797c0878a551994e55f1e87c23f5a200969 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 15 Feb 2024 14:05:54 -0700 Subject: [PATCH 0367/1863] Skip flaky extension smoke tests (#205309) --- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index c78cbe8708973..bc65a8631c44d 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -7,7 +7,7 @@ import { Application, Logger } from '../../../../automation'; import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { - describe('Extensions', () => { + describe.skip('Extensions', () => { // Shared before/after handling installAllHandlers(logger); From 3c0c5106b81dc4f2c169415e816b361d6d13010a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 15:08:56 -0600 Subject: [PATCH 0368/1863] 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 8ea1469a4b1d2..dcca7e64e6864 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 1a813210025c8..aa8f4e045d1c1 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 9c274d92b9ab9..fd5f072846bb5 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 0369/1863] 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 57c29d41e9da6..99862b01d7bb8 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 a1a29cfd29bc6d59aac90b855d6a724a5d8294da Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 15 Feb 2024 14:02:00 -0800 Subject: [PATCH 0370/1863] Fix list view npe when inserting whitespaces at top (#205311) --- src/vs/base/browser/ui/list/listView.ts | 6 +++--- .../notebook/browser/view/notebookCellList.ts | 2 +- .../notebook/browser/view/notebookCellListView.ts | 7 ++++++- .../notebook/browser/viewParts/notebookViewZones.ts | 3 ++- .../notebook/test/browser/notebookViewZones.test.ts | 13 +++++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 546f0bdb1f49a..fdd9df0e2aece 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -295,8 +295,8 @@ export class ListView implements IListView { protected rangeMap: IRangeMap; private cache: RowCache; private renderers = new Map>(); - private lastRenderTop: number; - private lastRenderHeight: number; + protected lastRenderTop: number; + protected lastRenderHeight: number; private renderWidth = 0; private rowsContainer: HTMLElement; private scrollable: Scrollable; @@ -1400,7 +1400,7 @@ export class ListView implements IListView { return undefined; } - private getRenderRange(renderTop: number, renderHeight: number): IRange { + protected getRenderRange(renderTop: number, renderHeight: number): IRange { return { start: this.rangeMap.indexAt(renderTop), end: this.rangeMap.indexAfter(renderTop + renderHeight - 1) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 94cbebb16b55e..bd47eb879e98e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -594,7 +594,7 @@ export class NotebookCellList extends WorkbenchList implements ID if (modelIndex >= this.hiddenRangesPrefixSum.getTotalSum()) { // it's already after the last hidden range - return this.hiddenRangesPrefixSum.getTotalSum(); + return Math.min(this.length, this.hiddenRangesPrefixSum.getTotalSum()); } return this.hiddenRangesPrefixSum.getIndexOf(modelIndex).index; diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts index bafaac004e091..e7f7d018a80fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellListView.ts @@ -276,9 +276,14 @@ export class NotebookCellListView extends ListView { insertWhitespace(afterPosition: number, size: number): string { const scrollTop = this.scrollTop; const id = `${++this._lastWhitespaceId}`; + const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + const elementPosition = this.elementTop(afterPosition); + const aboveScrollTop = scrollTop > elementPosition; this.notebookRangeMap.insertWhitespace(id, afterPosition, size); - this._rerender(scrollTop, this.renderHeight, false); + const newScrolltop = aboveScrollTop ? scrollTop + size : scrollTop; + this.render(previousRenderRange, newScrolltop, this.lastRenderHeight, undefined, undefined, false); + this._rerender(newScrolltop, this.renderHeight, false); this.eventuallyUpdateScrollDimensions(); return id; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts index bc7384c7654ed..7332529b231bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookViewZones.ts @@ -120,7 +120,8 @@ export class NotebookViewZones extends Disposable { } private _addZone(zone: INotebookViewZone): string { - const whitespaceId = this.listView.insertWhitespace(zone.afterModelPosition, zone.heightInPx); + const viewPosition = this.coordinator.convertModelIndexToViewIndex(zone.afterModelPosition); + const whitespaceId = this.listView.insertWhitespace(viewPosition, zone.heightInPx); const isInHiddenArea = this._isInHiddenRanges(zone); const myZone: IZoneWidget = { whitespaceId: whitespaceId, diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts index a858c9d786603..2f87376fe5c66 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewZones.test.ts @@ -346,6 +346,19 @@ suite('NotebookRangeMap with whitesspaces', () => { instantiationService.stub(IConfigurationService, config); }); + test('simple', () => { + const rangeMap = new NotebookCellsLayout(0); + rangeMap.splice(0, 0, [{ size: 479 }, { size: 163 }, { size: 182 }, { size: 106 }, { size: 106 }, { size: 106 }, { size: 87 }]); + + const start = rangeMap.indexAt(650); + const end = rangeMap.indexAfter(650 + 890 - 1); + assert.strictEqual(start, 2); + assert.strictEqual(end, 7); + + rangeMap.insertWhitespace('1', 0, 18); + assert.strictEqual(rangeMap.indexAt(650), 1); + }); + test('Whitespace CRUD', async function () { const twenty = { size: 20 }; From a9f226d8eba2370051e88cfedf8e47f716e261f8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 15 Feb 2024 16:02:41 -0600 Subject: [PATCH 0371/1863] 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 aa8f4e045d1c1..187d16f6d513b 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 fd5f072846bb5..030e2bb8fdf39 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 0372/1863] 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 030e2bb8fdf39..baa3943274c92 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 0373/1863] 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 baa3943274c92..f8e9767117119 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 93307188f0428375840c99bb140c4dec1f4e4afd Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 15 Feb 2024 14:47:22 -0800 Subject: [PATCH 0374/1863] Fix #205291. (#205313) --- .../notebook/browser/notebookOptions.ts | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 36ed3544b98b4..c05004146c284 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -284,28 +284,33 @@ export class NotebookOptions extends Disposable { return; } - const options = this.codeEditorService.resolveDecorationOptions(e, true); - if (options.afterContentClassName || options.beforeContentClassName) { - const cssRules = this.codeEditorService.resolveDecorationCSSRules(e); - if (cssRules !== null) { - for (let i = 0; i < cssRules.length; i++) { - // The following ways to index into the list are equivalent - if ( - ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) - && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 - ) { - // there is a `::before` or `::after` text decoration whose position is above or below current line - // we at least make sure that the editor top padding is at least one line - const editorOptions = this.configurationService.getValue('editor'); - updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); - decorationTriggeredAdjustment = true; - break; + try { + const options = this.codeEditorService.resolveDecorationOptions(e, true); + if (options.afterContentClassName || options.beforeContentClassName) { + const cssRules = this.codeEditorService.resolveDecorationCSSRules(e); + if (cssRules !== null) { + for (let i = 0; i < cssRules.length; i++) { + // The following ways to index into the list are equivalent + if ( + ((cssRules[i] as CSSStyleRule).selectorText.endsWith('::after') || (cssRules[i] as CSSStyleRule).selectorText.endsWith('::after')) + && (cssRules[i] as CSSStyleRule).cssText.indexOf('top:') > -1 + ) { + // there is a `::before` or `::after` text decoration whose position is above or below current line + // we at least make sure that the editor top padding is at least one line + const editorOptions = this.configurationService.getValue('editor'); + updateEditorTopPadding(BareFontInfo.createFromRawSettings(editorOptions, PixelRatio.getInstance(this.targetWindow).value).lineHeight + 2); + decorationTriggeredAdjustment = true; + break; + } } } } + + decorationCheckSet.add(e); + } catch (_ex) { + // do not throw and break notebook } - decorationCheckSet.add(e); }; this._register(this.codeEditorService.onDecorationTypeRegistered(onDidAddDecorationType)); this.codeEditorService.listDecorationTypes().forEach(onDidAddDecorationType); From 03aeb958495cd47a902aa5102bb23703ba9b35df Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 01:06:58 +0000 Subject: [PATCH 0375/1863] Add isSticky to chat agent (#205322) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 11 ++++++++++- .../chat/browser/contrib/chatInputEditorContrib.ts | 8 +++----- src/vs/workbench/contrib/chat/common/chatAgents.ts | 3 ++- src/vscode-dts/vscode.proposed.chatAgents2.d.ts | 5 +++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 78adfe193fdaa..1f40b04c9241e 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -353,6 +353,7 @@ class ExtHostChatAgent { private _supportIssueReporting: boolean | undefined; private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; private _welcomeMessageProvider?: vscode.ChatAgentWelcomeMessageProvider | undefined; + private _isSticky: boolean | undefined; constructor( public readonly extension: IExtensionDescription, @@ -476,7 +477,8 @@ class ExtHostChatAgent { helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : typeConvert.MarkdownString.from(this._helpTextPostfix), sampleRequest: this._sampleRequest, - supportIssueReporting: this._supportIssueReporting + supportIssueReporting: this._supportIssueReporting, + isSticky: this._isSticky, }); updateScheduled = false; }); @@ -622,6 +624,13 @@ class ExtHostChatAgent { ? undefined! : this._onDidPerformAction.event , + get isSticky() { + return that._isSticky; + }, + set isSticky(v) { + that._isSticky = v; + updateMetadataSoon(); + }, dispose() { disposed = true; that._commandProvider = undefined; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 0b1a1793ea234..12c466bfd6f90 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -237,8 +237,7 @@ class InputEditorSlashCommandMode extends Disposable { public readonly id = 'InputEditorSlashCommandMode'; constructor( - private readonly widget: IChatWidget, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + private readonly widget: IChatWidget ) { super(); this._register(this.widget.onDidSubmitAgent(e => { @@ -248,10 +247,9 @@ class InputEditorSlashCommandMode extends Disposable { private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) { let value: string | undefined; - if (slashCommand && slashCommand.shouldRepopulate) { + if (slashCommand && slashCommand.isSticky) { value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `; - } else if (agent.id !== this._chatAgentService.getDefaultAgent()?.id) { - // Agents always repopulate, and slash commands fall back to the agent if they don't repopulate + } else if (agent.metadata.isSticky) { value = `${chatAgentLeader}${agent.id} `; } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index dbcabdd422d1e..a6ef4c6c3592c 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -54,7 +54,7 @@ export interface IChatAgentCommand { * chat into a persistent mode, where the * slash command is prepended to the chat input. */ - shouldRepopulate?: boolean; + isSticky?: boolean; /** * Placeholder text to render in the chat input @@ -79,6 +79,7 @@ export interface IChatAgentMetadata { sampleRequest?: string; supportIssueReporting?: boolean; followupPlaceholder?: string; + isSticky?: boolean; } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 1762bdafb6408..63cad62d20ad0 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -277,6 +277,11 @@ declare module 'vscode' { */ sampleRequest?: string; + /** + * Whether invoking the agent puts the chat into a persistent mode, where the agent is automatically added to the chat input for the next message. + */ + isSticky?: boolean; + /** * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes * a result. From d84c946d27b649d614319c796911871320befd42 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Thu, 15 Feb 2024 17:38:10 -0800 Subject: [PATCH 0376/1863] Support `command:toSide` in markdown also (#205316) --- .../browser/gettingStarted.ts | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 7c23216426b3d..99d6a2608866f 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -645,7 +645,12 @@ export class GettingStartedPage extends EditorPane { this.stepDisposables.add(this.webview.onDidClickLink(link => { if (matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.http) || (matchesScheme(link, Schemas.command))) { - this.openerService.open(link, { allowCommands: true }); + const toSide = link.startsWith('command:toSide:'); + if (toSide) { + link = link.replace('command:toSide:', 'command:'); + this.focusSideEditorGroup(); + } + this.openerService.open(link, { allowCommands: true, openToSide: toSide }); } })); @@ -1164,6 +1169,25 @@ export class GettingStartedPage extends EditorPane { return widget; } + private focusSideEditorGroup() { + const fullSize = this.group ? this.groupsService.getPart(this.group).contentDimension : undefined; + if (!fullSize || fullSize.width <= 700) { return; } + if (this.groupsService.count === 1) { + const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); + this.groupsService.activateGroup(sideGroup); + + const gettingStartedSize = Math.floor(fullSize.width / 2); + + const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); + this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); + } + + const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); + if (nonGettingStartedGroup) { + this.groupsService.activateGroup(nonGettingStartedGroup); + nonGettingStartedGroup.focus(); + } + } private runStepCommand(href: string) { const isCommand = href.startsWith('command:'); @@ -1172,24 +1196,8 @@ export class GettingStartedPage extends EditorPane { this.telemetryService.publicLog2('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href, walkthroughId: this.currentWalkthrough?.id }); - const fullSize = this.group ? this.groupsService.getPart(this.group).contentDimension : undefined; - - if (toSide && fullSize && fullSize.width > 700) { - if (this.groupsService.count === 1) { - const sideGroup = this.groupsService.addGroup(this.groupsService.groups[0], GroupDirection.RIGHT); - this.groupsService.activateGroup(sideGroup); - - const gettingStartedSize = Math.floor(fullSize.width / 2); - - const gettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => (group.activeEditor instanceof GettingStartedInput)); - this.groupsService.setSize(assertIsDefined(gettingStartedGroup), { width: gettingStartedSize, height: fullSize.height }); - } - - const nonGettingStartedGroup = this.groupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(group => !(group.activeEditor instanceof GettingStartedInput)); - if (nonGettingStartedGroup) { - this.groupsService.activateGroup(nonGettingStartedGroup); - nonGettingStartedGroup.focus(); - } + if (toSide) { + this.focusSideEditorGroup(); } if (isCommand) { const commandURI = URI.parse(command); From 877caad9437ec2f26970b48e60e394a7a45b47e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 09:33:07 +0100 Subject: [PATCH 0377/1863] debt - make history suspend more explicit (#205181) * debt - make history suspend more explicit * added tests * :lipstick: --------- Co-authored-by: andreamah --- .../browser/debugConfigurationManager.test.ts | 4 +- .../quickTextSearch/textSearchQuickAccess.ts | 15 +- .../history/browser/historyService.ts | 22 +-- .../services/history/common/history.ts | 7 +- .../test/browser/historyService.test.ts | 138 ++++++++++++++++++ .../test/browser/workbenchTestServices.ts | 24 +-- .../test/common/workbenchTestServices.ts | 2 +- 7 files changed, 170 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts index a63840db4e65d..71b53f1ab2522 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts @@ -20,8 +20,8 @@ import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentitySe import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; import { DebugConfigurationProviderTriggerKind, IAdapterManager, IConfig, IDebugAdapterExecutable, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { TestHistoryService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestHistoryService, TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('debugConfigurationManager', () => { const configurationProviderType = 'custom-type'; diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 15a07cb067524..3691e98ec1deb 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -125,12 +125,15 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { // disable and re-enable history service so that we can ignore this history entry - this._historyService.shouldIgnoreActiveEditorChange = true; - await this._editorService.openEditor({ - resource: itemMatch.parent().resource, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection: itemMatch.range() } - }); - this._historyService.shouldIgnoreActiveEditorChange = false; + 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(); + } }); } })); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index 3c580614b86e4..108685831ad99 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 } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore, IDisposable, toDisposable } 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'; @@ -47,9 +47,7 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly editorHelper = this.instantiationService.createInstance(EditorHelper); - // Can be set to temporarily ignore messages from the editor service that indicate a new active editor. - // Used for ignoring some editors for history. - public shouldIgnoreActiveEditorChange: boolean = false; + constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -75,6 +73,13 @@ 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 @@ -82,14 +87,14 @@ export class HistoryService extends Disposable implements IHistoryService { // Editor changes this._register(this.editorService.onDidActiveEditorChange((e) => { - if (!this.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { 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.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { this.handleEditorEventInRecentEditorsStack(); } })); @@ -190,11 +195,10 @@ export class HistoryService extends Disposable implements IHistoryService { // is having a selection concept. if (isEditorPaneWithSelection(activeEditorPane)) { this.activeEditorListeners.add(activeEditorPane.onDidChangeSelection(e => { - if (!this.shouldIgnoreActiveEditorChange) { + if (!this.trackingSuspended) { this.handleActiveEditorSelectionChangeEvent(activeEditorGroup, activeEditorPane, e); } - } - )); + })); } // Context keys diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 86d21f49dad14..3d135ec2a7032 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -8,6 +8,7 @@ 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'); @@ -60,7 +61,6 @@ export const enum GoScope { export interface IHistoryService { readonly _serviceBrand: undefined; - shouldIgnoreActiveEditorChange: boolean; /** * Navigate forwards in editor navigation history. @@ -138,4 +138,9 @@ 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 124a79c02035d..43838aad18979 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -807,5 +807,143 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); + test('suspend should suspend editor changes- skip two editors and continue (single group)', 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 }); + 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 }); + assert.strictEqual(part.activeGroup.activeEditor, input2); + await part.activeGroup.openEditor(input3, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input3); + + await editorChangePromise; + disposable.dispose(); + + await part.activeGroup.openEditor(input4, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await part.activeGroup.openEditor(input5, { pinned: true }); + assert.strictEqual(part.activeGroup.activeEditor, input5); + + // stack should be [input1, input4, input5] + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input4); + await historyService.goBack(); + assert.strictEqual(part.activeGroup.activeEditor, input1); + 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- 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/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 23355faf76d73..6db63c1cc2ef3 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -98,7 +98,7 @@ import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputS import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; -import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService, TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService, TestMarkerService, TestHistoryService } from 'vs/workbench/test/common/workbenchTestServices'; import { IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; @@ -542,28 +542,6 @@ export class TestMenuService implements IMenuService { } } -export class TestHistoryService implements IHistoryService { - - declare readonly _serviceBrand: undefined; - declare shouldIgnoreActiveEditorChange: boolean; - - constructor(private root?: URI) { } - - async reopenLastClosedEditor(): Promise { } - async goForward(): Promise { } - async goBack(): Promise { } - async goPrevious(): Promise { } - async goLast(): Promise { } - removeFromHistory(_input: EditorInput | IResourceEditorInput): void { } - clear(): void { } - clearRecentlyOpened(): void { } - getHistory(): readonly (EditorInput | IResourceEditorInput)[] { return []; } - async openNextRecentlyUsedEditor(group?: GroupIdentifier): Promise { } - async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } - getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } - getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } -} - export class TestFileDialogService implements IFileDialogService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index d1483ff15beb7..1d09528dcf6c1 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -149,7 +149,6 @@ export class TestStorageService extends InMemoryStorageService { export class TestHistoryService implements IHistoryService { declare readonly _serviceBrand: undefined; - declare shouldIgnoreActiveEditorChange: boolean; constructor(private root?: URI) { } @@ -166,6 +165,7 @@ 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 10a596e4d046742624a6550e1dc233e462076eda Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 10:02:00 +0100 Subject: [PATCH 0378/1863] voice - suspend keyword recognition when focus lost (#205336) * speech - bind stop keyword detection to window focus * . * . * . --- src/vs/code/electron-main/app.ts | 6 +- .../electron-main/auxiliaryWindows.ts | 5 - .../electron-main/nativeHostMainService.ts | 6 +- .../speech.contribution.ts | 3 +- .../contrib/speech/browser/speechService.ts | 210 ++++++++++++++++++ .../contrib/speech/common/speechService.ts | 159 +------------ src/vs/workbench/workbench.common.main.ts | 2 +- 7 files changed, 224 insertions(+), 167 deletions(-) rename src/vs/workbench/contrib/speech/{common => browser}/speech.contribution.ts (76%) create mode 100644 src/vs/workbench/contrib/speech/browser/speechService.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1dfa17a817cc0..7a03905040247 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -15,7 +15,7 @@ import { Event } from 'vs/base/common/event'; import { stripComments } from 'vs/base/common/json'; import { getPathLabel } from 'vs/base/common/labels'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; +import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import { isAbsolute, join, posix } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; import { assertType } from 'vs/base/common/types'; @@ -118,7 +118,7 @@ import { ElectronPtyHostStarter } from 'vs/platform/terminal/electron-main/elect import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from 'vs/platform/remote/common/electronRemoteResources'; import { Lazy } from 'vs/base/common/lazy'; -import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { AuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService'; /** @@ -392,7 +392,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (event, contents) => { // Auxiliary Window: delegate to `AuxiliaryWindow` class - if (isAuxiliaryWindow(contents)) { + if (contents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`)) { this.logService.trace('[aux window] app.on("web-contents-created"): Registering auxiliary window'); this.auxiliaryWindowsMainService?.registerWindow(contents); diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts index 699659d0c77c8..4ce7e22bbff26 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -5,7 +5,6 @@ import { BrowserWindowConstructorOptions, HandlerDetails, WebContents } from 'electron'; import { Event } from 'vs/base/common/event'; -import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -30,7 +29,3 @@ export interface IAuxiliaryWindowsMainService { getWindows(): readonly IAuxiliaryWindow[]; } - -export function isAuxiliaryWindow(webContents: WebContents): boolean { - return webContents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`); -} diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index fa4087a411fb5..a1d2830161112 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -40,7 +40,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl'; import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfiling'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; -import { IAuxiliaryWindowsMainService, isAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; import { CancellationError } from 'vs/base/common/errors'; @@ -105,11 +105,11 @@ export class NativeHostMainService extends Disposable implements INativeHostMain readonly onDidBlurMainOrAuxiliaryWindow = Event.any( this.onDidBlurMainWindow, - Event.map(Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window), window => isAuxiliaryWindow(window.webContents)), window => window.id) + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window.id), windowId => !!this.auxiliaryWindowsMainService.getWindowById(windowId)) ); readonly onDidFocusMainOrAuxiliaryWindow = Event.any( this.onDidFocusMainWindow, - Event.map(Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (event, window: BrowserWindow) => window), window => isAuxiliaryWindow(window.webContents)), window => window.id) + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (event, window: BrowserWindow) => window.id), windowId => !!this.auxiliaryWindowsMainService.getWindowById(windowId)) ); readonly onDidResumeOS = Event.fromNodeEventEmitter(powerMonitor, 'resume'); diff --git a/src/vs/workbench/contrib/speech/common/speech.contribution.ts b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts similarity index 76% rename from src/vs/workbench/contrib/speech/common/speech.contribution.ts rename to src/vs/workbench/contrib/speech/browser/speech.contribution.ts index 6a093cd32e7a6..03a6035fb8005 100644 --- a/src/vs/workbench/contrib/speech/common/speech.contribution.ts +++ b/src/vs/workbench/contrib/speech/browser/speech.contribution.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ISpeechService, SpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { SpeechService } from 'vs/workbench/contrib/speech/browser/speechService'; registerSingleton(ISpeechService, SpeechService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts new file mode 100644 index 0000000000000..99a2fe06e81d1 --- /dev/null +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { firstOrDefault } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +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'; + +export class SpeechService extends Disposable implements ISpeechService { + + readonly _serviceBrand: undefined; + + private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); + readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; + + private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); + readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; + + get hasSpeechProvider() { return this.providers.size > 0; } + + private readonly providers = new Map(); + + private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); + + constructor( + @ILogService private readonly logService: ILogService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IHostService private readonly hostService: IHostService + ) { + super(); + } + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { + if (this.providers.has(identifier)) { + throw new Error(`Speech provider with identifier ${identifier} is already registered.`); + } + + this.providers.set(identifier, provider); + this.hasSpeechProviderContext.set(true); + + this._onDidRegisterSpeechProvider.fire(provider); + + return toDisposable(() => { + this.providers.delete(identifier); + this._onDidUnregisterSpeechProvider.fire(provider); + + if (this.providers.size === 0) { + this.hasSpeechProviderContext.set(false); + } + }); + } + + private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); + readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; + + private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); + readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; + + private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; + get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } + + private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeSpeechToTextSession) { + this._activeSpeechToTextSession = undefined; + this.speechToTextInProgress.reset(); + this._onDidEndSpeechToTextSession.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + switch (e.status) { + case SpeechToTextStatus.Started: + if (session === this._activeSpeechToTextSession) { + this.speechToTextInProgress.set(true); + this._onDidStartSpeechToTextSession.fire(); + } + break; + case SpeechToTextStatus.Stopped: + onSessionStoppedOrCanceled(); + break; + } + })); + + return session; + } + + private readonly _onDidStartKeywordRecognition = this._register(new Emitter()); + readonly onDidStartKeywordRecognition = this._onDidStartKeywordRecognition.event; + + private readonly _onDidEndKeywordRecognition = this._register(new Emitter()); + readonly onDidEndKeywordRecognition = this._onDidEndKeywordRecognition.event; + + private _activeKeywordRecognitionSession: IKeywordRecognitionSession | undefined = undefined; + get hasActiveKeywordRecognition() { return !!this._activeKeywordRecognitionSession; } + + async recognizeKeyword(token: CancellationToken): Promise { + const result = new DeferredPromise(); + + const disposables = new DisposableStore(); + disposables.add(token.onCancellationRequested(() => { + disposables.dispose(); + result.complete(KeywordRecognitionStatus.Canceled); + })); + + const recognizeKeywordDisposables = disposables.add(new DisposableStore()); + let activeRecognizeKeywordSession: Promise | undefined = undefined; + const recognizeKeyword = () => { + recognizeKeywordDisposables.clear(); + + const cts = new CancellationTokenSource(token); + recognizeKeywordDisposables.add(toDisposable(() => cts.dispose(true))); + const currentRecognizeKeywordSession = activeRecognizeKeywordSession = this.doRecognizeKeyword(cts.token).then(status => { + if (currentRecognizeKeywordSession === activeRecognizeKeywordSession) { + result.complete(status); + } + }, error => { + if (currentRecognizeKeywordSession === activeRecognizeKeywordSession) { + result.error(error); + } + }); + }; + + disposables.add(this.hostService.onDidChangeFocus(focused => { + if (!focused && activeRecognizeKeywordSession) { + recognizeKeywordDisposables.clear(); + activeRecognizeKeywordSession = undefined; + } else if (!activeRecognizeKeywordSession) { + recognizeKeyword(); + } + })); + + if (this.hostService.hasFocus) { + recognizeKeyword(); + } + + try { + return await result.p; + } finally { + disposables.dispose(); + } + } + + private async doRecognizeKeyword(token: CancellationToken): Promise { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + const session = this._activeKeywordRecognitionSession = provider.createKeywordRecognitionSession(token); + this._onDidStartKeywordRecognition.fire(); + + const disposables = new DisposableStore(); + + const onSessionStoppedOrCanceled = () => { + if (session === this._activeKeywordRecognitionSession) { + this._activeKeywordRecognitionSession = undefined; + this._onDidEndKeywordRecognition.fire(); + } + + disposables.dispose(); + }; + + disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); + if (token.isCancellationRequested) { + onSessionStoppedOrCanceled(); + } + + disposables.add(session.onDidChange(e => { + if (e.status === KeywordRecognitionStatus.Stopped) { + onSessionStoppedOrCanceled(); + } + })); + + try { + return (await Event.toPromise(session.onDidChange)).status; + } finally { + onSessionStoppedOrCanceled(); + } + } +} diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 48f21fb8ac134..d70eabdfb2dca 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { Event } from 'vs/base/common/event'; +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 { ILogService } from 'vs/platform/log/common/log'; export const ISpeechService = createDecorator('speechService'); @@ -41,7 +39,8 @@ export interface ISpeechToTextSession extends IDisposable { export enum KeywordRecognitionStatus { Recognized = 1, - Stopped = 2 + Stopped = 2, + Canceled = 3 } export interface IKeywordRecognitionEvent { @@ -94,151 +93,3 @@ export interface ISpeechService { */ recognizeKeyword(token: CancellationToken): Promise; } - -export class SpeechService extends Disposable implements ISpeechService { - - readonly _serviceBrand: undefined; - - private readonly _onDidRegisterSpeechProvider = this._register(new Emitter()); - readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; - - private readonly _onDidUnregisterSpeechProvider = this._register(new Emitter()); - readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; - - get hasSpeechProvider() { return this.providers.size > 0; } - - private readonly providers = new Map(); - - private readonly hasSpeechProviderContext = HasSpeechProvider.bindTo(this.contextKeyService); - - constructor( - @ILogService private readonly logService: ILogService, - @IContextKeyService private readonly contextKeyService: IContextKeyService - ) { - super(); - } - - registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { - if (this.providers.has(identifier)) { - throw new Error(`Speech provider with identifier ${identifier} is already registered.`); - } - - this.providers.set(identifier, provider); - this.hasSpeechProviderContext.set(true); - - this._onDidRegisterSpeechProvider.fire(provider); - - return toDisposable(() => { - this.providers.delete(identifier); - this._onDidUnregisterSpeechProvider.fire(provider); - - if (this.providers.size === 0) { - this.hasSpeechProviderContext.set(false); - } - }); - } - - private readonly _onDidStartSpeechToTextSession = this._register(new Emitter()); - readonly onDidStartSpeechToTextSession = this._onDidStartSpeechToTextSession.event; - - private readonly _onDidEndSpeechToTextSession = this._register(new Emitter()); - readonly onDidEndSpeechToTextSession = this._onDidEndSpeechToTextSession.event; - - private _activeSpeechToTextSession: ISpeechToTextSession | undefined = undefined; - get hasActiveSpeechToTextSession() { return !!this._activeSpeechToTextSession; } - - private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); - - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { - const provider = firstOrDefault(Array.from(this.providers.values())); - if (!provider) { - throw new Error(`No Speech provider is registered.`); - } else if (this.providers.size > 1) { - this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); - } - - const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); - - const disposables = new DisposableStore(); - - const onSessionStoppedOrCanceled = () => { - if (session === this._activeSpeechToTextSession) { - this._activeSpeechToTextSession = undefined; - this.speechToTextInProgress.reset(); - this._onDidEndSpeechToTextSession.fire(); - } - - disposables.dispose(); - }; - - disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); - if (token.isCancellationRequested) { - onSessionStoppedOrCanceled(); - } - - disposables.add(session.onDidChange(e => { - switch (e.status) { - case SpeechToTextStatus.Started: - if (session === this._activeSpeechToTextSession) { - this.speechToTextInProgress.set(true); - this._onDidStartSpeechToTextSession.fire(); - } - break; - case SpeechToTextStatus.Stopped: - onSessionStoppedOrCanceled(); - break; - } - })); - - return session; - } - - private readonly _onDidStartKeywordRecognition = this._register(new Emitter()); - readonly onDidStartKeywordRecognition = this._onDidStartKeywordRecognition.event; - - private readonly _onDidEndKeywordRecognition = this._register(new Emitter()); - readonly onDidEndKeywordRecognition = this._onDidEndKeywordRecognition.event; - - private _activeKeywordRecognitionSession: IKeywordRecognitionSession | undefined = undefined; - get hasActiveKeywordRecognition() { return !!this._activeKeywordRecognitionSession; } - - async recognizeKeyword(token: CancellationToken): Promise { - const provider = firstOrDefault(Array.from(this.providers.values())); - if (!provider) { - throw new Error(`No Speech provider is registered.`); - } else if (this.providers.size > 1) { - this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); - } - - const session = this._activeKeywordRecognitionSession = provider.createKeywordRecognitionSession(token); - this._onDidStartKeywordRecognition.fire(); - - const disposables = new DisposableStore(); - - const onSessionStoppedOrCanceled = () => { - if (session === this._activeKeywordRecognitionSession) { - this._activeKeywordRecognitionSession = undefined; - this._onDidEndKeywordRecognition.fire(); - } - - disposables.dispose(); - }; - - disposables.add(token.onCancellationRequested(() => onSessionStoppedOrCanceled())); - if (token.isCancellationRequested) { - onSessionStoppedOrCanceled(); - } - - disposables.add(session.onDidChange(e => { - if (e.status === KeywordRecognitionStatus.Stopped) { - onSessionStoppedOrCanceled(); - } - })); - - try { - return (await Event.toPromise(session.onDidChange)).status; - } finally { - onSessionStoppedOrCanceled(); - } - } -} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 719bd0bc24c69..a7d0a54f6ddc6 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -181,7 +181,7 @@ import 'vs/workbench/contrib/contextmenu/browser/contextmenu.contribution'; import 'vs/workbench/contrib/notebook/browser/notebook.contribution'; // Speech -import 'vs/workbench/contrib/speech/common/speech.contribution'; +import 'vs/workbench/contrib/speech/browser/speech.contribution'; // Chat import 'vs/workbench/contrib/chat/browser/chat.contribution'; From 133b4e591c6e14847b775ddf8c8478d8d619fc23 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:07:01 +0100 Subject: [PATCH 0379/1863] Revert custom hover for input boxes (#205337) --- src/vs/base/browser/ui/inputbox/inputBox.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index a23848dd914e9..e4c89dd3affb6 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -11,8 +11,6 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { AnchorAlignment, 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 { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; @@ -113,7 +111,6 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private hover: ICustomHover | undefined; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -233,11 +230,7 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - if (!this.hover) { - this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.input, tooltip)); - } else { - this.hover.update(tooltip); - } + this.input.title = tooltip; } public setAriaLabel(label: string): void { From 00b91ce10ab487b4fa6e8fe3b157d1b97342956b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 10:21:57 +0100 Subject: [PATCH 0380/1863] fix https://github.com/microsoft/vscode/issues/203299 (#205342) --- .../inlineChat/electron-sandbox/inlineChatQuickVoice.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index ad596b50fe8dd..16c1d70d90b88 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -151,13 +151,9 @@ class QuickVoiceWidget implements IContentWidget { return null; } const selection = this._editor.getSelection(); - // const position = this._editor.getPosition(); return { - position: selection.getPosition(), - preference: [ - selection.getPosition().equals(selection.getStartPosition()) ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW, - ContentWidgetPositionPreference.EXACT - ] + position: selection.getStartPosition(), + preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.EXACT] }; } From 0f8cab37f1c453af2ce8a717d4a7c661ac8fe333 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:58:44 +0100 Subject: [PATCH 0381/1863] Git - add "Hard wrap all lines" code action is there are multiple long lines (#205349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - add "Hard wrap all lines" code action is there are multiple long lines * 💄 Moved code around --- extensions/git/src/diagnostics.ts | 52 ++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index f9b45a9b23ea5..aa09fbe61e841 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -82,6 +82,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider provideCodeActions(document: TextDocument, range: Range | Selection): CodeAction[] { const codeActions: CodeAction[] = []; const diagnostics = this.diagnosticsManager.getDiagnostics(document.uri); + const wrapAllLinesCodeAction = this.getWrapAllLinesCodeAction(document, diagnostics); for (const diagnostic of diagnostics) { if (!diagnostic.range.contains(range)) { @@ -96,17 +97,23 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; - codeActions.push(codeAction); + break; } case DiagnosticCodes.line_length: { const workspaceEdit = this.getWrapLineWorkspaceEdit(document, diagnostic.range); + const codeAction = new CodeAction(l10n.t('Hard wrap line'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; - codeActions.push(codeAction); + + if (wrapAllLinesCodeAction) { + wrapAllLinesCodeAction.diagnostics = [diagnostic]; + codeActions.push(wrapAllLinesCodeAction); + } + break; } } @@ -116,13 +123,45 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider } private getWrapLineWorkspaceEdit(document: TextDocument, range: Range): WorkspaceEdit { + const lineSegments = this.wrapTextDocumentLine(document, range.start.line); + + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); + + return workspaceEdit; + } + + private getWrapAllLinesCodeAction(document: TextDocument, diagnostics: readonly Diagnostic[]): CodeAction | undefined { + const lineLengthDiagnostics = diagnostics.filter(d => d.code === DiagnosticCodes.line_length); + if (lineLengthDiagnostics.length < 2) { + return undefined; + } + + const wrapAllLinesCodeAction = new CodeAction(l10n.t('Hard wrap all lines'), CodeActionKind.QuickFix); + wrapAllLinesCodeAction.edit = this.getWrapAllLinesWorkspaceEdit(document, lineLengthDiagnostics); + + return wrapAllLinesCodeAction; + } + + private getWrapAllLinesWorkspaceEdit(document: TextDocument, diagnostics: Diagnostic[]): WorkspaceEdit { + const workspaceEdit = new WorkspaceEdit(); + + for (const diagnostic of diagnostics) { + const lineSegments = this.wrapTextDocumentLine(document, diagnostic.range.start.line); + workspaceEdit.replace(document.uri, diagnostic.range, lineSegments.join('\n')); + } + + return workspaceEdit; + } + + private wrapTextDocumentLine(document: TextDocument, line: number): string[] { const config = workspace.getConfiguration('git'); const inputValidationLength = config.get('inputValidationLength', 50); const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); - const lineLengthThreshold = range.start.line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; + const lineLengthThreshold = line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; const lineSegments: string[] = []; - const lineText = document.lineAt(range.start.line).text; + const lineText = document.lineAt(line).text; let position = 0; while (lineText.length - position > lineLengthThreshold) { @@ -147,10 +186,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider lineSegments.push(lineText.substring(position)); } - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.replace(document.uri, range, lineSegments.join('\n')); - - return workspaceEdit; + return lineSegments; } dispose() { From b28c2debc506c4c9df9433846797d976948a1dfb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 11:02:03 +0100 Subject: [PATCH 0382/1863] fix https://github.com/microsoft/vscode/issues/202232 (#205351) --- src/vs/editor/common/languageSelector.ts | 11 +++++++++ .../workbench/api/common/extHost.api.impl.ts | 10 +++++--- .../api/common/extHostDocumentData.ts | 1 - .../api/common/extHostDocumentsAndEditors.ts | 13 ++--------- .../api/common/extHostInteractive.ts | 1 - .../workbench/api/common/extHostNotebook.ts | 2 +- .../api/common/extHostNotebookDocument.ts | 23 +++++++++++++------ 7 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/common/languageSelector.ts b/src/vs/editor/common/languageSelector.ts index e657a75337638..db32360aa2e79 100644 --- a/src/vs/editor/common/languageSelector.ts +++ b/src/vs/editor/common/languageSelector.ts @@ -131,3 +131,14 @@ export function score(selector: LanguageSelector | undefined, candidateUri: URI, return 0; } } + + +export function targetsNotebooks(selector: LanguageSelector): boolean { + if (typeof selector === 'string') { + return false; + } else if (Array.isArray(selector)) { + return selector.some(targetsNotebooks); + } else { + return !!(selector).notebookType; + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 4e95fc270b096..b93fdbeb5d7c6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -11,7 +11,7 @@ import { Schemas, matchesScheme } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { score } from 'vs/editor/common/languageSelector'; +import { score, targetsNotebooks } from 'vs/editor/common/languageSelector'; import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; import { OverviewRulerLane } from 'vs/editor/common/model'; import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -533,8 +533,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguages.changeLanguage(document.uri, languageId); }, match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number { - const notebook = extHostDocuments.getDocumentData(document.uri)?.notebook; - return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); + const interalSelector = typeConverters.LanguageSelector.from(selector); + let notebook: vscode.NotebookDocument | undefined; + if (targetsNotebooks(interalSelector)) { + notebook = extHostNotebook.notebookDocuments.find(value => Boolean(value.getCell(document.uri)))?.apiNotebook; + } + return score(interalSelector, document.uri, document.languageId, true, notebook?.uri, notebook?.notebookType); }, registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 139f826be433e..ee321b2575a87 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -37,7 +37,6 @@ export class ExtHostDocumentData extends MirrorTextModel { uri: URI, lines: string[], eol: string, versionId: number, private _languageId: string, private _isDirty: boolean, - public readonly notebook?: vscode.NotebookDocument | undefined ) { super(uri, lines, eol, versionId); } diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index bb64f26fa8e80..224d9d5537132 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IModelAddedData, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; @@ -31,14 +31,6 @@ class Reference { } } -export interface IExtHostModelAddedData extends IModelAddedData { - notebook?: vscode.NotebookDocument; -} - -export interface IExtHostDocumentsAndEditorsDelta extends IDocumentsAndEditorsDelta { - addedDocuments?: IExtHostModelAddedData[]; -} - export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { readonly _serviceBrand: undefined; @@ -67,7 +59,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha this.acceptDocumentsAndEditorsDelta(delta); } - acceptDocumentsAndEditorsDelta(delta: IExtHostDocumentsAndEditorsDelta): void { + acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { const removedDocuments: ExtHostDocumentData[] = []; const addedDocuments: ExtHostDocumentData[] = []; @@ -105,7 +97,6 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha data.versionId, data.languageId, data.isDirty, - data.notebook )); this._documents.set(resource, ref); addedDocuments.push(ref.value); diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index b53d846c2e141..63296ab10300b 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -52,7 +52,6 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { uri: uri, isDirty: false, versionId: 1, - notebook: this._extHostNotebooks.getNotebookDocument(URI.revive(notebookUri))?.apiNotebook }] }); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 6af6206b7fc11..75dc5a402ffc3 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -625,7 +625,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { ); // add cell document as vscode.TextDocument - addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.apiNotebook, cell))); + addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(cell))); this._documents.get(uri)?.dispose(); this._documents.set(uri, document); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index e8970f70dee6f..2cc7a200edc60 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -7,7 +7,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { NotebookRange } from 'vs/workbench/api/common/extHostTypes'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -33,15 +33,14 @@ class RawContentChangeEvent { export class ExtHostCell { - static asModelAddData(notebook: vscode.NotebookDocument, cell: extHostProtocol.NotebookCellDto): IExtHostModelAddedData { + static asModelAddData(cell: extHostProtocol.NotebookCellDto): extHostProtocol.IModelAddedData { return { EOL: cell.eol, lines: cell.source, languageId: cell.language, uri: cell.uri, isDirty: false, - versionId: 1, - notebook + versionId: 1 }; } @@ -356,7 +355,7 @@ export class ExtHostNotebookDocument { } const contentChangeEvents: RawContentChangeEvent[] = []; - const addedCellDocuments: IExtHostModelAddedData[] = []; + const addedCellDocuments: extHostProtocol.IModelAddedData[] = []; const removedCellDocuments: URI[] = []; splices.reverse().forEach(splice => { @@ -365,7 +364,7 @@ export class ExtHostNotebookDocument { const extCell = new ExtHostCell(this, this._textDocumentsAndEditors, cell); if (!initialization) { - addedCellDocuments.push(ExtHostCell.asModelAddData(this.apiNotebook, cell)); + addedCellDocuments.push(ExtHostCell.asModelAddData(cell)); } return extCell; }); @@ -443,7 +442,17 @@ export class ExtHostNotebookDocument { return this._cells[index]; } - getCell(cellHandle: number): ExtHostCell | undefined { + getCell(cellHandle: number | URI): ExtHostCell | undefined { + if (URI.isUri(cellHandle)) { + const data = notebookCommon.CellUri.parse(cellHandle); + if (!data) { + return undefined; + } + if (data.notebook.toString() !== this.uri.toString()) { + return undefined; + } + cellHandle = data.handle; + } return this._cells.find(cell => cell.handle === cellHandle); } From 746c6d8b84260c475e173cf6e857e31f14ce8f09 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:03:19 +0100 Subject: [PATCH 0383/1863] adding review changes --- .../multiDiffEditorWidget.ts | 7 +++-- .../multiDiffEditorWidgetImpl.ts | 27 ++++++++++++------- .../bulkEdit/browser/preview/bulkEditPane.ts | 4 +-- .../browser/multiDiffEditor.ts | 22 +++++---------- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index a1405574d341e..dcc315e5b7d4f 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptionRevealData, IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -18,7 +18,6 @@ import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IRange } from 'vs/editor/common/core/range'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; export class MultiDiffEditorWidget extends Disposable { @@ -46,8 +45,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: IRange): void { - this._widgetImpl.get().reveal(resource, range); + public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + this._widgetImpl.get().reveal(revealData); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 3aedbd4cc57d8..26ad0f1b16426 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -26,7 +26,8 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IRange } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -190,7 +191,8 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: IRange): void { + public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + const resource = revealData.resource; const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -199,7 +201,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (revealData.range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } @@ -299,16 +301,21 @@ interface IMultiDiffDocState { selections?: ISelection[]; } -export type IMultiDiffResource = { original: URI } | { modified: URI }; +export interface IMultiDiffEditorOptions extends ITextEditorOptions { + viewState?: IMultiDiffEditorOptionsViewState; +} -export function isIMultiDiffResource(obj: any): obj is IMultiDiffResource { - if (('original' in obj && obj.original instanceof URI) - || ('modified' in obj && obj.modified instanceof URI)) { - return true; - } - return false; +export interface IMultiDiffEditorOptionsViewState { + revealData?: IMultiDiffEditorOptionRevealData; } +export interface IMultiDiffEditorOptionRevealData { + resource: IMultiDiffResource; + range: Range; +} + +export type IMultiDiffResource = { original: URI } | { modified: URI }; + class VirtualizedViewItem extends Disposable { private readonly _templateRef = this._register(disposableObservableValue | undefined>(this, undefined)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 913f8bdf1366c..8739a79bbbb85 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -27,7 +27,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IViewDescriptorService } from 'vs/workbench/common/views'; @@ -39,6 +38,7 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; import { Range } from 'vs/editor/common/core/range'; +import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; const enum State { Data = 'data', @@ -335,7 +335,7 @@ export class BulkEditPane extends ViewPane { } const resources = await this._resolveResources(fileOperations); - const options: Mutable = { + const options: Mutable = { ...e.editorOptions, viewState: { revealData: { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index 7a7aa309a9eaa..ed582ca29b635 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -8,7 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -24,10 +23,9 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorViewState, IMultiDiffResource, isIMultiDiffResource } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IRange, Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -73,11 +71,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { + override async setInput(input: MultiDiffEditorInput, options: IMultiDiffEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); this._viewModel = await input.getViewModel(); this._multiDiffEditorWidget!.setViewModel(this._viewModel); @@ -89,20 +83,16 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From ac465646a9e89eea1bf23941299694716555a2b1 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:12:15 +0100 Subject: [PATCH 0384/1863] removing the interface for revealData --- .../multiDiffEditorWidget/multiDiffEditorWidget.ts | 5 +++-- .../multiDiffEditorWidgetImpl.ts | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index dcc315e5b7d4f..60adf436d1502 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorOptionRevealData, IMultiDiffEditorViewState, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; @@ -19,6 +19,7 @@ import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditorWidget extends Disposable { private readonly _dimension = observableValue(this, undefined); @@ -45,7 +46,7 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { this._widgetImpl.get().reveal(revealData); } diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index 26ad0f1b16426..e423974d4fb70 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,7 +191,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(revealData: IMultiDiffEditorOptionRevealData): void { + public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { const resource = revealData.resource; const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; @@ -306,12 +306,10 @@ export interface IMultiDiffEditorOptions extends ITextEditorOptions { } export interface IMultiDiffEditorOptionsViewState { - revealData?: IMultiDiffEditorOptionRevealData; -} - -export interface IMultiDiffEditorOptionRevealData { - resource: IMultiDiffResource; - range: Range; + revealData?: { + resource: IMultiDiffResource; + range: Range; + }; } export type IMultiDiffResource = { original: URI } | { modified: URI }; From cff1e8e9a755d206fb33237bb6b6337d171471f2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 11:00:18 +0100 Subject: [PATCH 0385/1863] Removes accessibleDiffViewer dependency to DiffEditorEditors --- .../components/accessibleDiffViewer.ts | 113 ++++++++++++++---- .../widget/diffEditor/diffEditorWidget.ts | 4 +- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index aa3d6da540292..3d4e7ef30676e 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -15,7 +15,6 @@ import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecy import { IObservable, ITransaction, autorun, autorunWithStore, derived, derivedWithStore, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { applyStyle } from 'vs/editor/browser/widget/diffEditor/utils'; import { EditorFontLigatures, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; @@ -34,11 +33,33 @@ import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/ac import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import 'vs/css!./accessibleDiffViewer'; +import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; const accessibleDiffViewerInsertIcon = registerIcon('diff-review-insert', Codicon.add, localize('accessibleDiffViewerInsertIcon', 'Icon for \'Insert\' in accessible diff viewer.')); const accessibleDiffViewerRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, localize('accessibleDiffViewerRemoveIcon', 'Icon for \'Remove\' in accessible diff viewer.')); const accessibleDiffViewerCloseIcon = registerIcon('diff-review-close', Codicon.close, localize('accessibleDiffViewerCloseIcon', 'Icon for \'Close\' in accessible diff viewer.')); +export interface IAccessibleDiffViewerModel { + getOriginalModel(): ITextModel; + getOriginalOptions(): IComputedEditorOptions; + + /** + * Should do: `setSelection`, `revealLine` and `focus` + */ + originalReveal(range: Range): void; + + getModifiedModel(): ITextModel; + getModifiedOptions(): IComputedEditorOptions; + /** + * Should do: `setSelection`, `revealLine` and `focus` + */ + modifiedReveal(range?: Range): void; + modifiedSetSelection(range: Range): void; + modifiedFocus(): void; + + getModifiedPosition(): Position | undefined; +} + export class AccessibleDiffViewer extends Disposable { public static _ttPolicy = createTrustedTypesPolicy('diffReview', { createHTML: value => value }); @@ -50,7 +71,7 @@ export class AccessibleDiffViewer extends Disposable { private readonly _width: IObservable, private readonly _height: IObservable, private readonly _diffs: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -62,8 +83,8 @@ export class AccessibleDiffViewer extends Disposable { if (!visible) { return null; } - const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._editors, this._setVisible, this._canClose)); - const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._editors)); + const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._models, this._setVisible, this._canClose)); + const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._models)); return { model, view, }; }).recomputeInitiallyAndOnChange(this._store); @@ -106,7 +127,7 @@ class ViewModel extends Disposable { constructor( private readonly _diffs: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, public readonly canClose: IObservable, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService, @@ -123,12 +144,12 @@ class ViewModel extends Disposable { const groups = computeViewElementGroups( diffs, - this._editors.original.getModel()!.getLineCount(), - this._editors.modified.getModel()!.getLineCount() + this._models.getOriginalModel().getLineCount(), + this._models.getModifiedModel().getLineCount() ); transaction(tx => { - const p = this._editors.modified.getPosition(); + const p = this._models.getModifiedPosition(); if (p) { const nextGroup = groups.findIndex(g => p?.lineNumber < g.range.modified.endLineNumberExclusive); if (nextGroup !== -1) { @@ -155,7 +176,7 @@ class ViewModel extends Disposable { const currentViewItem = this.currentElement.read(reader); if (currentViewItem && currentViewItem.type !== LineType.Header) { const lineNumber = currentViewItem.modifiedLineNumber ?? currentViewItem.diff.modified.startLineNumber; - this._editors.modified.setSelection(Range.fromPositions(new Position(lineNumber, 1))); + this._models.modifiedSetSelection(Range.fromPositions(new Position(lineNumber, 1))); } })); } @@ -194,27 +215,27 @@ class ViewModel extends Disposable { } revealCurrentElementInEditor(): void { + if (!this.canClose.get()) { return; } this._setVisible(false, undefined); const curElem = this.currentElement.get(); if (curElem) { if (curElem.type === LineType.Deleted) { - this._editors.original.setSelection(Range.fromPositions(new Position(curElem.originalLineNumber, 1))); - this._editors.original.revealLine(curElem.originalLineNumber); - this._editors.original.focus(); + this._models.originalReveal(Range.fromPositions(new Position(curElem.originalLineNumber, 1))); } else { - if (curElem.type !== LineType.Header) { - this._editors.modified.setSelection(Range.fromPositions(new Position(curElem.modifiedLineNumber, 1))); - this._editors.modified.revealLine(curElem.modifiedLineNumber); - } - this._editors.modified.focus(); + this._models.modifiedReveal( + curElem.type !== LineType.Header + ? Range.fromPositions(new Position(curElem.modifiedLineNumber, 1)) + : undefined + ); } } } close(): void { + if (!this.canClose.get()) { return; } this._setVisible(false, undefined); - this._editors.modified.focus(); + this._models.modifiedFocus(); } } @@ -327,7 +348,7 @@ class View extends Disposable { private readonly _model: ViewModel, private readonly _width: IObservable, private readonly _height: IObservable, - private readonly _editors: DiffEditorEditors, + private readonly _models: IAccessibleDiffViewerModel, @ILanguageService private readonly _languageService: ILanguageService, ) { super(); @@ -412,8 +433,8 @@ class View extends Disposable { } private _render(store: DisposableStore): void { - const originalOptions = this._editors.original.getOptions(); - const modifiedOptions = this._editors.modified.getOptions(); + const originalOptions = this._models.getOriginalOptions(); + const modifiedOptions = this._models.getModifiedOptions(); const container = document.createElement('div'); container.className = 'diff-review-table'; @@ -423,8 +444,8 @@ class View extends Disposable { reset(this._content, container); - const originalModel = this._editors.original.getModel(); - const modifiedModel = this._editors.modified.getModel(); + const originalModel = this._models.getOriginalModel(); + const modifiedModel = this._models.getModifiedModel(); if (!originalModel || !modifiedModel) { return; } @@ -659,3 +680,49 @@ class View extends Disposable { return r.html; } } + +export class AccessibleDiffViewerModelFromEditors implements IAccessibleDiffViewerModel { + constructor(private readonly editors: DiffEditorEditors) { } + + getOriginalModel(): ITextModel { + return this.editors.original.getModel()!; + } + + getOriginalOptions(): IComputedEditorOptions { + return this.editors.original.getOptions(); + } + + originalReveal(range: Range): void { + this.editors.original.revealRange(range); + this.editors.original.setSelection(range); + this.editors.original.focus(); + } + + getModifiedModel(): ITextModel { + return this.editors.modified.getModel()!; + } + + getModifiedOptions(): IComputedEditorOptions { + return this.editors.modified.getOptions(); + } + + modifiedReveal(range?: Range | undefined): void { + if (range) { + this.editors.modified.revealRange(range); + this.editors.modified.setSelection(range); + } + this.editors.modified.focus(); + } + + modifiedSetSelection(range: Range): void { + this.editors.modified.setSelection(range); + } + + modifiedFocus(): void { + this.editors.modified.focus(); + } + + getModifiedPosition(): Position | undefined { + return this.editors.modified.getPosition() ?? undefined; + } +} diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index acf848c25c418..6b57de66397a4 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -17,7 +17,7 @@ import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { AccessibleDiffViewer } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; +import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; @@ -233,7 +233,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._rootSizeObserver.width, this._rootSizeObserver.height, this._diffModel.map((m, r) => m?.diff.read(r)?.mappings.map(m => m.lineRangeMapping)), - this._editors, + new AccessibleDiffViewerModelFromEditors(this._editors), ) ).recomputeInitiallyAndOnChange(this._store); From 10bc7cabe1d267cfa9c34dd0c5abc29ff9ba976c Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 11:25:10 +0100 Subject: [PATCH 0386/1863] Diff editor code improvements (#205147) --- .../widget/diffEditor/components/diffEditorEditors.ts | 8 ++++++-- .../diffEditor/features/hideUnchangedRegionsFeature.ts | 1 + .../widget/diffEditor/features/movedBlocksLinesFeature.ts | 6 ++---- src/vs/editor/browser/widget/diffEditor/utils.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 1f26c2cad8b16..79d6509b225c1 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, autorunHandleChanges, observableFromEvent } from 'vs/base/common/observable'; +import { IObservable, IReader, autorunHandleChanges, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -36,6 +36,8 @@ export class DiffEditorEditors extends Disposable { public readonly modifiedSelections: IObservable; public readonly modifiedCursor: IObservable; + public readonly originalCursor: IObservable; + constructor( private readonly originalEditorElement: HTMLElement, private readonly modifiedEditorElement: HTMLElement, @@ -56,7 +58,9 @@ export class DiffEditorEditors extends Disposable { this.modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); this.modifiedSelections = observableFromEvent(this.modified.onDidChangeCursorSelection, () => this.modified.getSelections() ?? []); - this.modifiedCursor = observableFromEvent(this.modified.onDidChangeCursorPosition, () => this.modified.getPosition() ?? new Position(1, 1)); + this.modifiedCursor = derivedOpts({ owner: this, equalityComparer: Position.equals }, reader => this.modifiedSelections.read(reader)[0]?.getPosition() ?? new Position(1, 1)); + + this.originalCursor = observableFromEvent(this.original.onDidChangeCursorPosition, () => this.original.getPosition() ?? new Position(1, 1)); this._register(autorunHandleChanges({ createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions), diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index fb45a24f3c0ef..f2eb90c6d2f12 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -268,6 +268,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { } this._register(autorun(reader => { + /** @description Update CollapsedCodeOverlayWidget canMove* css classes */ const isFullyRevealed = this._unchangedRegion.visibleLineCountTop.read(reader) + this._unchangedRegion.visibleLineCountBottom.read(reader) === this._unchangedRegion.lineCount; this._nodes.bottom.classList.toggle('canMoveTop', !isFullyRevealed); diff --git a/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts index 5fa2d56fb0a6c..af2b6a8899584 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature.ts @@ -83,8 +83,6 @@ export class MovedBlocksLinesFeature extends Disposable { } })); - const originalCursorPosition = observableFromEvent(this._editors.original.onDidChangeCursorPosition, () => this._editors.original.getPosition()); - const modifiedCursorPosition = observableFromEvent(this._editors.modified.onDidChangeCursorPosition, () => this._editors.modified.getPosition()); const originalHasFocus = observableSignalFromEvent( 'original.onDidFocusEditorWidget', e => this._editors.original.onDidFocusEditorWidget(() => setTimeout(() => e(undefined), 0)) @@ -115,14 +113,14 @@ export class MovedBlocksLinesFeature extends Disposable { let movedText: MovedText | undefined = undefined; if (diff && lastChangedEditor === 'original') { - const originalPos = originalCursorPosition.read(reader); + const originalPos = this._editors.originalCursor.read(reader); if (originalPos) { movedText = diff.movedTexts.find(m => m.lineRangeMapping.original.contains(originalPos.lineNumber)); } } if (diff && lastChangedEditor === 'modified') { - const modifiedPos = modifiedCursorPosition.read(reader); + const modifiedPos = this._editors.modifiedCursor.read(reader); if (modifiedPos) { movedText = diff.movedTexts.find(m => m.lineRangeMapping.modified.contains(modifiedPos.lineNumber)); } diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index 0109a630f203a..b71da8bb55d19 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -443,7 +443,7 @@ function addLength(position: Position, length: LengthObj): Position { export function bindContextKey(key: RawContextKey, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable { const boundKey = key.bindTo(service); - return autorunOpts({ debugName: () => `Update ${key.key}` }, reader => { + return autorunOpts({ debugName: () => `Set Context Key "${key.key}"` }, reader => { boundKey.set(computeValue(reader)); }); } From 05e5d4bb073221eb49c4f161d55ea4f48ff88958 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 16 Feb 2024 11:32:28 +0100 Subject: [PATCH 0387/1863] fix https://github.com/microsoft/vscode/issues/203373 (#205354) --- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 9b713be146e1d..15afa9df6bf53 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -165,6 +165,10 @@ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick { display: flex; + justify-content: start; + overflow: hidden; + margin: auto; + max-width: 600px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-icon { From 0fd2b0ce5d941c8c24713076ec07c6f794a1d860 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 16 Feb 2024 11:34:48 +0100 Subject: [PATCH 0388/1863] inlining the resource and the range --- .../widget/multiDiffEditorWidget/multiDiffEditorWidget.ts | 4 ++-- .../multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts | 5 ++--- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 60adf436d1502..5a869f83bd2f7 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -46,8 +46,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { - this._widgetImpl.get().reveal(revealData); + public reveal(resource: IMultiDiffResource, range: Range): void { + this._widgetImpl.get().reveal(resource, range); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index e423974d4fb70..ca4e240c1dace 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -191,8 +191,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(revealData: { resource: IMultiDiffResource; range: Range }): void { - const resource = revealData.resource; + public reveal(resource: IMultiDiffResource, range: Range): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -201,7 +200,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (revealData.range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index ed582ca29b635..bb8bdc7c16e53 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -92,7 +92,7 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From e713ba0fb922a8e466897652d8a587b8669eeb8e Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:48:08 +0100 Subject: [PATCH 0389/1863] Git - Add context menu to history items (#205359) --- extensions/git/package.json | 28 ++++++++++++++++--- .../contrib/scm/browser/scmViewPane.ts | 7 +++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 4e26cff75f4ef..c2745213c681f 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1882,6 +1882,11 @@ "command": "git.viewAllChanges", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewAllChanges", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/incomingChanges/historyItem/context": [ @@ -1889,6 +1894,11 @@ "command": "git.viewCommit", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewCommit", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/outgoingChanges": [ @@ -1906,13 +1916,13 @@ "scm/outgoingChanges/context": [ { "command": "git.pushRef", - "group": "1_modification@1", - "when": "scmProvider == git && scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && scmHistoryItemGroupHasUpstream", + "group": "1_modification@1" }, { "command": "git.publish", - "group": "1_modification@1", - "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream" + "when": "scmProvider == git && !scmHistoryItemGroupHasUpstream", + "group": "1_modification@1" } ], "scm/outgoingChanges/allChanges/context": [ @@ -1920,6 +1930,11 @@ "command": "git.viewAllChanges", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewAllChanges", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "scm/outgoingChanges/historyItem/context": [ @@ -1927,6 +1942,11 @@ "command": "git.viewCommit", "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", "group": "inline@1" + }, + { + "command": "git.viewCommit", + "when": "scmProvider == git && scmHistoryItemFileCount != 0 && config.multiDiffEditor.experimental.enabled", + "group": "1_view@1" } ], "editor/title": [ diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index ca7a033fdcd62..e075d69cca4cc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -3222,6 +3222,13 @@ export class SCMViewPane extends ViewPane { actionRunner = new HistoryItemGroupActionRunner(); createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, actions); } + } else if (isSCMHistoryItemTreeElement(element)) { + const menus = this.scmViewService.menus.getRepositoryMenus(element.historyItemGroup.repository.provider); + const menu = menus.historyProviderMenu?.getHistoryItemMenu(element); + if (menu) { + actionRunner = new HistoryItemActionRunner(); + actions = collectContextMenuActions(menu); + } } actionRunner.onWillRun(() => this.tree.domFocus()); From bc7e1fc60904d89b111bb107f6fa0b9f7b331d3a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 10:20:57 +0100 Subject: [PATCH 0390/1863] ObservablePromise cleanup --- src/vs/base/common/observable.ts | 9 ++- .../base/common/observableInternal/derived.ts | 2 +- .../base/common/observableInternal/promise.ts | 71 +++++++++++++++---- .../browser/multiDiffEditorInput.ts | 8 +-- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts index 978c212d765cd..155ad2aa4f6de 100644 --- a/src/vs/base/common/observable.ts +++ b/src/vs/base/common/observable.ts @@ -49,15 +49,20 @@ export { } from 'vs/base/common/observableInternal/utils'; export { ObservableLazy, - ObservableLazyStatefulPromise, + ObservableLazyPromise, ObservablePromise, PromiseResult, waitForState, + derivedWithCancellationToken, } from 'vs/base/common/observableInternal/promise'; import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableInternal/logging'; -const enableLogging = false; +// Remove "//" in the next line to enable logging +const enableLogging = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + if (enableLogging) { setLogger(new ConsoleObservableLogger()); } diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index cab2c7c4bc1c7..70dc7168418c6 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -10,7 +10,7 @@ import { DebugNameData, IDebugNameData, Owner } from 'vs/base/common/observableI import { getLogger } from 'vs/base/common/observableInternal/logging'; export type EqualityComparer = (a: T, b: T) => boolean; -const defaultEqualityComparer: EqualityComparer = (a, b) => a === b; +export const defaultEqualityComparer: EqualityComparer = (a, b) => a === b; /** * Creates an observable that is derived from other observables. diff --git a/src/vs/base/common/observableInternal/promise.ts b/src/vs/base/common/observableInternal/promise.ts index 23292cdea50bf..363ffea7d6e36 100644 --- a/src/vs/base/common/observableInternal/promise.ts +++ b/src/vs/base/common/observableInternal/promise.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { IObservable, observableValue } from './base'; -import { derived } from 'vs/base/common/observableInternal/derived'; +import { IObservable, IReader, observableValue, transaction } from './base'; +import { Derived, defaultEqualityComparer, derived } from 'vs/base/common/observableInternal/derived'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DebugNameData, Owner } from 'vs/base/common/observableInternal/debugName'; export class ObservableLazy { private readonly _value = observableValue(this, undefined); @@ -38,15 +40,29 @@ export class ObservableLazy { export class ObservablePromise { private readonly _value = observableValue | undefined>(this, undefined); + /** + * The promise that this object wraps. + */ public readonly promise: Promise; - public readonly value: IObservable | undefined> = this._value; + + /** + * The current state of the promise. + * Is `undefined` if the promise didn't resolve yet. + */ + public readonly promiseResult: IObservable | undefined> = this._value; constructor(promise: Promise) { this.promise = promise.then(value => { - this._value.set(new PromiseResult(value, undefined), undefined); + transaction(tx => { + /** @description onPromiseResolved */ + this._value.set(new PromiseResult(value, undefined), tx); + }); return value; }, error => { - this._value.set(new PromiseResult(undefined, error), undefined); + transaction(tx => { + /** @description onPromiseRejected */ + this._value.set(new PromiseResult(undefined, error), tx); + }); throw error; }); } @@ -58,7 +74,7 @@ export class PromiseResult { * The value of the resolved promise. * Undefined if the promise rejected. */ - public readonly value: T | undefined, + public readonly data: T | undefined, /** * The error in case of a rejected promise. @@ -71,30 +87,30 @@ export class PromiseResult { /** * Returns the value if the promise resolved, otherwise throws the error. */ - public getValue(): T { + public getDataOrThrow(): T { if (this.error) { throw this.error; } - return this.value!; + return this.data!; } } /** * A lazy promise whose state is observable. */ -export class ObservableLazyStatefulPromise { - private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computeValue())); +export class ObservableLazyPromise { + private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computePromise())); /** * Does not enforce evaluation of the promise compute function. * Is undefined if the promise has not been computed yet. */ - public readonly cachedValue = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.value.read(reader)); + public readonly cachedPromiseResult = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.promiseResult.read(reader)); - constructor(private readonly _computeValue: () => Promise) { + constructor(private readonly _computePromise: () => Promise) { } - public getValue(): Promise { + public getPromise(): Promise { return this._lazyValue.getValue().promise; } } @@ -139,3 +155,32 @@ export function waitForState(observable: IObservable, predicate: (state: T } }); } + +export function derivedWithCancellationToken(computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(owner: object, computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IReader, cancellationToken: CancellationToken) => T) | object, computeFnOrUndefined?: ((reader: IReader, cancellationToken: CancellationToken) => T)): IObservable { + let computeFn: (reader: IReader, store: CancellationToken) => T; + let owner: Owner; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + let cancellationTokenSource: CancellationTokenSource | undefined = undefined; + return new Derived( + new DebugNameData(owner, undefined, computeFn), + r => { + if (cancellationTokenSource) { + cancellationTokenSource.dispose(true); + } + cancellationTokenSource = new CancellationTokenSource(); + return computeFn(r, cancellationTokenSource.token); + }, undefined, + undefined, + () => cancellationTokenSource?.dispose(), + defaultEqualityComparer, + ); +} diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index b813d41bff3d7..cffa05225e621 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } fr import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { deepClone } from 'vs/base/common/objects'; -import { ObservableLazyStatefulPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; +import { ObservableLazyPromise, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { constObservable, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; @@ -131,7 +131,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }); private async _createModel(): Promise { - const source = await this._resolvedSource.getValue(); + const source = await this._resolvedSource.getPromise(); const textResourceConfigurationService = this._textResourceConfigurationService; // Enables delayed disposing @@ -207,7 +207,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor }; } - private readonly _resolvedSource = new ObservableLazyStatefulPromise(async () => { + private readonly _resolvedSource = new ObservableLazyPromise(async () => { const source: IResolvedMultiDiffSource | undefined = this.initialResources ? new ConstResolvedMultiDiffSource(this.initialResources) : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource); @@ -229,7 +229,7 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor return false; } - private readonly _resources = derived(this, reader => this._resolvedSource.cachedValue.read(reader)?.value?.resources.read(reader)); + private readonly _resources = derived(this, reader => this._resolvedSource.cachedPromiseResult.read(reader)?.data?.resources.read(reader)); private readonly _isDirtyObservables = mapObservableArrayCached(this, this._resources.map(r => r || []), res => { const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false); const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false); From a0762a9a5c3a5daa5bbcee6e41823ed245a8d218 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 16 Feb 2024 14:06:19 +0100 Subject: [PATCH 0391/1863] voice - log success rates for speech and keyword (#205360) --- .../contrib/chat/common/voiceChat.ts | 2 +- .../contrib/speech/browser/speechService.ts | 48 +++++++++++++++++-- .../contrib/speech/common/speechService.ts | 2 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index b1d1d468ea1fa..ba4156495dbb3 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -136,7 +136,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); - const session = disposables.add(this.speechService.createSpeechToTextSession(token)); + const session = disposables.add(this.speechService.createSpeechToTextSession(token, 'chat')); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index 99a2fe06e81d1..e190ac66d3607 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -12,6 +12,7 @@ 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class SpeechService extends Disposable implements ISpeechService { @@ -32,7 +33,8 @@ export class SpeechService extends Disposable implements ISpeechService { constructor( @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); } @@ -68,7 +70,7 @@ export class SpeechService extends Disposable implements ISpeechService { private readonly speechToTextInProgress = SpeechToTextInProgress.bindTo(this.contextKeyService); - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + createSpeechToTextSession(token: CancellationToken, context: string = 'speech'): ISpeechToTextSession { const provider = firstOrDefault(Array.from(this.providers.values())); if (!provider) { throw new Error(`No Speech provider is registered.`); @@ -78,6 +80,9 @@ export class SpeechService extends Disposable implements ISpeechService { const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + const sessionStart = Date.now(); + let sessionRecognized = false; + const disposables = new DisposableStore(); const onSessionStoppedOrCanceled = () => { @@ -85,6 +90,24 @@ export class SpeechService extends Disposable implements ISpeechService { this._activeSpeechToTextSession = undefined; this.speechToTextInProgress.reset(); this._onDidEndSpeechToTextSession.fire(); + + type SpeechToTextSessionClassification = { + owner: 'bpasero'; + comment: 'An event that fires when a speech to text session is created'; + context: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Context of the session.' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Duration of the session.' }; + recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If speech was recognized.' }; + }; + type SpeechToTextSessionEvent = { + context: string; + duration: number; + recognized: boolean; + }; + this.telemetryService.publicLog2('speechToTextSession', { + context, + duration: Date.now() - sessionStart, + recognized: sessionRecognized + }); } disposables.dispose(); @@ -103,6 +126,10 @@ export class SpeechService extends Disposable implements ISpeechService { this._onDidStartSpeechToTextSession.fire(); } break; + case SpeechToTextStatus.Recognizing: + case SpeechToTextStatus.Recognized: + sessionRecognized = true; + break; case SpeechToTextStatus.Stopped: onSessionStoppedOrCanceled(); break; @@ -161,11 +188,26 @@ export class SpeechService extends Disposable implements ISpeechService { recognizeKeyword(); } + let status: KeywordRecognitionStatus; try { - return await result.p; + status = await result.p; } finally { disposables.dispose(); } + + type KeywordRecognitionClassification = { + owner: 'bpasero'; + comment: 'An event that fires when a speech keyword detection is started'; + recognized: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'If the keyword was recognized.' }; + }; + type KeywordRecognitionEvent = { + recognized: boolean; + }; + this.telemetryService.publicLog2('keywordRecognition', { + recognized: status === KeywordRecognitionStatus.Recognized + }); + + return status; } private async doRecognizeKeyword(token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index d70eabdfb2dca..1e6e442eaefe5 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -79,7 +79,7 @@ export interface ISpeechService { * Starts to transcribe speech from the default microphone. The returned * session object provides an event to subscribe for transcribed text. */ - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createSpeechToTextSession(token: CancellationToken, context?: string): ISpeechToTextSession; readonly onDidStartKeywordRecognition: Event; readonly onDidEndKeywordRecognition: Event; From ae7a786d1b5f11001fcae8a465577ce0fb0ccb30 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:32:31 +0100 Subject: [PATCH 0392/1863] Git - do not invoke post commit commands when calling commit through the git extension api (#205364) --- extensions/git/src/api/api1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 1a0a483409c27..38aa9ec4236f1 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -257,7 +257,7 @@ export class ApiRepository implements Repository { } commit(message: string, opts?: CommitOptions): Promise { - return this.repository.commit(message, opts); + return this.repository.commit(message, { ...opts, postCommitCommand: null }); } merge(ref: string): Promise { From f439a7d9c53d6316ed67f0d663d78f29f824a04b Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 16 Feb 2024 14:52:45 +0100 Subject: [PATCH 0393/1863] wip - use accessible diff viewer for hunk info --- .../contrib/inlineChat/browser/inlineChat.css | 1 + .../browser/inlineChatController.ts | 10 +- .../browser/inlineChatStrategies.ts | 11 +- .../inlineChat/browser/inlineChatWidget.ts | 125 +++++++++++++++++- 4 files changed, 134 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index ee46f6df25e52..7d299bbc1e548 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -307,6 +307,7 @@ padding-top: 6px; } +.monaco-editor .inline-chat .diff-review.hidden, .monaco-editor .inline-chat .previewDiff.hidden, .monaco-editor .inline-chat .previewCreate.hidden, .monaco-editor .inline-chat .previewCreateTitle.hidden { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index ce432473cfbb5..39b4563509b5b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -27,7 +27,6 @@ import { ProviderResult, TextEdit } from 'vs/editor/common/languages'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { localize } from 'vs/nls'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -145,7 +144,6 @@ export class InlineChatController implements IEditorContribution { @IConfigurationService private readonly _configurationService: IConfigurationService, @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @@ -235,13 +233,7 @@ export class InlineChatController implements IEditorContribution { } private _getMode(): EditMode { - const editMode = this._configurationService.inspect(InlineChatConfigKeys.Mode); - let editModeValue = editMode.value; - if (this._accessibilityService.isScreenReaderOptimized() && editModeValue === editMode.defaultValue) { - // By default, use preview mode for screen reader users - editModeValue = EditMode.Preview; - } - return editModeValue!; + return this._configurationService.getValue(InlineChatConfigKeys.Mode); } getWidgetPosition(): Position | undefined { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 78cfd2a3bc52f..9659c4cdb28d6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -36,6 +36,7 @@ import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export interface IEditObserver { start(): void; @@ -404,6 +405,8 @@ type HunkDisplayData = { toggleDiff?: () => any; remove(): void; move: (next: boolean) => void; + + hunk: HunkInformation; }; @@ -441,6 +444,7 @@ export class LiveStrategy extends EditModeStrategy { zone: InlineChatZoneWidget, @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IInstantiationService protected readonly _instaService: IInstantiationService, ) { super(session, editor, zone); @@ -652,6 +656,7 @@ export class LiveStrategy extends EditModeStrategy { : zoneLineNumber - hunkRanges[0].endLineNumber; data = { + hunk: hunkData, decorationIds, viewZoneId: '', viewZone: viewZoneData, @@ -661,7 +666,7 @@ export class LiveStrategy extends EditModeStrategy { discardHunk, toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined, remove, - move + move, }; this._hunkDisplayData.set(hunkData, data); @@ -700,6 +705,10 @@ export class LiveStrategy extends EditModeStrategy { const remainingHunks = this._session.hunkData.pending; this._updateSummaryMessage(remainingHunks); + if (this._accessibilityService.isScreenReaderOptimized()) { + this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk); + } + this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff)); this.toggleDiff = widgetData.toggleDiff; this.acceptHunk = async () => widgetData!.acceptHunk(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index ca909ba98e0e9..ba003be69a7d6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,6 +12,7 @@ import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./inlineChat'; @@ -19,11 +20,13 @@ import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfi import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; -import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; +import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; @@ -58,7 +61,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 { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +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'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; @@ -182,6 +185,7 @@ export class InlineChatWidget { h('div.label.status.hidden@statusLabel'), h('div.actions.hidden@feedbackToolbar'), ]), + h('div.accessibleViewer@accessibleViewer') ] ); @@ -204,6 +208,8 @@ export class InlineChatWidget { private readonly _previewDiffEditor: Lazy; private readonly _previewDiffModel = this._store.add(new MutableDisposable()); + private readonly _accessibleViewer = this._store.add(new MutableDisposable()); + private readonly _previewCreateTitle: ResourceLabel; private readonly _previewCreateEditor: Lazy; private readonly _previewCreateDispoable = this._store.add(new MutableDisposable()); @@ -467,6 +473,9 @@ export class InlineChatWidget { layout(_dim: Dimension) { this._isLayouting = true; try { + if (this._accessibleViewer.value) { + this._accessibleViewer.value.width = _dim.width - 12; + } const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar); const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */; const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth; @@ -489,6 +498,7 @@ export class InlineChatWidget { this._elements.previewCreate.style.height = `${previewCreateDim.height}px`; } + const lineHeight = this.parentEditor.getOption(EditorOption.lineHeight); const editorHeight = this.parentEditor.getLayoutInfo().height; const editorHeightInLines = Math.floor(editorHeight / lineHeight); @@ -510,7 +520,8 @@ export class InlineChatWidget { const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0; const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle); const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0; - return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/; + const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0; + return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + 18 /* padding */ + 8 /*shadow*/; } updateProgress(show: boolean) { @@ -735,6 +746,10 @@ export class InlineChatWidget { this.updateInfo(''); this.hideCreatePreview(); this.hideEditsPreview(); + + this._accessibleViewer.clear(); + this._elements.accessibleViewer.classList.toggle('hidden', true); + this._onDidChangeHeight.fire(); } @@ -908,6 +923,25 @@ export class InlineChatWidget { this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations)); updateSlashDecorations(); } + + + // --- accessible viewer + + showAccessibleHunk(session: Session, hunkData: HunkInformation): void { + + this._elements.accessibleViewer.classList.remove('hidden'); + this._accessibleViewer.clear(); + + this._accessibleViewer.value = this._instantiationService.createInstance(HunkAccessibleDiffViewer, + this._elements.accessibleViewer, + session, + hunkData, + new AccessibleHunk(this.parentEditor, session, hunkData) + ); + + this._onDidChangeHeight.fire(); + + } } export class InlineChatZoneWidget extends ZoneWidget { @@ -1063,3 +1097,88 @@ export class InlineChatZoneWidget extends ZoneWidget { aria.status(localize('inlineChatClosed', 'Closed inline chat widget')); } } + +class HunkAccessibleDiffViewer extends AccessibleDiffViewer { + + readonly height: number; + + set width(value: number) { + this._width2.set(value, undefined); + } + + private readonly _width2: ISettableObservable; + + constructor( + parentNode: HTMLElement, + session: Session, + hunk: HunkInformation, + models: IAccessibleDiffViewerModel, + @IInstantiationService instantiationService: IInstantiationService, + ) { + const width = observableValue('width', 0); + const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk)); + const diffs = derived(r => [diff.read(r)]); + const lines = Math.min(6, 3 * diff.get().changedLineCount); + const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines; + + super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService); + + this.height = height; + this._width2 = width; + + this._store.add(session.textModelN.onDidChangeContent(() => { + diff.set(HunkAccessibleDiffViewer._asMapping(hunk), undefined); + })); + } + + private static _asMapping(hunk: HunkInformation): DetailedLineRangeMapping { + const ranges0 = hunk.getRanges0(); + const rangesN = hunk.getRangesN(); + const originalLineRange = LineRange.fromRangeInclusive(ranges0[0]); + const modifiedLineRange = LineRange.fromRangeInclusive(rangesN[0]); + const innerChanges: RangeMapping[] = []; + for (let i = 1; i < ranges0.length; i++) { + innerChanges.push(new RangeMapping(ranges0[i], rangesN[i])); + } + return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, innerChanges); + } + +} + +class AccessibleHunk implements IAccessibleDiffViewerModel { + + constructor( + private readonly _editor: ICodeEditor, + private readonly _session: Session, + private readonly _hunk: HunkInformation + ) { } + + getOriginalModel(): ITextModel { + return this._session.textModel0; + } + getModifiedModel(): ITextModel { + return this._session.textModelN; + } + getOriginalOptions(): IComputedEditorOptions { + return this._editor.getOptions(); + } + getModifiedOptions(): IComputedEditorOptions { + return this._editor.getOptions(); + } + originalReveal(range: Range): void { + // throw new Error('Method not implemented.'); + } + modifiedReveal(range?: Range | undefined): void { + this._editor.revealRangeInCenterIfOutsideViewport(range || this._hunk.getRangesN()[0], ScrollType.Smooth); + } + modifiedSetSelection(range: Range): void { + // this._editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth); + // this._editor.setSelection(range); + } + modifiedFocus(): void { + this._editor.focus(); + } + getModifiedPosition(): Position | undefined { + return this._hunk.getRangesN()[0].getStartPosition(); + } +} From 3fdb0fa6e0e8c8b8443123e37caa8666d538f024 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:54:39 +0100 Subject: [PATCH 0394/1863] Refactor hover delegate assignments (#205366) :lipstick: --- src/vs/base/browser/ui/actionbar/actionViewItems.ts | 1 - src/vs/platform/quickinput/browser/quickInputService.ts | 2 +- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 2 +- src/vs/workbench/contrib/timeline/browser/timelinePane.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 804b0c386acdb..a714411e3a20e 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -226,7 +226,6 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - this.element.title = ''; if (!this.customHover) { const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); this.customHover = setupCustomHover(hoverDelegate, this.element, title); diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index c2d178c4c463a..2974eed807762 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -90,7 +90,7 @@ export class QuickInputService extends Themable implements IQuickInputService { options: IWorkbenchListOptions ) => this.instantiationService.createInstance(WorkbenchList, user, container, delegate, renderers, options) as List, styles: this.computeStyles(), - hoverDelegate: this.instantiationService.createInstance(QuickInputHoverDelegate) + hoverDelegate: this._register(this.instantiationService.createInstance(QuickInputHoverDelegate)) }; const controller = this._register(new QuickInputController({ diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 3559f22390ef3..102de6b52f8da 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -355,7 +355,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer Date: Fri, 16 Feb 2024 15:06:36 +0100 Subject: [PATCH 0395/1863] Git - toggle diagnostics when git.experimental.inputValidation setting changes (#205355) --- extensions/git/src/diagnostics.ts | 23 ++++++++++++++--------- extensions/git/src/main.ts | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index aa09fbe61e841..3d5cba205c56a 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -5,6 +5,7 @@ import { CodeAction, CodeActionKind, CodeActionProvider, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, Range, Selection, TextDocument, Uri, WorkspaceEdit, l10n, languages, workspace } from 'vscode'; import { mapEvent, filterEvent, dispose } from './util'; +import { Model } from './model'; export enum DiagnosticCodes { empty_message = 'empty_message', @@ -17,37 +18,41 @@ export class GitCommitInputBoxDiagnosticsManager { private readonly severity = DiagnosticSeverity.Warning; private readonly disposables: Disposable[] = []; - constructor() { + constructor(private readonly model: Model) { this.diagnostics = languages.createDiagnosticCollection(); - mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.validateTextDocument, this, this.disposables); + + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.experimental.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); } public getDiagnostics(uri: Uri): ReadonlyArray { return this.diagnostics.get(uri) ?? []; } - private validateTextDocument(document: TextDocument): void { - this.diagnostics.delete(document.uri); + private onDidChangeConfiguration(): void { + for (const repository of this.model.repositories) { + this.onDidChangeTextDocument(repository.inputBox.document); + } + } + private onDidChangeTextDocument(document: TextDocument): void { const config = workspace.getConfiguration('git'); const inputValidation = config.get('experimental.inputValidation', false) === true; if (!inputValidation) { + this.diagnostics.set(document.uri, undefined); return; } - const diagnostics: Diagnostic[] = []; - if (/^\s+$/.test(document.getText())) { const documentRange = new Range(document.lineAt(0).range.start, document.lineAt(document.lineCount - 1).range.end); const diagnostic = new Diagnostic(documentRange, l10n.t('Current commit message only contains whitespace characters'), this.severity); diagnostic.code = DiagnosticCodes.empty_message; - diagnostics.push(diagnostic); - this.diagnostics.set(document.uri, diagnostics); - + this.diagnostics.set(document.uri, [diagnostic]); return; } + const diagnostics: Diagnostic[] = []; const inputValidationLength = config.get('inputValidationLength', 50); const inputValidationSubjectLength = config.get('inputValidationSubjectLength', undefined); diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index c40aedaec691e..c2d9b974be726 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -119,7 +119,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); model.registerPostCommitCommandsProvider(postCommitCommandsProvider); - const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(); + const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(model); disposables.push(diagnosticsManager); const codeActionsProvider = new GitCommitInputBoxCodeActionsProvider(diagnosticsManager); From 03510273fc85ff1d3b9e4f6321d1658f517c3d8c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:14:45 +0100 Subject: [PATCH 0396/1863] Less hovers with pointers (#205368) less hovers with pointers --- src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts | 2 +- src/vs/workbench/browser/parts/editor/editorTabsControl.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 2c88a644bd5d4..a3b752ff1f2fa 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -230,7 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = getDefaultHoverDelegate('element'); + 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/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 782ad9b007d97..218c8808205f3 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -164,7 +164,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.renderDropdownAsChildElement = false; - this.tabsHoverDelegate = getDefaultHoverDelegate('element'); + this.tabsHoverDelegate = getDefaultHoverDelegate('mouse'); this.create(parent); } From 559a3413bc85864e22f29df1fb7a207d5aba7590 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 08:57:13 -0600 Subject: [PATCH 0397/1863] 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 dcca7e64e6864..d823a5b7fd931 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 187d16f6d513b..d1586f8c4ba7c 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 f8e9767117119..996effa074370 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 0398/1863] 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 996effa074370..70ee41baa6803 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 45ccdfa1af4e923edd954c9dfa0b131b91511ff5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 16 Feb 2024 16:14:41 +0100 Subject: [PATCH 0399/1863] Revert "Tunnel: Extend port mapping lookup also for querystring (#203908)" (#205370) This reverts commit 08eda834f933e05d5c3bac64b147da8e2cffcb5a. --- src/vs/platform/tunnel/common/tunnel.ts | 25 +++-------- .../tunnel/test/common/tunnel.test.ts | 44 ------------------- .../webview/common/webviewPortMapping.ts | 2 +- 3 files changed, 7 insertions(+), 64 deletions(-) delete mode 100644 src/vs/platform/tunnel/test/common/tunnel.test.ts diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index bff2feb757605..62c9059d2f81a 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -141,31 +141,18 @@ export interface ITunnelService { isPortPrivileged(port: number): boolean; } -export function extractLocalHostUriMetaDataForPortMapping(uri: URI, { checkQuery = true } = {}): { address: string; port: number } | undefined { +export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string; port: number } | undefined { if (uri.scheme !== 'http' && uri.scheme !== 'https') { return undefined; } const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority); - if (localhostMatch) { - return { - address: localhostMatch[1], - port: +localhostMatch[2], - }; - } - if (!uri.query || !checkQuery) { + if (!localhostMatch) { return undefined; } - const keyvalues = uri.query.split('&'); - for (const keyvalue of keyvalues) { - const value = keyvalue.split('=')[1]; - if (/^https?:/.exec(value)) { - const result = extractLocalHostUriMetaDataForPortMapping(URI.parse(value)); - if (result) { - return result; - } - } - } - return undefined; + return { + address: localhostMatch[1], + port: +localhostMatch[2], + }; } export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; diff --git a/src/vs/platform/tunnel/test/common/tunnel.test.ts b/src/vs/platform/tunnel/test/common/tunnel.test.ts deleted file mode 100644 index 4c763765aa07f..0000000000000 --- a/src/vs/platform/tunnel/test/common/tunnel.test.ts +++ /dev/null @@ -1,44 +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 * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; - - -suite('Tunnel', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - function portMappingDoTest(res: { address: string; port: number } | undefined, expectedAddress?: string, expectedPort?: number) { - assert.strictEqual(!expectedAddress, !res); - assert.strictEqual(res?.address, expectedAddress); - assert.strictEqual(res?.port, expectedPort); - } - - function portMappingTest(uri: string, expectedAddress?: string, expectedPort?: number) { - for (const checkQuery of [true, false]) { - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery }), expectedAddress, expectedPort); - } - } - - function portMappingTestQuery(uri: string, expectedAddress?: string, expectedPort?: number) { - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri)), expectedAddress, expectedPort); - portMappingDoTest(extractLocalHostUriMetaDataForPortMapping(URI.parse(uri), { checkQuery: false }), undefined, undefined); - } - - test('portMapping', () => { - portMappingTest('file:///foo.bar/baz'); - portMappingTest('http://foo.bar:1234'); - portMappingTest('http://localhost:8080', 'localhost', 8080); - portMappingTest('https://localhost:443', 'localhost', 443); - portMappingTest('http://127.0.0.1:3456', '127.0.0.1', 3456); - portMappingTest('http://0.0.0.0:7654', '0.0.0.0', 7654); - portMappingTest('http://localhost:8080/path?foo=bar', 'localhost', 8080); - portMappingTest('http://localhost:8080/path?foo=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8080); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Flocalhost%3A8081&url2=http%3A%2F%2Flocalhost%3A8082', 'localhost', 8081); - portMappingTestQuery('http://foo.bar/path?url=http%3A%2F%2Fmicrosoft.com%2Fbad&url2=http%3A%2F%2Flocalhost%3A8081', 'localhost', 8081); - }); -}); diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index 1ebc8534a284e..b47339d28fa1c 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -29,7 +29,7 @@ export class WebviewPortMappingManager implements IDisposable { public async getRedirect(resolveAuthority: IAddress | null | undefined, url: string): Promise { const uri = URI.parse(url); - const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri, { checkQuery: false }); + const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri); if (!requestLocalHostInfo) { return undefined; } From ad35b733520004674f14684fcbe1231afd65a5e0 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 16:16:05 +0100 Subject: [PATCH 0400/1863] Inline Edits (#204158) * implementation of inline edits --------- Co-authored-by: Krzysztof Cieslak Co-authored-by: Andrew Rice --- src/vs/editor/common/config/editorOptions.ts | 87 +++++ src/vs/editor/common/languages.ts | 21 ++ .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 3 +- .../common/standalone/standaloneEnums.ts | 176 ++++----- .../browser/ghostTextWidget.ts | 6 +- .../contrib/inlineEdit/browser/commandIds.ts | 10 + .../contrib/inlineEdit/browser/commands.ts | 151 ++++++++ .../inlineEdit/browser/ghostTextWidget.ts | 231 ++++++++++++ .../inlineEdit/browser/hoverParticipant.ts | 105 ++++++ .../browser/inlineEdit.contribution.ts | 20 + .../contrib/inlineEdit/browser/inlineEdit.css | 38 ++ .../browser/inlineEditController.ts | 344 ++++++++++++++++++ .../browser/inlineEditHintsWidget.css | 34 ++ .../browser/inlineEditHintsWidget.ts | 246 +++++++++++++ src/vs/editor/editor.all.ts | 1 + .../standalone/browser/standaloneLanguages.ts | 7 + src/vs/monaco.d.ts | 213 ++++++----- src/vs/platform/actions/common/actions.ts | 2 + .../api/browser/mainThreadLanguageFeatures.ts | 15 +- .../workbench/api/common/extHost.api.impl.ts | 6 + .../workbench/api/common/extHost.protocol.ts | 7 + .../api/common/extHostLanguageFeatures.ts | 96 ++++- src/vs/workbench/api/common/extHostTypes.ts | 16 + .../actions/common/menusExtensionPoint.ts | 7 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.inlineEdit.d.ts | 86 +++++ 27 files changed, 1755 insertions(+), 178 deletions(-) create mode 100644 src/vs/editor/contrib/inlineEdit/browser/commandIds.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/commands.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css create mode 100644 src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts create mode 100644 src/vscode-dts/vscode.proposed.inlineEdit.d.ts diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index c0225ebc0b8b8..d06e32cef0963 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -428,6 +428,7 @@ export interface IEditorOptions { */ suggest?: ISuggestOptions; inlineSuggest?: IInlineSuggestOptions; + experimentalInlineEdit?: IInlineEditOptions; /** * Smart select options. */ @@ -4072,6 +4073,90 @@ class InlineEditorSuggest extends BaseEditorOption>; + +class InlineEditorEdit extends BaseEditorOption { + constructor() { + const defaults: InternalInlineEditOptions = { + enabled: false, + showToolbar: 'onHover', + fontFamily: 'default', + keepOnBlur: false, + backgroundColoring: false, + }; + + super( + EditorOption.inlineEdit, 'experimentalInlineEdit', defaults, + { + 'editor.experimentalInlineEdit.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('inlineEdit.enabled', "Controls whether to show inline edits in the editor.") + }, + 'editor.experimentalInlineEdit.showToolbar': { + type: 'string', + default: defaults.showToolbar, + enum: ['always', 'onHover', 'never'], + enumDescriptions: [ + nls.localize('inlineEdit.showToolbar.always', "Show the inline edit toolbar whenever an inline suggestion is shown."), + nls.localize('inlineEdit.showToolbar.onHover', "Show the inline edit toolbar when hovering over an inline suggestion."), + nls.localize('inlineEdit.showToolbar.never', "Never show the inline edit toolbar."), + ], + description: nls.localize('inlineEdit.showToolbar', "Controls when to show the inline edit toolbar."), + }, + 'editor.experimentalInlineEdit.fontFamily': { + type: 'string', + default: defaults.fontFamily, + description: nls.localize('inlineEdit.fontFamily', "Controls the font family of the inline edit.") + }, + 'editor.experimentalInlineEdit.backgroundColoring': { + type: 'boolean', + default: defaults.backgroundColoring, + description: nls.localize('inlineEdit.backgroundColoring', "Controls whether to color the background of inline edits.") + }, + } + ); + } + + public validate(_input: any): InternalInlineEditOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IInlineEditOptions; + return { + enabled: boolean(input.enabled, this.defaultValue.enabled), + showToolbar: stringSet(input.showToolbar, this.defaultValue.showToolbar, ['always', 'onHover', 'never']), + fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily), + keepOnBlur: boolean(input.keepOnBlur, this.defaultValue.keepOnBlur), + backgroundColoring: boolean(input.backgroundColoring, this.defaultValue.backgroundColoring) + }; + } +} + //#region bracketPairColorization export interface IBracketPairColorizationOptions { @@ -5132,6 +5217,7 @@ export const enum EditorOption { hover, inDiffEditor, inlineSuggest, + inlineEdit, letterSpacing, lightbulb, lineDecorationsWidth, @@ -5835,6 +5921,7 @@ export const EditorOptions = { )), suggest: register(new EditorSuggest()), inlineSuggest: register(new InlineEditorSuggest()), + inlineEdit: register(new InlineEditorEdit()), inlineCompletionsAccessibilityVerbose: register(new EditorBooleanOption(EditorOption.inlineCompletionsAccessibilityVerbose, 'inlineCompletionsAccessibilityVerbose', false, { description: nls.localize('inlineCompletionsAccessibilityVerbose', "Controls whether the accessibility hint should be provided to screen reader users when an inline completion is shown.") })), suggestFontSize: register(new EditorIntOption( diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 252dabcbda609..16550bdef8d13 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -2143,3 +2143,24 @@ export interface MappedEditsProvider { token: CancellationToken ): Promise; } + +export interface IInlineEdit { + text: string; + range: IRange; + accepted?: Command; + rejected?: Command; +} + +export interface IInlineEditContext { + triggerKind: InlineEditTriggerKind; +} + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1, +} + +export interface InlineEditProvider { + provideInlineEdit(model: model.ITextModel, context: IInlineEditContext, token: CancellationToken): ProviderResult; + freeInlineEdit(edit: T): void; +} diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index 92f7e271f1ab1..72889bd0b7e22 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, MappedEditsProvider, MultiDocumentHighlightProvider, NewSymbolNamesProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, InlineEditProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -65,6 +65,8 @@ export interface ILanguageFeaturesService { readonly inlineCompletionsProvider: LanguageFeatureRegistry; + readonly inlineEditProvider: LanguageFeatureRegistry; + readonly completionProvider: LanguageFeatureRegistry; readonly linkedEditingRangeProvider: LanguageFeatureRegistry; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index 4e414b34b3699..920a78d7402f2 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, MultiDocumentHighlightProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, MappedEditsProvider, NewSymbolNamesProvider, InlineEditProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -36,6 +36,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly foldingRangeProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly linkProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly inlineCompletionsProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly inlineEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly completionProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly linkedEditingRangeProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly inlineValuesProvider = new LanguageFeatureRegistry(this._score.bind(this)); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index ed3b49ae7562a..26e9ad270950b 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -236,91 +236,92 @@ export enum EditorOption { hover = 60, inDiffEditor = 61, inlineSuggest = 62, - letterSpacing = 63, - lightbulb = 64, - lineDecorationsWidth = 65, - lineHeight = 66, - lineNumbers = 67, - lineNumbersMinChars = 68, - linkedEditing = 69, - links = 70, - matchBrackets = 71, - minimap = 72, - mouseStyle = 73, - mouseWheelScrollSensitivity = 74, - mouseWheelZoom = 75, - multiCursorMergeOverlapping = 76, - multiCursorModifier = 77, - multiCursorPaste = 78, - multiCursorLimit = 79, - occurrencesHighlight = 80, - overviewRulerBorder = 81, - overviewRulerLanes = 82, - padding = 83, - pasteAs = 84, - parameterHints = 85, - peekWidgetDefaultFocus = 86, - definitionLinkOpensInPeek = 87, - quickSuggestions = 88, - quickSuggestionsDelay = 89, - readOnly = 90, - readOnlyMessage = 91, - renameOnType = 92, - renderControlCharacters = 93, - renderFinalNewline = 94, - renderLineHighlight = 95, - renderLineHighlightOnlyWhenFocus = 96, - renderValidationDecorations = 97, - renderWhitespace = 98, - revealHorizontalRightPadding = 99, - roundedSelection = 100, - rulers = 101, - scrollbar = 102, - scrollBeyondLastColumn = 103, - scrollBeyondLastLine = 104, - scrollPredominantAxis = 105, - selectionClipboard = 106, - selectionHighlight = 107, - selectOnLineNumbers = 108, - showFoldingControls = 109, - showUnused = 110, - snippetSuggestions = 111, - smartSelect = 112, - smoothScrolling = 113, - stickyScroll = 114, - stickyTabStops = 115, - stopRenderingLineAfter = 116, - suggest = 117, - suggestFontSize = 118, - suggestLineHeight = 119, - suggestOnTriggerCharacters = 120, - suggestSelection = 121, - tabCompletion = 122, - tabIndex = 123, - unicodeHighlighting = 124, - unusualLineTerminators = 125, - useShadowDOM = 126, - useTabStops = 127, - wordBreak = 128, - wordSeparators = 129, - wordWrap = 130, - wordWrapBreakAfterCharacters = 131, - wordWrapBreakBeforeCharacters = 132, - wordWrapColumn = 133, - wordWrapOverride1 = 134, - wordWrapOverride2 = 135, - wrappingIndent = 136, - wrappingStrategy = 137, - showDeprecated = 138, - inlayHints = 139, - editorClassName = 140, - pixelRatio = 141, - tabFocusMode = 142, - layoutInfo = 143, - wrappingInfo = 144, - defaultColorDecorators = 145, - colorDecoratorsActivatedOn = 146, - inlineCompletionsAccessibilityVerbose = 147 + inlineEdit = 63, + letterSpacing = 64, + lightbulb = 65, + lineDecorationsWidth = 66, + lineHeight = 67, + lineNumbers = 68, + lineNumbersMinChars = 69, + linkedEditing = 70, + links = 71, + matchBrackets = 72, + minimap = 73, + mouseStyle = 74, + mouseWheelScrollSensitivity = 75, + mouseWheelZoom = 76, + multiCursorMergeOverlapping = 77, + multiCursorModifier = 78, + multiCursorPaste = 79, + multiCursorLimit = 80, + occurrencesHighlight = 81, + overviewRulerBorder = 82, + overviewRulerLanes = 83, + padding = 84, + pasteAs = 85, + parameterHints = 86, + peekWidgetDefaultFocus = 87, + definitionLinkOpensInPeek = 88, + quickSuggestions = 89, + quickSuggestionsDelay = 90, + readOnly = 91, + readOnlyMessage = 92, + renameOnType = 93, + renderControlCharacters = 94, + renderFinalNewline = 95, + renderLineHighlight = 96, + renderLineHighlightOnlyWhenFocus = 97, + renderValidationDecorations = 98, + renderWhitespace = 99, + revealHorizontalRightPadding = 100, + roundedSelection = 101, + rulers = 102, + scrollbar = 103, + scrollBeyondLastColumn = 104, + scrollBeyondLastLine = 105, + scrollPredominantAxis = 106, + selectionClipboard = 107, + selectionHighlight = 108, + selectOnLineNumbers = 109, + showFoldingControls = 110, + showUnused = 111, + snippetSuggestions = 112, + smartSelect = 113, + smoothScrolling = 114, + stickyScroll = 115, + stickyTabStops = 116, + stopRenderingLineAfter = 117, + suggest = 118, + suggestFontSize = 119, + suggestLineHeight = 120, + suggestOnTriggerCharacters = 121, + suggestSelection = 122, + tabCompletion = 123, + tabIndex = 124, + unicodeHighlighting = 125, + unusualLineTerminators = 126, + useShadowDOM = 127, + useTabStops = 128, + wordBreak = 129, + wordSeparators = 130, + wordWrap = 131, + wordWrapBreakAfterCharacters = 132, + wordWrapBreakBeforeCharacters = 133, + wordWrapColumn = 134, + wordWrapOverride1 = 135, + wordWrapOverride2 = 136, + wrappingIndent = 137, + wrappingStrategy = 138, + showDeprecated = 139, + inlayHints = 140, + editorClassName = 141, + pixelRatio = 142, + tabFocusMode = 143, + layoutInfo = 144, + wrappingInfo = 145, + defaultColorDecorators = 146, + colorDecoratorsActivatedOn = 147, + inlineCompletionsAccessibilityVerbose = 148 } /** @@ -415,6 +416,11 @@ export enum InlineCompletionTriggerKind { */ Explicit = 1 } + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1 +} /** * Virtual Key Codes, the value does not hold any inherent meaning. * Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts index 0cfd4f3cb4430..0c93d8465ab94 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts @@ -184,7 +184,7 @@ export class GhostTextWidget extends Disposable { } } -class AdditionalLinesWidget extends Disposable { +export class AdditionalLinesWidget extends Disposable { private _viewZoneId: string | undefined = undefined; public get viewZoneId(): string | undefined { return this._viewZoneId; } @@ -263,7 +263,7 @@ class AdditionalLinesWidget extends Disposable { } } -interface LineData { +export interface LineData { content: string; // Must not contain a linebreak! decorations: LineDecoration[]; } @@ -325,4 +325,4 @@ function renderLines(domNode: HTMLElement, tabSize: number, lines: LineData[], o domNode.innerHTML = trustedhtml as string; } -const ttPolicy = createTrustedTypesPolicy('editorGhostText', { createHTML: value => value }); +export const ttPolicy = createTrustedTypesPolicy('editorGhostText', { createHTML: value => value }); diff --git a/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts b/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts new file mode 100644 index 0000000000000..ccd1b40df5a7d --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/commandIds.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const inlineEditAcceptId = 'editor.action.inlineEdit.accept'; +export const inlineEditTriggerId = 'editor.action.inlineEdit.trigger'; +export const inlineEditRejectId = 'editor.action.inlineEdit.reject'; +export const inlineEditJumpToId = 'editor.action.inlineEdit.jumpTo'; +export const inlineEditJumpBackId = 'editor.action.inlineEdit.jumpBack'; diff --git a/src/vs/editor/contrib/inlineEdit/browser/commands.ts b/src/vs/editor/contrib/inlineEdit/browser/commands.ts new file mode 100644 index 0000000000000..93f1f9d4a2cc4 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/commands.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { inlineEditAcceptId, inlineEditJumpBackId, inlineEditJumpToId, inlineEditRejectId } from 'vs/editor/contrib/inlineEdit/browser/commandIds'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; + +export class AcceptInlineEdit extends EditorAction { + constructor() { + super({ + id: inlineEditAcceptId, + label: 'Accept Inline Edit', + alias: 'Accept Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: [ + { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyCode.Tab, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext, InlineEditController.cursorAtInlineEditContext) + }], + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Accept', + group: 'primary', + order: 1, + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.accept(); + } +} + +export class TriggerInlineEdit extends EditorAction { + constructor() { + super({ + id: 'editor.action.inlineEdit.trigger', + label: 'Trigger Inline Edit', + alias: 'Trigger Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)) + }, + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.trigger(); + } +} + +export class JumpToInlineEdit extends EditorAction { + constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext, ContextKeyExpr.not(InlineEditController.cursorAtInlineEditKey)); + + super({ + id: inlineEditJumpToId, + label: 'Jump to Inline Edit', + alias: 'Jump to Inline Edit', + precondition: activeExpr, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 1, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: activeExpr + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Jump To Edit', + group: 'primary', + order: 3, + when: activeExpr + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.jumpToCurrent(); + } +} + +export class JumpBackInlineEdit extends EditorAction { + constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.cursorAtInlineEditContext); + + super({ + id: inlineEditJumpBackId, + label: 'Jump Back from Inline Edit', + alias: 'Jump Back from Inline Edit', + precondition: activeExpr, + kbOpts: { + weight: KeybindingWeight.EditorContrib + 10, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, + kbExpr: activeExpr + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Jump Back', + group: 'primary', + order: 3, + when: activeExpr + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.jumpBack(); + } +} + +export class RejectInlineEdit extends EditorAction { + constructor() { + super({ + id: inlineEditRejectId, + label: 'Reject Inline Edit', + alias: 'Reject Inline Edit', + precondition: EditorContextKeys.writable, + kbOpts: { + weight: KeybindingWeight.EditorContrib, + primary: KeyCode.Escape, + kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext) + }, + menuOpts: [{ + menuId: MenuId.InlineEditToolbar, + title: 'Reject', + group: 'secondary', + order: 2, + }], + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = InlineEditController.get(editor); + controller?.clear(); + } +} + diff --git a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts new file mode 100644 index 0000000000000..da0fd80b5436b --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.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 { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import 'vs/css!./inlineEdit'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; +import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; +import { InlineDecorationType } from 'vs/editor/common/viewModel'; +import { AdditionalLinesWidget, LineData } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; +import { GhostText } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { ColumnRange, applyObservableDecorations } from 'vs/editor/contrib/inlineCompletions/browser/utils'; + +export const INLINE_EDIT_DESCRIPTION = 'inline-edit'; +export interface IGhostTextWidgetModel { + readonly targetTextModel: IObservable; + readonly ghostText: IObservable; + readonly minReservedLineCount: IObservable; + readonly range: IObservable; + readonly backgroundColoring: IObservable; +} + +export class GhostTextWidget extends Disposable { + private readonly isDisposed = observableValue(this, false); + private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); + + constructor( + private readonly editor: ICodeEditor, + readonly model: IGhostTextWidgetModel, + @ILanguageService private readonly languageService: ILanguageService, + ) { + super(); + + this._register(toDisposable(() => { this.isDisposed.set(true, undefined); })); + this._register(applyObservableDecorations(this.editor, this.decorations)); + } + + private readonly uiState = derived(this, reader => { + if (this.isDisposed.read(reader)) { + return undefined; + } + const textModel = this.currentTextModel.read(reader); + if (textModel !== this.model.targetTextModel.read(reader)) { + return undefined; + } + const ghostText = this.model.ghostText.read(reader); + if (!ghostText) { + return undefined; + } + + + let range = this.model.range?.read(reader); + //if range is empty, we want to remove it + if (range && range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { + range = undefined; + } + //check if both range and text are single line - in this case we want to do inline replacement + //rather than replacing whole lines + const isSingleLine = (range ? range.startLineNumber === range.endLineNumber : true) && ghostText.parts.length === 1 && ghostText.parts[0].lines.length === 1; + + //check if we're just removing code + const isPureRemove = ghostText.parts.length === 1 && ghostText.parts[0].lines.every(l => l.length === 0); + + const inlineTexts: { column: number; text: string; preview: boolean }[] = []; + const additionalLines: LineData[] = []; + + function addToAdditionalLines(lines: readonly string[], className: string | undefined) { + if (additionalLines.length > 0) { + const lastLine = additionalLines[additionalLines.length - 1]; + if (className) { + lastLine.decorations.push(new LineDecoration(lastLine.content.length + 1, lastLine.content.length + 1 + lines[0].length, className, InlineDecorationType.Regular)); + } + lastLine.content += lines[0]; + + lines = lines.slice(1); + } + for (const line of lines) { + additionalLines.push({ + content: line, + decorations: className ? [new LineDecoration(1, line.length + 1, className, InlineDecorationType.Regular)] : [] + }); + } + } + + const textBufferLine = textModel.getLineContent(ghostText.lineNumber); + + let hiddenTextStartColumn: number | undefined = undefined; + let lastIdx = 0; + if (!isPureRemove) { + for (const part of ghostText.parts) { + let lines = part.lines; + //If remove range is set, we want to push all new liens to virtual area + if (range && !isSingleLine) { + addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); + lines = []; + } + if (hiddenTextStartColumn === undefined) { + inlineTexts.push({ + column: part.column, + text: lines[0], + preview: part.preview, + }); + lines = lines.slice(1); + } else { + addToAdditionalLines([textBufferLine.substring(lastIdx, part.column - 1)], undefined); + } + + if (lines.length > 0) { + addToAdditionalLines(lines, INLINE_EDIT_DESCRIPTION); + if (hiddenTextStartColumn === undefined && part.column <= textBufferLine.length) { + hiddenTextStartColumn = part.column; + } + } + + lastIdx = part.column - 1; + } + if (hiddenTextStartColumn !== undefined) { + addToAdditionalLines([textBufferLine.substring(lastIdx)], undefined); + } + } + + const hiddenRange = hiddenTextStartColumn !== undefined ? new ColumnRange(hiddenTextStartColumn, textBufferLine.length + 1) : undefined; + + const lineNumber = + (isSingleLine || !range) ? ghostText.lineNumber : range.endLineNumber - 1; + + return { + inlineTexts, + additionalLines, + hiddenRange, + lineNumber, + additionalReservedLineCount: this.model.minReservedLineCount.read(reader), + targetTextModel: textModel, + range, + isSingleLine, + isPureRemove, + backgroundColoring: this.model.backgroundColoring.read(reader) + }; + }); + + private readonly decorations = derived(this, reader => { + const uiState = this.uiState.read(reader); + if (!uiState) { + return []; + } + + const decorations: IModelDeltaDecoration[] = []; + + if (uiState.hiddenRange) { + decorations.push({ + range: uiState.hiddenRange.toRange(uiState.lineNumber), + options: { inlineClassName: 'inline-edit-hidden', description: 'inline-edit-hidden', } + }); + } + + if (uiState.range) { + const ranges = []; + if (uiState.isSingleLine) { + ranges.push(uiState.range); + } + else if (uiState.isPureRemove) { + const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; + for (let i = 0; i < lines; i++) { + const line = uiState.range.startLineNumber + i; + const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); + const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); + const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); + ranges.push(range); + } + } + else { + const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; + for (let i = 0; i <= lines; i++) { + const line = uiState.range.startLineNumber + i; + const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); + const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); + const range = new Range(line, firstNonWhitespace, line, lastNonWhitespace); + ranges.push(range); + } + } + const className = uiState.backgroundColoring ? 'inline-edit-remove backgroundColoring' : 'inline-edit-remove'; + for (const range of ranges) { + decorations.push({ + range, + options: { inlineClassName: className, description: 'inline-edit-remove', } + }); + } + } + + for (const p of uiState.inlineTexts) { + + decorations.push({ + range: Range.fromPositions(new Position(uiState.lineNumber, p.column)), + options: { + description: INLINE_EDIT_DESCRIPTION, + after: { content: p.text, inlineClassName: p.preview ? 'inline-edit-decoration-preview' : 'inline-edit-decoration', cursorStops: InjectedTextCursorStops.Left }, + showIfCollapsed: true, + } + }); + } + + return decorations; + }); + + private readonly additionalLinesWidget = this._register( + new AdditionalLinesWidget( + this.editor, + this.languageService.languageIdCodec, + derived(reader => { + /** @description lines */ + const uiState = this.uiState.read(reader); + return uiState && !uiState.isPureRemove ? { + lineNumber: uiState.lineNumber, + additionalLines: uiState.additionalLines, + minReservedLineCount: uiState.additionalReservedLineCount, + targetTextModel: uiState.targetTextModel, + } : undefined; + }) + ) + ); + + public ownsViewZone(viewZoneId: string): boolean { + return this.additionalLinesWidget.viewZoneId === viewZoneId; + } +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts b/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts new file mode 100644 index 0000000000000..6c1c7337f7fc5 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/hoverParticipant.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { constObservable } from 'vs/base/common/observable'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { IModelDecoration } from 'vs/editor/common/model'; +import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { InlineEditHintsContentWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; + +export class InlineEditHover implements IHoverPart { + constructor( + public readonly owner: IEditorHoverParticipant, + public readonly range: Range, + public readonly controller: InlineEditController + ) { } + + public isValidForHoverAnchor(anchor: HoverAnchor): boolean { + return ( + anchor.type === HoverAnchorType.Range + && this.range.startColumn <= anchor.range.startColumn + && this.range.endColumn >= anchor.range.endColumn + ); + } +} + +export class InlineEditHoverParticipant implements IEditorHoverParticipant { + + public readonly hoverOrdinal: number = 5; + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + ) { + } + + suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null { + const controller = InlineEditController.get(this._editor); + if (!controller) { + return null; + } + + const target = mouseEvent.target; + if (target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + // handle the case where the mouse is over the view zone + const viewZoneData = target.detail; + if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) { + // const range = Range.fromPositions(this._editor.getModel()!.validatePosition(viewZoneData.positionBefore || viewZoneData.position)); + const range = target.range; + return new HoverForeignElementAnchor(1000, this, range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + if (target.type === MouseTargetType.CONTENT_EMPTY) { + // handle the case where the mouse is over the empty portion of a line following ghost text + if (controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + if (target.type === MouseTargetType.CONTENT_TEXT) { + // handle the case where the mouse is directly over ghost text + const mightBeForeignElement = target.detail.mightBeForeignElement; + if (mightBeForeignElement && controller.shouldShowHoverAt(target.range)) { + return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false); + } + } + return null; + } + + computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): InlineEditHover[] { + if (this._editor.getOption(EditorOption.inlineEdit).showToolbar !== 'onHover') { + return []; + } + + const controller = InlineEditController.get(this._editor); + if (controller && controller.shouldShowHoverAt(anchor.range)) { + return [new InlineEditHover(this, anchor.range, controller)]; + } + return []; + } + + renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineEditHover[]): IDisposable { + const disposableStore = new DisposableStore(); + + this._telemetryService.publicLog2<{}, { + owner: 'hediet'; + comment: 'This event tracks whenever an inline edit hover is shown.'; + }>('inlineEditHover.shown'); + + const w = this._instantiationService.createInstance(InlineEditHintsContentWidget, this._editor, false, + constObservable(null), + ); + context.fragment.appendChild(w.getDomNode()); + disposableStore.add(w); + + return disposableStore; + } +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts new file mode 100644 index 0000000000000..7196773a7cfc2 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; +import { AcceptInlineEdit, JumpBackInlineEdit, JumpToInlineEdit, RejectInlineEdit, TriggerInlineEdit } from 'vs/editor/contrib/inlineEdit/browser/commands'; +import { InlineEditHoverParticipant } from 'vs/editor/contrib/inlineEdit/browser/hoverParticipant'; +import { InlineEditController } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; + +registerEditorAction(AcceptInlineEdit); +registerEditorAction(RejectInlineEdit); +registerEditorAction(JumpToInlineEdit); +registerEditorAction(JumpBackInlineEdit); +registerEditorAction(TriggerInlineEdit); +registerEditorContribution(InlineEditController.ID, InlineEditController, EditorContributionInstantiation.Eventually); + + +HoverParticipantRegistry.register(InlineEditHoverParticipant); diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css new file mode 100644 index 0000000000000..d6d156544e00e --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEdit.css @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inline-edit-remove { + background-color: var(--vscode-editorGhostText-background); + font-style: italic; + text-decoration: line-through; +} + +.monaco-editor .inline-edit-remove.backgroundColoring { + background-color: var(--vscode-diffEditor-removedLineBackground); +} + +.monaco-editor .inline-edit-hidden { + opacity: 0; + font-size: 0; +} + +.monaco-editor .inline-edit-decoration, .monaco-editor .suggest-preview-text .inline-edit { + font-style: italic; +} + +.monaco-editor .inline-completion-text-to-replace { + text-decoration: underline; + text-underline-position: under; +} + +.monaco-editor .inline-edit-decoration, +.monaco-editor .inline-edit-decoration-preview, +.monaco-editor .suggest-preview-text .inline-edit { + color: var(--vscode-editorGhostText-foreground) !important; + background-color: var(--vscode-editorGhostText-background); + border: 1px solid var(--vscode-editorGhostText-border); +} + + diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts new file mode 100644 index 0000000000000..2b5db89b91377 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -0,0 +1,344 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable, autorun, constObservable, disposableObservableValue, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { GhostTextWidget } from 'vs/editor/contrib/inlineEdit/browser/ghostTextWidget'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInlineEdit, InlineEditTriggerKind } from 'vs/editor/common/languages'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { InlineEditHintsWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createStyleSheet2 } from 'vs/base/browser/dom'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class InlineEditWidget implements IDisposable { + constructor(public readonly widget: GhostTextWidget, public readonly edit: IInlineEdit) { } + + dispose(): void { + this.widget.dispose(); + } +} + +export class InlineEditController extends Disposable { + static ID = 'editor.contrib.inlineEditController'; + + public static readonly inlineEditVisibleKey = 'inlineEditVisible'; + public static readonly inlineEditVisibleContext = new RawContextKey(InlineEditController.inlineEditVisibleKey, false); + private _isVisibleContext = InlineEditController.inlineEditVisibleContext.bindTo(this.contextKeyService); + + public static readonly cursorAtInlineEditKey = 'cursorAtInlineEdit'; + public static readonly cursorAtInlineEditContext = new RawContextKey(InlineEditController.cursorAtInlineEditKey, false); + private _isCursorAtInlineEditContext = InlineEditController.cursorAtInlineEditContext.bindTo(this.contextKeyService); + + public static get(editor: ICodeEditor): InlineEditController | null { + return editor.getContribution(InlineEditController.ID); + } + + private _currentEdit: ISettableObservable = this._register(disposableObservableValue(this, undefined)); + private _currentRequestCts: CancellationTokenSource | undefined; + + private _jumpBackPosition: Position | undefined; + private _isAccepting: boolean = false; + + private readonly _enabled = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).enabled); + private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).fontFamily); + private readonly _backgroundColoring = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).backgroundColoring); + + + constructor( + public readonly editor: ICodeEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @ICommandService private readonly _commandService: ICommandService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + ) { + super(); + + //Automatically request inline edit when the content was changed + //Cancel the previous request if there is one + //Remove the previous ghost text + const modelChangedSignal = observableSignalFromEvent('InlineEditController.modelContentChangedSignal', editor.onDidChangeModelContent); + this._register(autorun(reader => { + /** @description InlineEditController.modelContentChanged model */ + if (!this._enabled.read(reader)) { + return; + } + modelChangedSignal.read(reader); + this.getInlineEdit(editor, true); + })); + + //Check if the cursor is at the ghost text + const cursorPosition = observableFromEvent(editor.onDidChangeCursorPosition, () => editor.getPosition()); + this._register(autorun(reader => { + /** @description InlineEditController.cursorPositionChanged model */ + if (!this._enabled.read(reader)) { + return; + } + + const pos = cursorPosition.read(reader); + if (pos) { + this.checkCursorPosition(pos); + } + })); + + //Perform stuff when the current edit has changed + this._register(autorun((reader) => { + /** @description InlineEditController.update model */ + const currentEdit = this._currentEdit.read(reader); + this._isCursorAtInlineEditContext.set(false); + if (!currentEdit) { + this._isVisibleContext.set(false); + return; + } + this._isVisibleContext.set(true); + const pos = editor.getPosition(); + if (pos) { + this.checkCursorPosition(pos); + } + })); + + //Clear suggestions on lost focus + this._register(editor.onDidBlurEditorWidget(() => { + // This is a hidden setting very useful for debugging + if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) { + return; + } + this._currentRequestCts?.dispose(); + this._currentRequestCts = undefined; + this.clear(); + })); + + //Invoke provider on focus + this._register(editor.onDidFocusEditorText(async () => { + if (!this._enabled.get()) { + return; + } + await this.getInlineEdit(editor, true); + })); + + + //handle changes of font setting + const styleElement = this._register(createStyleSheet2()); + this._register(autorun(reader => { + const fontFamily = this._fontFamily.read(reader); + styleElement.setStyle(fontFamily === '' || fontFamily === 'default' ? `` : ` +.monaco-editor .inline-edit-decoration, +.monaco-editor .inline-edit-decoration-preview, +.monaco-editor .inline-edit { + font-family: ${fontFamily}; +}`); + })); + + this._register(new InlineEditHintsWidget(this.editor, this._currentEdit, this.instantiationService)); + } + + private checkCursorPosition(position: Position) { + if (!this._currentEdit) { + this._isCursorAtInlineEditContext.set(false); + return; + } + const gt = this._currentEdit.get()?.edit; + if (!gt) { + this._isCursorAtInlineEditContext.set(false); + return; + } + this._isCursorAtInlineEditContext.set(Range.containsPosition(gt.range, position)); + } + + private validateInlineEdit(editor: ICodeEditor, edit: IInlineEdit): boolean { + //Multiline inline replacing edit must replace whole lines + if (edit.text.includes('\n') && edit.range.startLineNumber !== edit.range.endLineNumber && edit.range.startColumn !== edit.range.endColumn) { + const firstColumn = edit.range.startColumn; + if (firstColumn !== 1) { + return false; + } + const lastLine = edit.range.endLineNumber; + const lastColumn = edit.range.endColumn; + const lineLength = editor.getModel()?.getLineLength(lastLine) ?? 0; + if (lastColumn !== lineLength + 1) { + return false; + } + } + return true; + } + + private async fetchInlineEdit(editor: ICodeEditor, auto: boolean): Promise { + if (this._currentRequestCts) { + this._currentRequestCts.dispose(true); + } + const model = editor.getModel(); + if (!model) { + return; + } + const modelVersion = model.getVersionId(); + const providers = this.languageFeaturesService.inlineEditProvider.all(model); + if (providers.length === 0) { + return; + } + const provider = providers[0]; + this._currentRequestCts = new CancellationTokenSource(); + const token = this._currentRequestCts.token; + const triggerKind = auto ? InlineEditTriggerKind.Automatic : InlineEditTriggerKind.Invoke; + const shouldDebounce = auto; + if (shouldDebounce) { + await wait(50, token); + } + if (token.isCancellationRequested || model.isDisposed() || model.getVersionId() !== modelVersion) { + return; + } + const edit = await provider.provideInlineEdit(model, { triggerKind }, token); + if (!edit) { + return; + } + if (token.isCancellationRequested || model.isDisposed() || model.getVersionId() !== modelVersion) { + return; + } + if (!this.validateInlineEdit(editor, edit)) { + return; + } + return edit; + } + + private async getInlineEdit(editor: ICodeEditor, auto: boolean) { + this._isCursorAtInlineEditContext.set(false); + this.clear(); + this._isAccepting = false; + const edit = await this.fetchInlineEdit(editor, auto); + if (!edit) { + return; + } + const line = edit.range.endLineNumber; + const column = edit.range.endColumn; + const ghostText = new GhostText(line, [new GhostTextPart(column, edit.text, false)]); + const instance = this.instantiationService.createInstance(GhostTextWidget, this.editor, { + ghostText: constObservable(ghostText), + minReservedLineCount: constObservable(0), + targetTextModel: constObservable(this.editor.getModel() ?? undefined), + range: constObservable(edit.range), + backgroundColoring: this._backgroundColoring + }); + this._currentEdit.set(new InlineEditWidget(instance, edit), undefined); + } + + public async trigger() { + await this.getInlineEdit(this.editor, false); + } + + public async jumpBack() { + if (!this._jumpBackPosition) { + return; + } + this.editor.setPosition(this._jumpBackPosition); + //if position is outside viewports, scroll to it + this.editor.revealPositionInCenterIfOutsideViewport(this._jumpBackPosition); + } + + public accept(): void { + this._isAccepting = true; + const data = this._currentEdit.get()?.edit; + if (!data) { + return; + } + + //It should only happen in case of last line suggestion + let text = data.text; + if (data.text.startsWith('\n')) { + text = data.text.substring(1); + } + this.editor.pushUndoStop(); + this.editor.executeEdits('acceptCurrent', [EditOperation.replace(Range.lift(data.range), text)]); + if (data.accepted) { + this._commandService.executeCommand(data.accepted.id, ...data.accepted.arguments || []); + } + this.freeEdit(data); + this._currentEdit.set(undefined, undefined); + } + + public jumpToCurrent(): void { + this._jumpBackPosition = this.editor.getSelection()?.getStartPosition(); + + const data = this._currentEdit.get()?.edit; + if (!data) { + return; + } + const position = Position.lift({ lineNumber: data.range.startLineNumber, column: data.range.startColumn }); + this.editor.setPosition(position); + //if position is outside viewports, scroll to it + this.editor.revealPositionInCenterIfOutsideViewport(position); + } + + public clear() { + const edit = this._currentEdit.get()?.edit; + if (edit && edit?.rejected && !this._isAccepting) { + this._commandService.executeCommand(edit.rejected.id, ...edit.rejected.arguments || []); + } + if (edit) { + this.freeEdit(edit); + } + this._currentEdit.set(undefined, undefined); + } + + private freeEdit(edit: IInlineEdit) { + const model = this.editor.getModel(); + if (!model) { + return; + } + const providers = this.languageFeaturesService.inlineEditProvider.all(model); + if (providers.length === 0) { + return; + } + providers[0].freeInlineEdit(edit); + } + + public shouldShowHoverAt(range: Range) { + const currentEdit = this._currentEdit.get(); + if (!currentEdit) { + return false; + } + const edit = currentEdit.edit; + const model = currentEdit.widget.model; + const overReplaceRange = Range.containsPosition(edit.range, range.getStartPosition()) || Range.containsPosition(edit.range, range.getEndPosition()); + if (overReplaceRange) { + return true; + } + const ghostText = model.ghostText.get(); + if (ghostText) { + return ghostText.parts.some(p => range.containsPosition(new Position(ghostText.lineNumber, p.column))); + } + return false; + } + + public shouldShowHoverAtViewZone(viewZoneId: string): boolean { + return this._currentEdit.get()?.widget.ownsViewZone(viewZoneId) ?? false; + } + +} + +function wait(ms: number, cancellationToken?: CancellationToken): Promise { + return new Promise(resolve => { + let d: IDisposable | undefined = undefined; + const handle = setTimeout(() => { + if (d) { d.dispose(); } + resolve(); + }, ms); + if (cancellationToken) { + d = cancellationToken.onCancellationRequested(() => { + clearTimeout(handle); + if (d) { d.dispose(); } + resolve(); + }); + } + }); +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css new file mode 100644 index 0000000000000..8f369a27c3da0 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.css @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .inlineEditHints.withBorder { + z-index: 39; + color: var(--vscode-editorHoverWidget-foreground); + background-color: var(--vscode-editorHoverWidget-background); + border: 1px solid var(--vscode-editorHoverWidget-border); +} + +.monaco-editor .inlineEditHints a { + color: var(--vscode-foreground); +} + +.monaco-editor .inlineEditHints a:hover { + color: var(--vscode-foreground); +} + +.monaco-editor .inlineEditHints .keybinding { + display: flex; + margin-left: 4px; + opacity: 0.6; +} + +.monaco-editor .inlineEditHints .keybinding .monaco-keybinding-key { + font-size: 8px; + padding: 2px 3px; +} + +.monaco-editor .inlineEditStatusBarItemLabel { + margin-right: 2px; +} diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts new file mode 100644 index 0000000000000..73824bd5e6cb7 --- /dev/null +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditHintsWidget.ts @@ -0,0 +1,246 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { h } from 'vs/base/browser/dom'; +import { KeybindingLabel, unthemedKeybindingLabelOptions } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { IAction, Separator } from 'vs/base/common/actions'; +import { equals } from 'vs/base/common/arrays'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, autorun, autorunWithStore, derived, observableFromEvent } from 'vs/base/common/observable'; +import { OS } from 'vs/base/common/platform'; +import 'vs/css!./inlineEditHintsWidget'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { PositionAffinity } from 'vs/editor/common/model'; +import { InlineEditWidget } from 'vs/editor/contrib/inlineEdit/browser/inlineEditController'; +import { MenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +export class InlineEditHintsWidget extends Disposable { + private readonly alwaysShowToolbar = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineEdit).showToolbar === 'always'); + + private sessionPosition: Position | undefined = undefined; + + private readonly position = derived(this, reader => { + const ghostText = this.model.read(reader)?.widget.model.ghostText.read(reader); + + if (!this.alwaysShowToolbar.read(reader) || !ghostText || ghostText.parts.length === 0) { + this.sessionPosition = undefined; + return null; + } + + const firstColumn = ghostText.parts[0].column; + if (this.sessionPosition && this.sessionPosition.lineNumber !== ghostText.lineNumber) { + this.sessionPosition = undefined; + } + + const position = new Position(ghostText.lineNumber, Math.min(firstColumn, this.sessionPosition?.column ?? Number.MAX_SAFE_INTEGER)); + this.sessionPosition = position; + return position; + }); + + constructor( + private readonly editor: ICodeEditor, + private readonly model: IObservable, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + + this._register(autorunWithStore((reader, store) => { + /** @description setup content widget */ + const model = this.model.read(reader); + if (!model || !this.alwaysShowToolbar.read(reader)) { + return; + } + + const contentWidget = store.add(this.instantiationService.createInstance( + InlineEditHintsContentWidget, + this.editor, + true, + this.position, + )); + editor.addContentWidget(contentWidget); + store.add(toDisposable(() => editor.removeContentWidget(contentWidget))); + })); + } +} + +export class InlineEditHintsContentWidget extends Disposable implements IContentWidget { + private static _dropDownVisible = false; + public static get dropDownVisible() { return this._dropDownVisible; } + + private static id = 0; + + private readonly id = `InlineEditHintsContentWidget${InlineEditHintsContentWidget.id++}`; + public readonly allowEditorOverflow = true; + public readonly suppressMouseDown = false; + + private readonly nodes = h('div.inlineEditHints', { className: this.withBorder ? '.withBorder' : '' }, [ + h('div@toolBar'), + ]); + + private readonly toolBar: CustomizedMenuWorkbenchToolBar; + + private readonly inlineCompletionsActionsMenus = this._register(this._menuService.createMenu( + MenuId.InlineEditActions, + this._contextKeyService + )); + + constructor( + private readonly editor: ICodeEditor, + private readonly withBorder: boolean, + private readonly _position: IObservable, + + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IMenuService private readonly _menuService: IMenuService, + ) { + super(); + + this.toolBar = this._register(instantiationService.createInstance(CustomizedMenuWorkbenchToolBar, this.nodes.toolBar, this.editor, MenuId.InlineEditToolbar, { + menuOptions: { renderShortTitle: true }, + toolbarOptions: { primaryGroup: g => g.startsWith('primary') }, + actionViewItemProvider: (action, options) => { + if (action instanceof MenuItemAction) { + return instantiationService.createInstance(StatusBarViewItem, action, undefined); + } + return undefined; + }, + telemetrySource: 'InlineEditToolbar', + })); + + this._register(this.toolBar.onDidChangeDropdownVisibility(e => { + InlineEditHintsContentWidget._dropDownVisible = e; + })); + + this._register(autorun(reader => { + /** @description update position */ + this._position.read(reader); + this.editor.layoutContentWidget(this); + })); + + this._register(autorun(reader => { + /** @description actions menu */ + + const extraActions = []; + + for (const [_, group] of this.inlineCompletionsActionsMenus.getActions()) { + for (const action of group) { + if (action instanceof MenuItemAction) { + extraActions.push(action); + } + } + } + + if (extraActions.length > 0) { + extraActions.unshift(new Separator()); + } + + this.toolBar.setAdditionalSecondaryActions(extraActions); + })); + + + } + + getId(): string { return this.id; } + + getDomNode(): HTMLElement { + return this.nodes.root; + } + + getPosition(): IContentWidgetPosition | null { + return { + position: this._position.get(), + preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW], + positionAffinity: PositionAffinity.LeftOfInjectedText, + }; + } +} + +class StatusBarViewItem extends MenuEntryActionViewItem { + protected override updateLabel() { + const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService); + if (!kb) { + return super.updateLabel(); + } + if (this.label) { + const div = h('div.keybinding').root; + + const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }); + k.set(kb); + this.label.textContent = this._action.label; + this.label.appendChild(div); + this.label.classList.add('inlineEditStatusBarItemLabel'); + } + } + + protected override updateTooltip(): void { + // NOOP, disable tooltip + } +} + +export class CustomizedMenuWorkbenchToolBar extends WorkbenchToolBar { + private readonly menu = this._store.add(this.menuService.createMenu(this.menuId, this.contextKeyService, { emitEventsForSubmenuChanges: true })); + private additionalActions: IAction[] = []; + private prependedPrimaryActions: IAction[] = []; + + constructor( + container: HTMLElement, + private readonly editor: ICodeEditor, + private readonly menuId: MenuId, + private readonly options2: IMenuWorkbenchToolBarOptions | undefined, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + super(container, { resetMenu: menuId, ...options2 }, menuService, contextKeyService, contextMenuService, keybindingService, telemetryService); + + this._store.add(this.menu.onDidChange(() => this.updateToolbar())); + this._store.add(this.editor.onDidChangeCursorPosition(() => this.updateToolbar())); + this.updateToolbar(); + } + + private updateToolbar(): void { + const primary: IAction[] = []; + const secondary: IAction[] = []; + createAndFillInActionBarActions( + this.menu, + this.options2?.menuOptions, + { primary, secondary }, + this.options2?.toolbarOptions?.primaryGroup, this.options2?.toolbarOptions?.shouldInlineSubmenu, this.options2?.toolbarOptions?.useSeparatorsInPrimaryActions + ); + + secondary.push(...this.additionalActions); + primary.unshift(...this.prependedPrimaryActions); + this.setActions(primary, secondary); + } + + setPrependedPrimaryActions(actions: IAction[]): void { + if (equals(this.prependedPrimaryActions, actions, (a, b) => a === b)) { + return; + } + + this.prependedPrimaryActions = actions; + this.updateToolbar(); + } + + setAdditionalSecondaryActions(actions: IAction[]): void { + if (equals(this.additionalActions, actions, (a, b) => a === b)) { + return; + } + + this.additionalActions = actions; + this.updateToolbar(); + } +} diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index cedb454f59531..227595d6224d8 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -41,6 +41,7 @@ import 'vs/editor/contrib/linkedEditing/browser/linkedEditing'; import 'vs/editor/contrib/links/browser/links'; import 'vs/editor/contrib/longLinesHelper/browser/longLinesHelper'; import 'vs/editor/contrib/multicursor/browser/multicursor'; +import 'vs/editor/contrib/inlineEdit/browser/inlineEdit.contribution'; import 'vs/editor/contrib/parameterHints/browser/parameterHints'; import 'vs/editor/contrib/rename/browser/rename'; import 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 5323333e6408a..8466fbf9f99c9 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -679,6 +679,11 @@ export function registerInlineCompletionsProvider(languageSelector: LanguageSele return languageFeaturesService.inlineCompletionsProvider.register(languageSelector, provider); } +export function registerInlineEditProvider(languageSelector: LanguageSelector, provider: languages.InlineEditProvider): IDisposable { + const languageFeaturesService = StandaloneServices.get(ILanguageFeaturesService); + return languageFeaturesService.inlineEditProvider.register(languageSelector, provider); +} + /** * Register an inlay hints provider. */ @@ -786,6 +791,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerDocumentSemanticTokensProvider: registerDocumentSemanticTokensProvider, registerDocumentRangeSemanticTokensProvider: registerDocumentRangeSemanticTokensProvider, registerInlineCompletionsProvider: registerInlineCompletionsProvider, + registerInlineEditProvider: registerInlineEditProvider, registerInlayHintsProvider: registerInlayHintsProvider, // enums @@ -800,6 +806,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { SignatureHelpTriggerKind: standaloneEnums.SignatureHelpTriggerKind, InlayHintKind: standaloneEnums.InlayHintKind, InlineCompletionTriggerKind: standaloneEnums.InlineCompletionTriggerKind, + InlineEditTriggerKind: standaloneEnums.InlineEditTriggerKind, CodeActionTriggerType: standaloneEnums.CodeActionTriggerType, NewSymbolNameTag: standaloneEnums.NewSymbolNameTag, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b1bad3efdfd88..c094df337afd1 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3455,6 +3455,7 @@ declare namespace monaco.editor { */ suggest?: ISuggestOptions; inlineSuggest?: IInlineSuggestOptions; + experimentalInlineEdit?: IInlineEditOptions; /** * Smart select options. */ @@ -4508,6 +4509,23 @@ declare namespace monaco.editor { fontFamily?: string | 'default'; } + export interface IInlineEditOptions { + /** + * Enable or disable the rendering of automatic inline edit. + */ + enabled?: boolean; + showToolbar?: 'always' | 'onHover' | 'never'; + /** + * Font family for inline suggestions. + */ + fontFamily?: string | 'default'; + /** + * Does not clear active inline suggestions when the editor loses focus. + */ + keepOnBlur?: boolean; + backgroundColoring?: boolean; + } + export interface IBracketPairColorizationOptions { /** * Enable or disable bracket pair colorization. @@ -4843,91 +4861,92 @@ declare namespace monaco.editor { hover = 60, inDiffEditor = 61, inlineSuggest = 62, - letterSpacing = 63, - lightbulb = 64, - lineDecorationsWidth = 65, - lineHeight = 66, - lineNumbers = 67, - lineNumbersMinChars = 68, - linkedEditing = 69, - links = 70, - matchBrackets = 71, - minimap = 72, - mouseStyle = 73, - mouseWheelScrollSensitivity = 74, - mouseWheelZoom = 75, - multiCursorMergeOverlapping = 76, - multiCursorModifier = 77, - multiCursorPaste = 78, - multiCursorLimit = 79, - occurrencesHighlight = 80, - overviewRulerBorder = 81, - overviewRulerLanes = 82, - padding = 83, - pasteAs = 84, - parameterHints = 85, - peekWidgetDefaultFocus = 86, - definitionLinkOpensInPeek = 87, - quickSuggestions = 88, - quickSuggestionsDelay = 89, - readOnly = 90, - readOnlyMessage = 91, - renameOnType = 92, - renderControlCharacters = 93, - renderFinalNewline = 94, - renderLineHighlight = 95, - renderLineHighlightOnlyWhenFocus = 96, - renderValidationDecorations = 97, - renderWhitespace = 98, - revealHorizontalRightPadding = 99, - roundedSelection = 100, - rulers = 101, - scrollbar = 102, - scrollBeyondLastColumn = 103, - scrollBeyondLastLine = 104, - scrollPredominantAxis = 105, - selectionClipboard = 106, - selectionHighlight = 107, - selectOnLineNumbers = 108, - showFoldingControls = 109, - showUnused = 110, - snippetSuggestions = 111, - smartSelect = 112, - smoothScrolling = 113, - stickyScroll = 114, - stickyTabStops = 115, - stopRenderingLineAfter = 116, - suggest = 117, - suggestFontSize = 118, - suggestLineHeight = 119, - suggestOnTriggerCharacters = 120, - suggestSelection = 121, - tabCompletion = 122, - tabIndex = 123, - unicodeHighlighting = 124, - unusualLineTerminators = 125, - useShadowDOM = 126, - useTabStops = 127, - wordBreak = 128, - wordSeparators = 129, - wordWrap = 130, - wordWrapBreakAfterCharacters = 131, - wordWrapBreakBeforeCharacters = 132, - wordWrapColumn = 133, - wordWrapOverride1 = 134, - wordWrapOverride2 = 135, - wrappingIndent = 136, - wrappingStrategy = 137, - showDeprecated = 138, - inlayHints = 139, - editorClassName = 140, - pixelRatio = 141, - tabFocusMode = 142, - layoutInfo = 143, - wrappingInfo = 144, - defaultColorDecorators = 145, - colorDecoratorsActivatedOn = 146, - inlineCompletionsAccessibilityVerbose = 147 + inlineEdit = 63, + letterSpacing = 64, + lightbulb = 65, + lineDecorationsWidth = 66, + lineHeight = 67, + lineNumbers = 68, + lineNumbersMinChars = 69, + linkedEditing = 70, + links = 71, + matchBrackets = 72, + minimap = 73, + mouseStyle = 74, + mouseWheelScrollSensitivity = 75, + mouseWheelZoom = 76, + multiCursorMergeOverlapping = 77, + multiCursorModifier = 78, + multiCursorPaste = 79, + multiCursorLimit = 80, + occurrencesHighlight = 81, + overviewRulerBorder = 82, + overviewRulerLanes = 83, + padding = 84, + pasteAs = 85, + parameterHints = 86, + peekWidgetDefaultFocus = 87, + definitionLinkOpensInPeek = 88, + quickSuggestions = 89, + quickSuggestionsDelay = 90, + readOnly = 91, + readOnlyMessage = 92, + renameOnType = 93, + renderControlCharacters = 94, + renderFinalNewline = 95, + renderLineHighlight = 96, + renderLineHighlightOnlyWhenFocus = 97, + renderValidationDecorations = 98, + renderWhitespace = 99, + revealHorizontalRightPadding = 100, + roundedSelection = 101, + rulers = 102, + scrollbar = 103, + scrollBeyondLastColumn = 104, + scrollBeyondLastLine = 105, + scrollPredominantAxis = 106, + selectionClipboard = 107, + selectionHighlight = 108, + selectOnLineNumbers = 109, + showFoldingControls = 110, + showUnused = 111, + snippetSuggestions = 112, + smartSelect = 113, + smoothScrolling = 114, + stickyScroll = 115, + stickyTabStops = 116, + stopRenderingLineAfter = 117, + suggest = 118, + suggestFontSize = 119, + suggestLineHeight = 120, + suggestOnTriggerCharacters = 121, + suggestSelection = 122, + tabCompletion = 123, + tabIndex = 124, + unicodeHighlighting = 125, + unusualLineTerminators = 126, + useShadowDOM = 127, + useTabStops = 128, + wordBreak = 129, + wordSeparators = 130, + wordWrap = 131, + wordWrapBreakAfterCharacters = 132, + wordWrapBreakBeforeCharacters = 133, + wordWrapColumn = 134, + wordWrapOverride1 = 135, + wordWrapOverride2 = 136, + wrappingIndent = 137, + wrappingStrategy = 138, + showDeprecated = 139, + inlayHints = 140, + editorClassName = 141, + pixelRatio = 142, + tabFocusMode = 143, + layoutInfo = 144, + wrappingInfo = 145, + defaultColorDecorators = 146, + colorDecoratorsActivatedOn = 147, + inlineCompletionsAccessibilityVerbose = 148 } export const EditorOptions: { @@ -5052,6 +5071,7 @@ declare namespace monaco.editor { stopRenderingLineAfter: IEditorOption; suggest: IEditorOption>>; inlineSuggest: IEditorOption>>; + inlineEdit: IEditorOption>>; inlineCompletionsAccessibilityVerbose: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; @@ -6454,6 +6474,8 @@ declare namespace monaco.languages { */ export function registerInlineCompletionsProvider(languageSelector: LanguageSelector, provider: InlineCompletionsProvider): IDisposable; + export function registerInlineEditProvider(languageSelector: LanguageSelector, provider: InlineEditProvider): IDisposable; + /** * Register an inlay hints provider. */ @@ -7913,6 +7935,27 @@ declare namespace monaco.languages { provideMappedEdits(document: editor.ITextModel, codeBlocks: string[], context: MappedEditsContext, token: CancellationToken): Promise; } + export interface IInlineEdit { + text: string; + range: IRange; + accepted?: Command; + rejected?: Command; + } + + export interface IInlineEditContext { + triggerKind: InlineEditTriggerKind; + } + + export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1 + } + + export interface InlineEditProvider { + provideInlineEdit(model: editor.ITextModel, context: IInlineEditContext, token: CancellationToken): ProviderResult; + freeInlineEdit(edit: T): void; + } + export interface ILanguageExtensionPoint { id: string; extensions?: string[]; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0b3015ddf756a..15c6a1dfe59ff 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -201,12 +201,14 @@ export class MenuId { static readonly TerminalStickyScrollContext = new MenuId('TerminalStickyScrollContext'); static readonly WebviewContext = new MenuId('WebviewContext'); static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); + static readonly InlineEditActions = new MenuId('InlineEditActions'); static readonly NewFile = new MenuId('NewFile'); static readonly MergeInput1Toolbar = new MenuId('MergeToolbar1Toolbar'); static readonly MergeInput2Toolbar = new MenuId('MergeToolbar2Toolbar'); static readonly MergeBaseToolbar = new MenuId('MergeBaseToolbar'); static readonly MergeInputResultToolbar = new MenuId('MergeToolbarResultToolbar'); static readonly InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar'); + static readonly InlineEditToolbar = new MenuId('InlineEditToolbar'); static readonly ChatContext = new MenuId('ChatContext'); static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 28193f860652a..80d13f113623e 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -32,7 +32,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IdentifiableInlineEdit, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol'; import { ResourceMap } from 'vs/base/common/map'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; @@ -618,6 +618,19 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.inlineCompletionsProvider.register(selector, provider)); } + $registerInlineEditProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier): void { + const provider: languages.InlineEditProvider = { + provideInlineEdit: async (model: ITextModel, context: languages.IInlineEditContext, token: CancellationToken): Promise => { + return this._proxy.$provideInlineEdit(handle, model.uri, context, token); + }, + freeInlineEdit: (edit: IdentifiableInlineEdit): void => { + this._proxy.$freeInlineEdit(handle, edit.pid); + } + + }; + this._registrations.set(handle, this._languageFeaturesService.inlineEditProvider.register(selector, provider)); + } + // --- parameter hints $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b93fdbeb5d7c6..b0b9c8c0b4731 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -632,6 +632,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostLanguageFeatures.registerInlineCompletionsProvider(extension, checkSelector(selector), provider, metadata); }, + registerInlineEditProvider(selector: vscode.DocumentSelector, provider: vscode.InlineEditProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'inlineEdit'); + return extHostLanguageFeatures.registerInlineEditProvider(extension, checkSelector(selector), provider); + }, registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentLinkProvider(extension, checkSelector(selector), provider); }, @@ -1681,6 +1685,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, NewSymbolName: extHostTypes.NewSymbolName, NewSymbolNameTag: extHostTypes.NewSymbolNameTag, + InlineEdit: extHostTypes.InlineEdit, + InlineEditTriggerKind: extHostTypes.InlineEditTriggerKind, }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 34e3f6664b35d..3481cd5a5102c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -392,6 +392,10 @@ export interface IdentifiableInlineCompletion extends languages.InlineCompletion idx: number; } +export interface IdentifiableInlineEdit extends languages.IInlineEdit { + pid: number; +} + export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void; @@ -422,6 +426,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend): void; $registerCompletionsProvider(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; $registerInlineCompletionsSupport(handle: number, selector: IDocumentFilterDto[], supportsHandleDidShowCompletionItem: boolean, extensionId: string, yieldsToExtensionIds: string[]): void; + $registerInlineEditProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier): void; $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; $registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean, eventHandle: number | undefined, displayName: string | undefined): void; $emitInlayHintsEvent(eventHandle: number): void; @@ -2146,6 +2151,8 @@ export interface ExtHostLanguageFeaturesShape { $releaseTypeHierarchy(handle: number, sessionId: string): void; $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise; $provideMappedEdits(handle: number, document: UriComponents, codeBlocks: string[], context: IMappedEditsContextDto, token: CancellationToken): Promise; + $provideInlineEdit(handle: number, document: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise; + $freeInlineEdit(handle: number, pid: number): void; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 4554c2a0b598b..d6bc4f793aa36 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -32,7 +32,7 @@ import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostTelemetry, IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; +import { CodeActionKind, CompletionList, Disposable, DocumentSymbol, InlineCompletionTriggerKind, InternalDataTransferItem, Location, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType, InlineEditTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; import { Cache } from './cache'; @@ -1355,6 +1355,81 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { } } +class InlineEditAdapter { + private readonly _references = new ReferenceMap<{ + dispose(): void; + item: vscode.InlineEdit; + }>(); + + private languageTriggerKindToVSCodeTriggerKind: Record = { + [languages.InlineEditTriggerKind.Automatic]: InlineEditTriggerKind.Automatic, + [languages.InlineEditTriggerKind.Invoke]: InlineEditTriggerKind.Invoke, + }; + + async provideInlineEdits(uri: URI, context: languages.IInlineEditContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(uri); + const result = await this._provider.provideInlineEdit(doc, { + triggerKind: this.languageTriggerKindToVSCodeTriggerKind[context.triggerKind] + }, token); + + if (!result) { + // undefined and null are valid results + return undefined; + } + + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + let disposableStore: DisposableStore | undefined = undefined; + const pid = this._references.createReferenceId({ + dispose() { + disposableStore?.dispose(); + }, + item: result + }); + + let acceptCommand: languages.Command | undefined = undefined; + if (result.accepted) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + acceptCommand = this._commands.toInternal(result.accepted, disposableStore); + } + let rejectCommand: languages.Command | undefined = undefined; + if (result.rejected) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + rejectCommand = this._commands.toInternal(result.rejected, disposableStore); + } + + const langResult: extHostProtocol.IdentifiableInlineEdit = { + pid, + text: result.text, + range: typeConvert.Range.from(result.range), + accepted: acceptCommand, + rejected: rejectCommand, + }; + + return langResult; + } + + disposeEdit(pid: number) { + const data = this._references.disposeReferenceId(pid); + data?.dispose(); + } + + constructor( + _extension: IExtensionDescription, + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.InlineEditProvider, + private readonly _commands: CommandsConverter, + ) { + } +} + class ReferenceMap { private readonly _references = new Map(); private _idPool = 1; @@ -1946,7 +2021,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter - | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter; + | DocumentOnDropEditAdapter | MappedEditsAdapter | NewSymbolNamesAdapter | InlineEditAdapter; class AdapterData { constructor( @@ -2424,6 +2499,23 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._withAdapter(handle, InlineCompletionAdapterBase, async adapter => { adapter.disposeCompletions(pid); }, undefined, undefined); } + // --- inline edit + + registerInlineEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineEditProvider): vscode.Disposable { + const adapter = new InlineEditAdapter(extension, this._documents, provider, this._commands.converter); + const handle = this._addNewAdapter(adapter, extension); + this._proxy.$registerInlineEditProvider(handle, this._transformDocumentSelector(selector, extension), extension.identifier); + return this._createDisposable(handle); + } + + $provideInlineEdit(handle: number, resource: UriComponents, context: languages.IInlineEditContext, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineEditAdapter, adapter => adapter.provideInlineEdits(URI.revive(resource), context, token), undefined, token); + } + + $freeInlineEdit(handle: number, pid: number): void { + this._withAdapter(handle, InlineEditAdapter, async adapter => { adapter.disposeEdit(pid); }, undefined, undefined); + } + // --- parameter hints registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 39e91887625e0..906dc75413ead 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4332,3 +4332,19 @@ export enum KeywordRecognitionStatus { } //#endregion + +//#region InlineEdit + +export class InlineEdit implements vscode.InlineEdit { + constructor( + public readonly text: string, + public readonly range: Range, + ) { } +} + +export enum InlineEditTriggerKind { + Invoke = 0, + Automatic = 1, +} + +//#endregion diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index a81d30aee5e10..3a88ed4a23f5f 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -395,6 +395,13 @@ const apiMenus: IAPIMenu[] = [ supportsSubmenus: false, proposed: 'inlineCompletionsAdditions' }, + { + key: 'editor/inlineEdit/actions', + id: MenuId.InlineEditActions, + description: localize('inlineEdit.actions', "The actions shown when hovering on an inline edit"), + supportsSubmenus: false, + proposed: 'inlineEdit' + }, { key: 'editor/content', id: MenuId.EditorContent, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 526ed79e09246..12ddfb110ec7d 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -62,6 +62,7 @@ export const allApiProposals = Object.freeze({ handleIssueUri: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts', idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', + inlineEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts', interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', diff --git a/src/vscode-dts/vscode.proposed.inlineEdit.d.ts b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts new file mode 100644 index 0000000000000..90a1957e849b4 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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 class InlineEdit { + + + /** + * The new text for this edit. + */ + readonly text: string; + + /** + * An range that will be replaced by the text of the inline edit. + * If change is only additive, this can be empty (same start and end position). + */ + readonly range: Range; + + /** + * An optional command that will be executed after applying the inline edit. + */ + accepted?: Command; + + /** + * An optional command that will be executed after rejecting the inline edit. + */ + rejected?: Command; + + /** + * Creates a new inline edit. + * + * @param text The new text for this edit. + * @param replaceRange An range that will be replaced by the text of the inline edit. + */ + constructor(text: string, range: Range); + } + + export interface InlineEditContext { + /** + * Describes how the inline edit was triggered. + */ + triggerKind: InlineEditTriggerKind; + } + + export enum InlineEditTriggerKind { + /** + * Completion was triggered explicitly by a user gesture. + * Return multiple completion items to enable cycling through them. + */ + Invoke = 0, + + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 1, + } + + export interface InlineEditProvider { + /** + * Provide inline edit for the given document. + * + * @param document The document for which the inline edit are computed. + * @param context Additional context information about the request. + * @param token A cancellation token. + * @return An inline edit or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideInlineEdit(document: TextDocument, context: InlineEditContext, token: CancellationToken): ProviderResult; + } + + export namespace languages { + + /** + * Register a provider that can handle inline edits. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A provider that can handle inline edits. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerInlineEditProvider(selector: DocumentSelector, provider: InlineEditProvider): Disposable; + + } +} From 881321da5cfac6fe0ce6894fe40b2ba6623cba0c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 09:22:53 -0600 Subject: [PATCH 0401/1863] 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 d851609de14ad..890fb103292a5 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 499d7e375da55..2cc70c7eb076a 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 00124e9e5830e3efc897db71c781899f8a676295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Fri, 16 Feb 2024 16:40:34 +0100 Subject: [PATCH 0402/1863] Improve inline edit commands' preconditions (#205373) Improve inline edit commands preconditions --- src/vs/editor/contrib/inlineEdit/browser/commands.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/commands.ts b/src/vs/editor/contrib/inlineEdit/browser/commands.ts index 93f1f9d4a2cc4..11d6dfa2f220d 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/commands.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/commands.ts @@ -19,7 +19,7 @@ export class AcceptInlineEdit extends EditorAction { id: inlineEditAcceptId, label: 'Accept Inline Edit', alias: 'Accept Inline Edit', - precondition: EditorContextKeys.writable, + precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext), kbOpts: [ { weight: KeybindingWeight.EditorContrib + 1, @@ -43,15 +43,16 @@ export class AcceptInlineEdit extends EditorAction { export class TriggerInlineEdit extends EditorAction { constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)); super({ id: 'editor.action.inlineEdit.trigger', label: 'Trigger Inline Edit', alias: 'Trigger Inline Edit', - precondition: EditorContextKeys.writable, + precondition: activeExpr, kbOpts: { weight: KeybindingWeight.EditorContrib + 1, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Equal, - kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, ContextKeyExpr.not(InlineEditController.inlineEditVisibleKey)) + kbExpr: activeExpr }, }); } @@ -124,15 +125,16 @@ export class JumpBackInlineEdit extends EditorAction { export class RejectInlineEdit extends EditorAction { constructor() { + const activeExpr = ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext); super({ id: inlineEditRejectId, label: 'Reject Inline Edit', alias: 'Reject Inline Edit', - precondition: EditorContextKeys.writable, + precondition: activeExpr, kbOpts: { weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, - kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineEditController.inlineEditVisibleContext) + kbExpr: activeExpr }, menuOpts: [{ menuId: MenuId.InlineEditToolbar, From 4ba66bf35e20a55e7261702371db549f2ad396bd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 16:09:04 +0000 Subject: [PATCH 0403/1863] Chat API: add 'command' to response history (#205377) --- .../src/singlefolder-tests/chat.test.ts | 39 ++++++++++++++----- .../api/common/extHostChatAgents2.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 3 +- .../vscode.proposed.chatAgents2.d.ts | 2 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 6c0406aadbbee..bdb033322ebd7 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; -import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; +import { CancellationToken, ChatAgentContext, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -22,6 +22,15 @@ suite('chat', () => { }); function getDeferredForRequest(): DeferredPromise { + const deferred = new DeferredPromise(); + disposables.push(setupAgent()(request => deferred.complete(request.request))); + + return deferred; + } + + function setupAgent(): Event<{ request: ChatAgentRequest; context: ChatAgentContext }> { + const emitter = new EventEmitter<{ request: ChatAgentRequest; context: ChatAgentContext }>(); + disposables.push(); disposables.push(interactive.registerInteractiveSessionProvider('provider', { prepareSession: (_token: CancellationToken): ProviderResult => { return { @@ -31,9 +40,8 @@ suite('chat', () => { }, })); - const deferred = new DeferredPromise(); - const agent = chat.createChatAgent('agent', (request, _context, _progress, _token) => { - deferred.complete(request); + const agent = chat.createChatAgent('agent', (request, context, _progress, _token) => { + emitter.fire({ request, context }); return null; }); agent.isDefault = true; @@ -43,15 +51,26 @@ suite('chat', () => { } }; disposables.push(agent); - return deferred; + return emitter.event; } test('agent and slash command', async () => { - const deferred = getDeferredForRequest(); + const onRequest = setupAgent(); interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); - const request = await deferred.p; - assert.deepStrictEqual(request.command, 'hello'); - assert.strictEqual(request.prompt, 'friend'); + + let i = 0; + onRequest(request => { + if (i === 0) { + assert.deepStrictEqual(request.request.command, 'hello'); + assert.strictEqual(request.request.prompt, 'friend'); + i++; + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + } else { + assert.strictEqual(request.context.history.length, 1); + assert.strictEqual(request.context.history[0].agent.agent, 'agent'); + assert.strictEqual(request.context.history[0].command, 'hello'); + } + }); }); test('agent and variable', async () => { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 1f40b04c9241e..399ff60d03002 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -239,7 +239,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId })); + res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId }, h.request.command)); } return res; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 906dc75413ead..611314d600fe2 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4274,7 +4274,8 @@ export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string } + readonly agent: { extensionId: string; agent: string }, + readonly command?: string ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 63cad62d20ad0..93938b8ba6819 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -54,6 +54,8 @@ declare module 'vscode' { */ readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly command?: string; + private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); } From f12778f1837d80f6210e9157d0f853a94d08c0b0 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:14:18 +0100 Subject: [PATCH 0404/1863] Fix statusbar actions (#205380) fix statusbar actions --- src/vs/workbench/contrib/scm/browser/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index d44c36c332bef..6f20e40b24cc0 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -150,7 +150,7 @@ export class StatusBarAction extends Action { class StatusBarActionViewItem extends ActionViewItem { constructor(action: StatusBarAction, options: IBaseActionViewItemOptions) { - super(null, action, options); + super(null, action, { ...options, icon: false, label: true }); } protected override updateLabel(): void { From ee1cb48bb1005d7299e97f8445783a7c976f9c08 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:17:11 -0600 Subject: [PATCH 0405/1863] 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 d409361d517a5..a2349bc25c12d 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 d1586f8c4ba7c..abf356fbdffd1 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 772e2113447bbb131b3a8c55642aee9905e8abf4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 16:54:57 +0100 Subject: [PATCH 0406/1863] Small code cleanup --- .../features/revertButtonsFeature.ts | 120 ++++++++++-------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 084706bf613a6..10673e3cdcb78 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -19,6 +19,8 @@ import { RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { GlyphMarginLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; +const emptyArr: never[] = []; + export class RevertButtonsFeature extends Disposable { constructor( private readonly _editors: DiffEditorEditors, @@ -27,71 +29,79 @@ export class RevertButtonsFeature extends Disposable { private readonly _widget: DiffEditorWidget ) { super(); + } - const emptyArr: never[] = []; - const selectedDiffs = derived(this, (reader) => { - /** @description selectedDiffs */ - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!diff) { return emptyArr; } - - const selections = this._editors.modifiedSelections.read(reader); - - if (selections.every(s => s.isEmpty())) { - return emptyArr; - } - - const lineRanges = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s))); - - const mappings = diff.mappings.filter(m => m.lineRangeMapping.innerChanges && lineRanges.intersects(m.lineRangeMapping.modified)); - - const result = mappings.map(mapping => ({ - mapping, - rangeMappings: mapping.lineRangeMapping.innerChanges!.filter(c => selections.some(s => Range.areIntersecting(c.modifiedRange, s))) - })); - if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } - return result; - }); + private readonly _selectedDiffs = derived(this, (reader) => { + /** @description selectedDiffs */ + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + // Return `emptyArr` because it is a constant. [] is always a new array and would trigger a change. + if (!diff) { return emptyArr; } + + const selections = this._editors.modifiedSelections.read(reader); + if (selections.every(s => s.isEmpty())) { return emptyArr; } + + const selectedLineNumbers = new LineRangeSet(selections.map(s => LineRange.fromRangeInclusive(s))); + + const selectedMappings = diff.mappings.filter(m => + m.lineRangeMapping.innerChanges && selectedLineNumbers.intersects(m.lineRangeMapping.modified) + ); + const result = selectedMappings.map(mapping => ({ + mapping, + rangeMappings: mapping.lineRangeMapping.innerChanges!.filter( + c => selections.some(s => Range.areIntersecting(c.modifiedRange, s)) + ) + })); + if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } + return result; + }); - this._register(autorunWithStore((reader, store) => { - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!model || !diff) { return; } - const movedTextToCompare = this._diffModel.read(reader)!.movedTextToCompare.read(reader); - if (movedTextToCompare) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } + private readonly _revertButtons = this._register(autorunWithStore((reader, store) => { + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + if (!model || !diff) { return; } + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } + if (model.movedTextToCompare.read(reader)) { return; } - const glyphWidgetsModified: IGlyphMarginWidget[] = []; + const glyphWidgetsModified: IGlyphMarginWidget[] = []; - const selectedDiffs_ = selectedDiffs.read(reader); - const diffsSet = new Set(selectedDiffs_.map(d => d.mapping)); + const selectedDiffs = this._selectedDiffs.read(reader); + const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); - if (selectedDiffs_.length > 0) { - const selections = this._editors.modifiedSelections.read(reader); + if (selectedDiffs.length > 0) { + // The button to revert the selection + const selections = this._editors.modifiedSelections.read(reader); - const btn = store.add(new RevertButton(selections[selections.length - 1].positionLineNumber, this._widget, selectedDiffs_.flatMap(d => d.rangeMappings), true)); + const btn = store.add(new RevertButton( + selections[selections.length - 1].positionLineNumber, + this._widget, + selectedDiffs.flatMap(d => d.rangeMappings), + true + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } + + for (const m of diff.mappings) { + if (selectedDiffsSet.has(m)) { continue; } + if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { + const btn = store.add(new RevertButton( + m.lineRangeMapping.modified.startLineNumber, + this._widget, + m.lineRangeMapping.innerChanges, + false + )); this._editors.modified.addGlyphMarginWidget(btn); glyphWidgetsModified.push(btn); } + } - for (const m of diff.mappings) { - if (diffsSet.has(m)) { - continue; - } - if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { - const btn = store.add(new RevertButton(m.lineRangeMapping.modified.startLineNumber, this._widget, m.lineRangeMapping.innerChanges, false)); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } + store.add(toDisposable(() => { + for (const w of glyphWidgetsModified) { + this._editors.modified.removeGlyphMarginWidget(w); } - - store.add(toDisposable(() => { - for (const w of glyphWidgetsModified) { - this._editors.modified.removeGlyphMarginWidget(w); - } - })); })); - } + })); } export class RevertButton extends Disposable implements IGlyphMarginWidget { @@ -102,7 +112,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { getId(): string { return this._id; } private readonly _domNode = h('div.revertButton', { - title: this._selection + title: this._revertSelection ? localize('revertSelectedChanges', 'Revert Selected Changes') : localize('revertChange', 'Revert Change') }, @@ -113,7 +123,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { private readonly _lineNumber: number, private readonly _widget: DiffEditorWidget, private readonly _diffs: RangeMapping[], - private readonly _selection: boolean, + private readonly _revertSelection: boolean, ) { super(); From 4ab700330c138d400bb21d3670c7f66f745fe33a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 17:01:40 +0100 Subject: [PATCH 0407/1863] Fixes CI --- .../features/revertButtonsFeature.ts | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 10673e3cdcb78..06a7ade202d93 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -29,6 +29,53 @@ export class RevertButtonsFeature extends Disposable { private readonly _widget: DiffEditorWidget ) { super(); + + this._register(autorunWithStore((reader, store) => { + const model = this._diffModel.read(reader); + const diff = model?.diff.read(reader); + if (!model || !diff) { return; } + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } + if (model.movedTextToCompare.read(reader)) { return; } + + const glyphWidgetsModified: IGlyphMarginWidget[] = []; + + const selectedDiffs = this._selectedDiffs.read(reader); + const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); + + if (selectedDiffs.length > 0) { + // The button to revert the selection + const selections = this._editors.modifiedSelections.read(reader); + + const btn = store.add(new RevertButton( + selections[selections.length - 1].positionLineNumber, + this._widget, + selectedDiffs.flatMap(d => d.rangeMappings), + true + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } + + for (const m of diff.mappings) { + if (selectedDiffsSet.has(m)) { continue; } + if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { + const btn = store.add(new RevertButton( + m.lineRangeMapping.modified.startLineNumber, + this._widget, + m.lineRangeMapping.innerChanges, + false + )); + this._editors.modified.addGlyphMarginWidget(btn); + glyphWidgetsModified.push(btn); + } + } + + store.add(toDisposable(() => { + for (const w of glyphWidgetsModified) { + this._editors.modified.removeGlyphMarginWidget(w); + } + })); + })); } private readonly _selectedDiffs = derived(this, (reader) => { @@ -55,53 +102,6 @@ export class RevertButtonsFeature extends Disposable { if (result.length === 0 || result.every(r => r.rangeMappings.length === 0)) { return emptyArr; } return result; }); - - private readonly _revertButtons = this._register(autorunWithStore((reader, store) => { - const model = this._diffModel.read(reader); - const diff = model?.diff.read(reader); - if (!model || !diff) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } - if (model.movedTextToCompare.read(reader)) { return; } - - const glyphWidgetsModified: IGlyphMarginWidget[] = []; - - const selectedDiffs = this._selectedDiffs.read(reader); - const selectedDiffsSet = new Set(selectedDiffs.map(d => d.mapping)); - - if (selectedDiffs.length > 0) { - // The button to revert the selection - const selections = this._editors.modifiedSelections.read(reader); - - const btn = store.add(new RevertButton( - selections[selections.length - 1].positionLineNumber, - this._widget, - selectedDiffs.flatMap(d => d.rangeMappings), - true - )); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } - - for (const m of diff.mappings) { - if (selectedDiffsSet.has(m)) { continue; } - if (!m.lineRangeMapping.modified.isEmpty && m.lineRangeMapping.innerChanges) { - const btn = store.add(new RevertButton( - m.lineRangeMapping.modified.startLineNumber, - this._widget, - m.lineRangeMapping.innerChanges, - false - )); - this._editors.modified.addGlyphMarginWidget(btn); - glyphWidgetsModified.push(btn); - } - } - - store.add(toDisposable(() => { - for (const w of glyphWidgetsModified) { - this._editors.modified.removeGlyphMarginWidget(w); - } - })); - })); } export class RevertButton extends Disposable implements IGlyphMarginWidget { From 7f07489455fda8bc1841b98b9b7e58b20b7ee3ef Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 16 Feb 2024 17:10:21 +0100 Subject: [PATCH 0408/1863] Small improvement --- .../browser/widget/diffEditor/features/revertButtonsFeature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index 06a7ade202d93..c82167c3d13da 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -31,10 +31,10 @@ export class RevertButtonsFeature extends Disposable { super(); this._register(autorunWithStore((reader, store) => { + if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } const model = this._diffModel.read(reader); const diff = model?.diff.read(reader); if (!model || !diff) { return; } - if (!this._options.shouldRenderRevertArrows.read(reader)) { return; } if (model.movedTextToCompare.read(reader)) { return; } const glyphWidgetsModified: IGlyphMarginWidget[] = []; From 29924c918fc21025c13354b62ff260678fda83db Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 10:32:42 -0600 Subject: [PATCH 0409/1863] 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 d823a5b7fd931..0c577a9df8a63 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 abf356fbdffd1..cd984a4d61e10 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 70ee41baa6803..ca3bdbab8939c 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 12281b11be58d840611d60415c105c7507e63ae2 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 16 Feb 2024 10:20:16 -0800 Subject: [PATCH 0410/1863] testing: adopt hover delegate (#205386) --- .../browser/ui/iconLabel/iconLabelHover.ts | 5 +- .../testing/browser/testCoverageBars.ts | 63 ++++++++----------- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 23878f78ea830..508a643abb4e7 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -31,6 +31,7 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo } type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; +type IHoverContentOrFactory = IHoverContent | (() => IHoverContent); type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined; /** @@ -162,7 +163,7 @@ class UpdatableHoverWidget implements IDisposable { } } -export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover { +export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContentOrFactory, options?: IUpdatableHoverOptions): ICustomHover { let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; @@ -186,7 +187,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM return new TimeoutTimer(async () => { if (!hoverWidget || hoverWidget.isDisposed) { hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0); - await hoverWidget.update(content, focus, options); + await hoverWidget.update(typeof content === 'function' ? content() : content, focus, options); } }, delay); }; diff --git a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts index 08cb528de32ab..7261e5e10218b 100644 --- a/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts +++ b/src/vs/workbench/contrib/testing/browser/testCoverageBars.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ 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 { assertNever } from 'vs/base/common/assert'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; @@ -20,7 +22,6 @@ import { ITestingCoverageBarThresholds, TestingConfigKeys, TestingDisplayedCover import { AbstractFileCoverage, getTotalCoveragePercent } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestCoverageService } from 'vs/workbench/contrib/testing/common/testCoverageService'; import { ICoveredCount } from 'vs/workbench/contrib/testing/common/testTypes'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; export interface TestCoverageBarsOptions { /** @@ -62,6 +63,7 @@ export class ManagedTestCoverageBars extends Disposable { }); private readonly visibleStore = this._register(new DisposableStore()); + private readonly customHovers: ICustomHover[] = []; /** Gets whether coverage is currently visible for the resource. */ public get visible() { @@ -70,36 +72,13 @@ export class ManagedTestCoverageBars extends Disposable { constructor( protected readonly options: TestCoverageBarsOptions, - @IHoverService private readonly hoverService: IHoverService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); } - private attachHover(target: HTMLElement, factory: (coverage: CoverageBarSource) => string | IMarkdownString | undefined) { - target.onmouseenter = () => { - if (!this._coverage) { - return; - } - - const content = factory(this._coverage); - if (!content) { - return; - } - - const hover = this.hoverService.showHover({ - content, - target, - appearance: { - showPointer: true, - compact: true, - skipFadeInAnimation: true, - } - }); - if (hover) { - this.visibleStore.add(hover); - } - }; + private attachHover(target: HTMLElement, factory: (coverage: CoverageBarSource) => string | ITooltipMarkdownString | undefined) { + this._register(setupCustomHover(getDefaultHoverDelegate('element'), target, () => this._coverage && factory(this._coverage))); } public setCoverageInfo(coverage: CoverageBarSource | undefined) { @@ -107,6 +86,7 @@ export class ManagedTestCoverageBars extends Disposable { if (!coverage) { if (this._coverage) { this._coverage = undefined; + this.customHovers.forEach(c => c.hide()); ds.clear(); } return; @@ -218,15 +198,23 @@ const displayPercent = (value: number, precision = 2) => { return `${display}%`; }; -const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', coverage.statement.covered, coverage.statement.total, displayPercent(percent(coverage.statement))); -const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', coverage.declaration.covered, coverage.declaration.total, displayPercent(percent(coverage.declaration))); -const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', coverage.branch.covered, coverage.branch.total, displayPercent(percent(coverage.branch))); - -const getOverallHoverText = (coverage: CoverageBarSource) => new MarkdownString([ - stmtCoverageText(coverage), - fnCoverageText(coverage), - branchCoverageText(coverage), -].filter(isDefined).join('\n\n')); +const nf = new Intl.NumberFormat(); +const stmtCoverageText = (coverage: CoverageBarSource) => localize('statementCoverage', '{0}/{1} statements covered ({2})', nf.format(coverage.statement.covered), nf.format(coverage.statement.total), displayPercent(percent(coverage.statement))); +const fnCoverageText = (coverage: CoverageBarSource) => coverage.declaration && localize('functionCoverage', '{0}/{1} functions covered ({2})', nf.format(coverage.declaration.covered), nf.format(coverage.declaration.total), displayPercent(percent(coverage.declaration))); +const branchCoverageText = (coverage: CoverageBarSource) => coverage.branch && localize('branchCoverage', '{0}/{1} branches covered ({2})', nf.format(coverage.branch.covered), nf.format(coverage.branch.total), displayPercent(percent(coverage.branch))); + +const getOverallHoverText = (coverage: CoverageBarSource): ITooltipMarkdownString => { + const str = [ + stmtCoverageText(coverage), + fnCoverageText(coverage), + branchCoverageText(coverage), + ].filter(isDefined).join('\n\n'); + + return { + markdown: new MarkdownString().appendText(str), + markdownNotSupportedFallback: str + }; +}; /** * Renders test coverage bars for a resource in the given container. It will @@ -237,11 +225,10 @@ export class ExplorerTestCoverageBars extends ManagedTestCoverageBars implements constructor( options: TestCoverageBarsOptions, - @IHoverService hoverService: IHoverService, @IConfigurationService configurationService: IConfigurationService, @ITestCoverageService testCoverageService: ITestCoverageService, ) { - super(options, hoverService, configurationService); + super(options, configurationService); const isEnabled = observeTestingConfiguration(configurationService, TestingConfigKeys.ShowCoverageInExplorer); From d5a79d5abe3239e61e332aeea88e38938d574419 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 16 Feb 2024 10:21:51 -0800 Subject: [PATCH 0411/1863] rename value property name (#205387) --- .../browser/telemetry.contribution.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 68275f3ea2002..a2d81dfbe7330 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -304,7 +304,7 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc private reportTelemetry(key: string, target: ConfigurationTarget.USER_LOCAL | ConfigurationTarget.WORKSPACE): void { type UpdatedSettingEvent = { - value: string | undefined; + settingValue: string | undefined; source: string; }; const source = ConfigurationTargetToString(target); @@ -315,90 +315,90 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc this.telemetryService.publicLog2('workbench.activityBar.location', { value: this.getValueToReport(key, target), source }); + }>('workbench.activityBar.location', { settingValue: this.getValueToReport(key, target), source }); return; case AutoUpdateConfigurationKey: this.telemetryService.publicLog2('extensions.autoUpdate', { value: this.getValueToReport(key, target), source }); + }>('extensions.autoUpdate', { settingValue: this.getValueToReport(key, target), source }); return; case 'files.autoSave': this.telemetryService.publicLog2('files.autoSave', { value: this.getValueToReport(key, target), source }); + }>('files.autoSave', { settingValue: this.getValueToReport(key, target), source }); return; case 'editor.stickyScroll.enabled': this.telemetryService.publicLog2('editor.stickyScroll.enabled', { value: this.getValueToReport(key, target), source }); + }>('editor.stickyScroll.enabled', { settingValue: this.getValueToReport(key, target), source }); return; case KEYWORD_ACTIVIATION_SETTING_ID: this.telemetryService.publicLog2('accessibility.voice.keywordActivation', { value: this.getValueToReport(key, target), source }); + }>('accessibility.voice.keywordActivation', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.zoomLevel': this.telemetryService.publicLog2('window.zoomLevel', { value: this.getValueToReport(key, target), source }); + }>('window.zoomLevel', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.zoomPerWindow': this.telemetryService.publicLog2('window.zoomPerWindow', { value: this.getValueToReport(key, target), source }); + }>('window.zoomPerWindow', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.titleBarStyle': this.telemetryService.publicLog2('window.titleBarStyle', { value: this.getValueToReport(key, target), source }); + }>('window.titleBarStyle', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.customTitleBarVisibility': this.telemetryService.publicLog2('window.customTitleBarVisibility', { value: this.getValueToReport(key, target), source }); + }>('window.customTitleBarVisibility', { settingValue: this.getValueToReport(key, target), source }); return; case 'window.nativeTabs': this.telemetryService.publicLog2('window.nativeTabs', { value: this.getValueToReport(key, target), source }); + }>('window.nativeTabs', { settingValue: this.getValueToReport(key, target), source }); return; } } From 0cbb15fb632a5e8933f7b600d2849c3f60e5653e Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 16 Feb 2024 10:29:13 -0800 Subject: [PATCH 0412/1863] Add Quick pick for notebook indentation status bar entry (#205052) * initial quick pick setup + indent multiEditorActions * finish convert whitespace commands for notebook scope * fix leaked disposable * fix setTimeout for indentation quickpick --- .../editorStatusBar/editorStatusBar.ts | 32 ++- .../browser/controller/editActions.ts | 88 +++++- .../controller/notebookIndentationActions.ts | 257 ++++++++++++++++++ .../notebook/common/notebookEditorInput.ts | 6 + 4 files changed, 355 insertions(+), 28 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 68b5eba7d19a2..9900e41b0c3c3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, WorkbenchPhase } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -// import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; -import { getNotebookEditorFromEditorPane, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { SELECT_NOTEBOOK_INDENTATION_ID } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; +import { INotebookEditor, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { NotebookCellsChangeType, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -283,6 +283,10 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); if (activeEditor) { this._show(activeEditor); + this._itemDisposables.add(activeEditor.onDidChangeSelection(() => { + this._accessor.clear(); + this._show(activeEditor); + })); } else { this._accessor.clear(); } @@ -294,11 +298,14 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC return; } - const cellEditorOverrides = this._configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; - - const indentSize = cellEditorOverrides['editor.indentSize'] ?? this._configurationService.getValue('editor.indentSize'); - const tabSize = cellEditorOverrides['editor.tabSize'] ?? this._configurationService.getValue('editor.tabSize'); - const insertSpaces = cellEditorOverrides['editor.insertSpaces'] ?? this._configurationService.getValue('editor.insertSpaces'); + const cellOptions = editor.getActiveCell()?.textModel?.getOptions(); + if (!cellOptions) { + this._accessor.clear(); + return; + } + const indentSize = cellOptions?.indentSize; + const tabSize = cellOptions?.tabSize; + const insertSpaces = cellOptions?.insertSpaces; const width = typeof indentSize === 'number' ? indentSize : tabSize; @@ -313,9 +320,8 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC name: nls.localize('notebook.indentation', "Notebook Indentation"), text: newText, ariaLabel: newText, - // tooltip: nls.localize('selectNotebookIndentation', "Select Notebook Indentation"), - tooltip: nls.localize('selectNotebookIndentation', "Notebook Indentation"), - // command: SELECT_NOTEBOOK_INDENTATION_ID // TODO@Yoyokrazy -- finish hooking this up + tooltip: nls.localize('selectNotebookIndentation', "Select Indentation"), + command: SELECT_NOTEBOOK_INDENTATION_ID }; if (!this._accessor.value) { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index f056d2e6c7faf..86ffcc2b33494 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -6,34 +6,35 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize, localize2 } from 'vs/nls'; import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConfirmationResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; -import { CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, executeNotebookCondition, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { CellEditState, CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, executeNotebookCondition, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { NotebookChangeTabDisplaySize, NotebookIndentUsingSpaces, NotebookIndentUsingTabs, NotebookIndentationToSpacesAction, NotebookIndentationToTabsAction } from 'vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions'; +import { CHANGE_CELL_LANGUAGE, CellEditState, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; +import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; - +import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; @@ -562,3 +563,60 @@ async function setCellToLanguage(languageId: string, context: IChangeCellContext ); } } + +registerAction2(class SelectNotebookIndentation extends NotebookAction { + constructor() { + super({ + id: SELECT_NOTEBOOK_INDENTATION_ID, + title: localize2('selectNotebookIndentation', 'Select Indentation'), + f1: true, + precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + await this.showNotebookIndentationPicker(accessor, context); + } + + private async showNotebookIndentationPicker(accessor: ServicesAccessor, context: INotebookActionContext) { + const quickInputService = accessor.get(IQuickInputService); + const editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + + const activeNotebook = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + if (!activeNotebook || activeNotebook.isDisposed) { + return quickInputService.pick([{ label: localize('noNotebookEditor', "No notebook editor active at this time") }]); + } + + if (activeNotebook.isReadOnly) { + return quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active notebook editor is read-only.") }]); + } + + const picks: QuickPickInput[] = [ + new NotebookIndentUsingTabs(), // indent using tabs + new NotebookIndentUsingSpaces(), // indent using spaces + new NotebookChangeTabDisplaySize(), // change tab size + new NotebookIndentationToTabsAction(), // convert indentation to tabs + new NotebookIndentationToSpacesAction() // convert indentation to spaces + ].map(item => { + return { + id: item.desc.id, + label: item.desc.title.toString(), + run: () => { + instantiationService.invokeFunction(item.run); + } + }; + }); + + picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") }); + picks.unshift({ type: 'separator', label: localize('indentView', "change view") }); + + const action = await quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true }); + if (!action) { + return; + } + action.run(); + context.notebookEditor.focus(); + return; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts new file mode 100644 index 0000000000000..58d4bf63b6d61 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { isNotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class NotebookIndentUsingTabs extends Action2 { + public static readonly ID = 'notebook.action.indentUsingTabs'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('indentUsingTabs', "Indent Using Tabs"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, false, false); + } +} + +export class NotebookIndentUsingSpaces extends Action2 { + public static readonly ID = 'notebook.action.indentUsingSpaces'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('indentUsingSpaces', "Indent Using Spaces"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, true, false); + } +} + +export class NotebookChangeTabDisplaySize extends Action2 { + public static readonly ID = 'notebook.action.changeTabDisplaySize'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('changeTabDisplaySize', "Change Tab Display Size"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + changeNotebookIndentation(accessor, true, true); + } +} + +export class NotebookIndentationToSpacesAction extends Action2 { + public static readonly ID = 'notebook.action.convertIndentationToSpaces'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('convertIndentationToSpaces', "Convert Indentation to Spaces"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + convertNotebookIndentation(accessor, true); + + } +} + +export class NotebookIndentationToTabsAction extends Action2 { + public static readonly ID = 'notebook.action.convertIndentationToTabs'; + + constructor() { + super({ + id: NotebookIndentUsingSpaces.ID, + title: nls.localize('convertIndentationToTabs', "Convert Indentation to Tabs"), + precondition: undefined, + }); + } + + override run(accessor: ServicesAccessor, ...args: any[]): void { + convertNotebookIndentation(accessor, false); + } +} + +function changeNotebookIndentation(accessor: ServicesAccessor, insertSpaces: boolean, displaySizeOnly: boolean) { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + const notebookEditorService = accessor.get(INotebookEditorService); + const quickInputService = accessor.get(IQuickInputService); + + // keep this check here to pop on non-notebook actions + const activeInput = editorService.activeEditorPane?.input; + const isNotebook = isNotebookEditorInput(activeInput); + if (!isNotebook) { + return; + } + + // get notebook editor to access all codeEditors + const notebookEditor = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value; + if (!notebookEditor) { + return; + } + + const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({ + id: n.toString(), + label: n.toString(), + })); + + // store the initial values of the configuration + const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + const initialInsertSpaces = initialConfig['editor.insertSpaces']; + // remove the initial values from the configuration + delete initialConfig['editor.indentSize']; + delete initialConfig['editor.tabSize']; + delete initialConfig['editor.insertSpaces']; + + setTimeout(() => { + quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File") }).then(pick => { + if (pick) { + const pickedVal = parseInt(pick.label, 10); + if (displaySizeOnly) { + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': pickedVal, + 'editor.indentSize': pickedVal, + 'editor.insertSpaces': initialInsertSpaces + }); + } else { + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': pickedVal, + 'editor.indentSize': pickedVal, + 'editor.insertSpaces': insertSpaces + }); + } + + } + }); + }, 50/* quick input is sensitive to being opened so soon after another */); +} + +function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: boolean): void { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + const logService = accessor.get(ILogService); + const textModelService = accessor.get(ITextModelService); + const notebookEditorService = accessor.get(INotebookEditorService); + const bulkEditService = accessor.get(IBulkEditService); + + // keep this check here to pop on non-notebook + const activeInput = editorService.activeEditorPane?.input; + const isNotebook = isNotebookEditorInput(activeInput); + if (!isNotebook) { + return; + } + + // get notebook editor to access all codeEditors + const notebookTextModel = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value?.textModel; + if (!notebookTextModel) { + return; + } + + const disposable = new DisposableStore(); + try { + Promise.all(notebookTextModel.cells.map(async cell => { + const ref = await textModelService.createModelReference(cell.uri); + disposable.add(ref); + const textEditorModel = ref.object.textEditorModel; + + const modelOpts = cell.textModel?.getOptions(); + if (!modelOpts) { + return; + } + + const edits = getIndentationEditOperations(textEditorModel, modelOpts.tabSize, tabsToSpaces); + + bulkEditService.apply(edits, { label: nls.localize('convertIndentation', "Convert Indentation"), code: 'undoredo.convertIndentation', }); + + })).then((notebookEdits) => { + + // store the initial values of the configuration + const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; + const initialIndentSize = initialConfig['editor.indentSize']; + const initialTabSize = initialConfig['editor.tabSize']; + // remove the initial values from the configuration + delete initialConfig['editor.indentSize']; + delete initialConfig['editor.tabSize']; + delete initialConfig['editor.insertSpaces']; + + configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, { + ...initialConfig, + 'editor.tabSize': initialTabSize, + 'editor.indentSize': initialIndentSize, + 'editor.insertSpaces': tabsToSpaces + }); + disposable.dispose(); + }); + } catch { + logService.error('Failed to convert indentation to spaces for notebook cells.'); + } +} + +function getIndentationEditOperations(model: ITextModel, tabSize: number, tabsToSpaces: boolean): ResourceTextEdit[] { + if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { + // Model is empty + return []; + } + + let spaces = ''; + for (let i = 0; i < tabSize; i++) { + spaces += ' '; + } + + const spacesRegExp = new RegExp(spaces, 'gi'); + + const edits: ResourceTextEdit[] = []; + for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) { + let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber); + if (lastIndentationColumn === 0) { + lastIndentationColumn = model.getLineMaxColumn(lineNumber); + } + + if (lastIndentationColumn === 1) { + continue; + } + + const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn); + const originalIndentation = model.getValueInRange(originalIndentationRange); + const newIndentation = ( + tabsToSpaces + ? originalIndentation.replace(/\t/ig, spaces) + : originalIndentation.replace(spacesRegExp, '\t') + ); + edits.push(new ResourceTextEdit(model.uri, { range: originalIndentationRange, text: newIndentation })); + } + return edits; +} + +registerAction2(NotebookIndentUsingSpaces); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index db98d0bbb96ff..7c1a89da760ad 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -374,3 +374,9 @@ export function isCompositeNotebookEditorInput(thing: unknown): thing is ICompos && Array.isArray((thing).editorInputs) && ((thing).editorInputs.every(input => input instanceof NotebookEditorInput)); } + +export function isNotebookEditorInput(thing: unknown): thing is NotebookEditorInput { + return !!thing + && typeof thing === 'object' + && thing instanceof NotebookEditorInput; +} From 687a754a431df5e59ab528d8c7d35042e1c6ad92 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 16 Feb 2024 19:54:46 +0100 Subject: [PATCH 0413/1863] tweak height logic --- 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 ba003be69a7d6..7fd28a442ac6a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -1118,7 +1118,7 @@ class HunkAccessibleDiffViewer extends AccessibleDiffViewer { const width = observableValue('width', 0); const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk)); const diffs = derived(r => [diff.read(r)]); - const lines = Math.min(6, 3 * diff.get().changedLineCount); + const lines = Math.min(10, 8 + diff.get().changedLineCount); const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines; super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService); From b0593428a503eb46f04513682969ab63778fbfba Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 13:01:10 -0600 Subject: [PATCH 0414/1863] fix #205391 --- src/vs/workbench/contrib/preferences/browser/settingsLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 2edaff0b3b707..6bee52d8257ab 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -149,7 +149,7 @@ export const tocData: ITOCEntry = { { id: 'features/accessibilitySignals', label: localize('accessibility.signals', 'Accessibility Signals'), - settings: ['accessibility.signals.*'] + settings: ['accessibility.signals.*', 'audioCues.*'] }, { id: 'features/accessibility', From ba24d929651af34cd13b52b21cf88d5521068194 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 13:07:26 -0600 Subject: [PATCH 0415/1863] 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 1aaf1516c5e0d..488815cf2589b 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 25d492a063c3d..602560618cc5c 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 f1deb6932a0c0a8a151f78cd8a3fe97c3881d755 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 16 Feb 2024 11:12:12 -0800 Subject: [PATCH 0416/1863] Update unit tests. --- .../test/browser/indentation.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 18ee1b9309c9b..5748693d84b64 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -325,3 +325,47 @@ suite('Editor Contrib - Keep Indent On Paste', () => { }); }); }); + + +suite('Editor Contrib - Auto Dedent On Type', () => { + const rubyIndentationRules = { + // decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b/, + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }; + + let disposables: DisposableStore; + + setup(() => { + disposables = new DisposableStore(); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('issue #198350: in or when incorrectly match non keywords for Ruby', () => { + const languageId = "ruby"; + const model = createTextModel("", languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + const languageService = instantiationService.get(ILanguageService); + const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); + disposables.add(languageService.registerLanguage({ id: languageId })); + disposables.add(languageConfigurationService.register(languageId, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: rubyIndentationRules, + })); + + viewModel.type("def foo\n in"); + assert.strictEqual(model.getValue(), "def foo\n in"); + }); + }); +}); From 89bc8c3765b9f1c27f4c2236cb2ca0f2b01441f0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 16 Feb 2024 20:09:23 +0000 Subject: [PATCH 0417/1863] Render followup agent+command in tooltip only (#205398) * Rename /newChat to /clear * Remove text() from the Stream * Render followup agent+command in tooltip only * Fix sticky slash command --- .../api/common/extHostChatAgents2.ts | 13 +++------ .../contrib/chat/browser/chat.contribution.ts | 8 +++--- .../contrib/chat/browser/chatFollowups.ts | 27 +++++++++++-------- .../vscode.proposed.chatAgents2.d.ts | 12 --------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 399ff60d03002..110b89a042b66 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -8,7 +8,7 @@ import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; @@ -77,11 +77,6 @@ class ChatAgentResponseStream { }; this._apiObject = { - text(value) { - throwIfDone(this.text); - this.markdown(new MarkdownString().appendText(value)); - return this; - }, markdown(value) { throwIfDone(this.markdown); const part = new extHostTypes.ChatResponseMarkdownPart(value); @@ -389,7 +384,7 @@ class ExtHostChatAgent { } return result .map(c => { - if ('repopulate2' in c) { + if ('isSticky2' in c) { checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); } @@ -397,9 +392,9 @@ class ExtHostChatAgent { name: c.name, description: c.description, followupPlaceholder: c.isSticky2?.placeholder, - shouldRepopulate: c.isSticky2?.isSticky ?? c.isSticky, + isSticky: c.isSticky2?.isSticky ?? c.isSticky, sampleRequest: c.sampleRequest - }; + } satisfies IChatAgentCommand; }); } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 52871880c7ced..c07a0b1afb68c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -195,7 +195,7 @@ class ChatAccessibleViewContribution extends Disposable { accessibleViewService.show({ id: AccessibleViewProviderId.Chat, verbositySettingKey: AccessibilityVerbositySettingId.Chat, - provideContent(): string { return responseContent; }, + provideContent(): string { return responseContent!; }, onClose() { verifiedWidget.reveal(focusedItem); if (chatInputFocused) { @@ -231,9 +231,9 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { ) { super(); this._store.add(slashCommandService.registerSlashCommand({ - command: 'newChat', - detail: nls.localize('newChat', "Start a new chat"), - sortText: 'z2_newChat', + command: 'clear', + detail: nls.localize('clear', "Start a new chat"), + sortText: 'z2_clear', executeImmediately: true }, async () => { commandService.executeCommand(ACTION_ID_NEW_CHAT); diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index ab3733a119288..7f0c4279c5d7f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -42,7 +42,20 @@ export class ChatFollowups extend return; } - const tooltip = 'tooltip' in followup ? followup.tooltip : undefined; + let tooltipPrefix = ''; + if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { + tooltipPrefix += `${chatAgentLeader}${followup.agentId} `; + if ('subCommand' in followup && followup.subCommand) { + tooltipPrefix += `${chatSubcommandLeader}${followup.subCommand} `; + } + } + + const baseTitle = followup.kind === 'reply' ? + (followup.title || followup.message) + : followup.title; + + const tooltip = tooltipPrefix + + ('tooltip' in followup && followup.tooltip || baseTitle); const button = this._register(new Button(container, { ...this.options, supportIcons: true, title: tooltip })); if (followup.kind === 'reply') { button.element.classList.add('interactive-followup-reply'); @@ -50,19 +63,11 @@ export class ChatFollowups extend button.element.classList.add('interactive-followup-command'); } button.element.ariaLabel = localize('followUpAriaLabel', "Follow up question: {0}", followup.title); - let prefix = ''; - if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent()?.id) { - prefix += `${chatAgentLeader}${followup.agentId} `; - if ('subCommand' in followup && followup.subCommand) { - prefix += `${chatSubcommandLeader}${followup.subCommand} `; - } - } - let label = ''; if (followup.kind === 'reply') { - label = '$(sparkle) ' + (followup.title || (prefix + followup.message)); + label = '$(sparkle) ' + baseTitle; } else { - label = followup.title; + label = baseTitle; } button.label = new MarkdownString(label, { supportThemeIcons: true }); diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts index 93938b8ba6819..783c1bcf78e06 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents2.d.ts @@ -356,17 +356,6 @@ declare module 'vscode' { export interface ChatAgentResponseStream { - /** - * Push a text part to this stream. Short-hand for - * `push(new ChatResponseTextPart(value))`. - * - * @see {@link ChatAgentResponseStream.push} - * @param value A plain text value. - * @returns This stream. - */ - // TODO@API remove? - text(value: string): ChatAgentResponseStream; - /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. @@ -375,7 +364,6 @@ declare module 'vscode' { * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ - // TODO@API NAME: content markdown(value: string | MarkdownString): ChatAgentResponseStream; /** From 025e11a803f6176ff2f0cb7de32576bc6d06124f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 16 Feb 2024 20:10:39 +0000 Subject: [PATCH 0418/1863] Fix indentation rules in test and validate the rules update. --- .../test/browser/indentation.test.ts | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 5748693d84b64..91666a05d1920 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -326,14 +326,7 @@ suite('Editor Contrib - Keep Indent On Paste', () => { }); }); - suite('Editor Contrib - Auto Dedent On Type', () => { - const rubyIndentationRules = { - // decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b/, - decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s/, - increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, - }; - let disposables: DisposableStore; setup(() => { @@ -355,17 +348,42 @@ suite('Editor Contrib - Auto Dedent On Type', () => { const languageService = instantiationService.get(ILanguageService); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); disposables.add(languageService.registerLanguage({ id: languageId })); - disposables.add(languageConfigurationService.register(languageId, { + const languageModel = languageConfigurationService.register(languageId, { brackets: [ ['{', '}'], ['[', ']'], ['(', ')'] ], - indentationRules: rubyIndentationRules, - })); + indentationRules: { + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b)/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }, + }); + + viewModel.type("def foo\n i", 'keyboard'); + viewModel.type("n", 'keyboard'); + // The 'in' triggers decreaseIndentPattern immediately, which is incorrect + assert.strictEqual(model.getValue(), "def foo\nin"); + languageModel.dispose(); - viewModel.type("def foo\n in"); - assert.strictEqual(model.getValue(), "def foo\n in"); + const improvedLanguageModel = languageConfigurationService.register(languageId, { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: { + decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s)/, + increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, + }, + }); + viewModel.model.setValue(""); + viewModel.type("def foo\n i"); + viewModel.type("n", 'keyboard'); + assert.strictEqual(model.getValue(), "def foo\n in"); + viewModel.type(" ", 'keyboard'); + assert.strictEqual(model.getValue(), "def foo\nin "); + improvedLanguageModel.dispose(); }); }); }); 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 0419/1863] 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 bba5a1f648ee1..ca7ebdca1a044 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 d9f725ed61d85..8deb70a073e2b 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 0c2dba5003528..0049340fadd5f 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 0000000000000..6e5f79e1cb1d6 --- /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 0420/1863] 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 537c7b8951f62..3aeb25cd2e27f 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 cd984a4d61e10..f374aee77e8a2 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 0421/1863] 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 0c577a9df8a63..72335480aee0a 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 a2349bc25c12d..5aefe757469be 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 890fb103292a5..c9631f7fa1d47 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 6e5f79e1cb1d6..afc24a1516310 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 8c146698482ef..938546894790d 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 f374aee77e8a2..7f8dc50524d0e 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 ca3bdbab8939c..c3409674e1239 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 2cc70c7eb076a..82f68ccf9f4a9 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 0422/1863] 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 5aefe757469be..89893d6d31e7b 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 c9631f7fa1d47..d330683af2e98 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 afc24a1516310..61d119986903f 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 938546894790d..798c0bf9774e0 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 7f8dc50524d0e..fc0777f9cde69 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 c3409674e1239..0226d6536f054 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 82f68ccf9f4a9..86b3fd8de33cc 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 0423/1863] 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 3aeb25cd2e27f..ca909ba98e0e9 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 0424/1863] 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 aeb85706fc621..fe9ddb2f8f009 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 6077d59e422b88df1ec5ce7b539bcf50dd7077f6 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 16 Feb 2024 12:46:29 -0800 Subject: [PATCH 0425/1863] add expression field to help access non-root variables (#205400) --- .../contrib/notebookVariables/notebookVariablesDataSource.ts | 1 + .../browser/contrib/notebookVariables/notebookVariablesView.ts | 3 ++- src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts index c233e02a8a013..b9cdadad7ff38 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesDataSource.ts @@ -21,6 +21,7 @@ export interface INotebookVariableElement { readonly name: string; readonly value: string; readonly type?: string; + readonly expression?: string; readonly language?: string; readonly indexedChildrenCount: number; readonly indexStart?: number; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts index df59bbeb0442d..1c3c61d25ea27 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookVariables/notebookVariablesView.ts @@ -34,7 +34,7 @@ import { ICellExecutionStateChangedEvent, IExecutionStateChangedEvent, INotebook import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export type contextMenuArg = { source?: string; type?: string; value?: string; language?: string; extensionId?: string }; +export type contextMenuArg = { source?: string; type?: string; value?: string; expression?: string; language?: string; extensionId?: string }; export class NotebookVariablesView extends ViewPane { @@ -111,6 +111,7 @@ export class NotebookVariablesView extends ViewPane { source: element.notebook.uri.toString(), value: element.value, type: element.type, + expression: element.expression, language: element.language, extensionId: element.extensionId }; diff --git a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts index 515818d3a1b23..883319ddcd9ac 100644 --- a/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookVariableProvider.d.ts @@ -39,6 +39,9 @@ declare module 'vscode' { */ value: string; + /** The code that represents how the variable would be accessed in the runtime environment */ + expression?: string; + /** The type of the variable's value */ type?: string; 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 0426/1863] 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 fc0777f9cde69..72eac94827496 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 0427/1863] 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 86b3fd8de33cc..0653f403de0d8 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 0428/1863] 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 602560618cc5c..64253f67db565 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 0049340fadd5f..44eabbf13f6b1 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 d330683af2e98..c29c7394c3f0d 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 61d119986903f..0000000000000 --- 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 0226d6536f054..47025af317b96 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 0429/1863] 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 c29c7394c3f0d..dad7747e7f12b 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 72eac94827496..04ff3b3a47208 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 0653f403de0d8..4e9bed0f3545a 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 33c385ba7ac77f9532223ea26abd200c71ef7125 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:21:46 -0600 Subject: [PATCH 0430/1863] add clarity to FindFiles2 (#205407) --- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index 37743a897da23..f861c7930cea6 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -10,8 +10,7 @@ declare module 'vscode' { /** * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will - * apply. + * will be matched against the file paths of resulting matches relative to their workspace. */ exclude?: GlobPattern; @@ -32,21 +31,24 @@ declare module 'vscode' { /** * Whether external files that exclude files, like .gitignore, should be respected. - * See the vscode setting `"search.useIgnoreFiles"`. + * Defaults to the value for `search.useIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useIgnoreFiles?: boolean; /** * Whether global files that exclude files, like .gitignore, should be respected. * Must set `useIgnoreFiles` to `true` to use this. - * See the vscode setting `"search.useGlobalIgnoreFiles"`. + * Defaults to the value for `search.useGlobalIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useGlobalIgnoreFiles?: boolean; /** * Whether files in parent directories that exclude files, like .gitignore, should be respected. * Must set `useIgnoreFiles` to `true` to use this. - * See the vscode setting `"search.useParentIgnoreFiles"`. + * Defaults to the value for `search.useParentIgnoreFiles` in settings. + * For more info, see the setting listed above. */ useParentIgnoreFiles?: boolean; @@ -77,7 +79,6 @@ declare module 'vscode' { * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} * to restrict the search results to a {@link WorkspaceFolder workspace folder}. * @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings). - * If enabled, any ignore files will take prescendence over any files found in the `filePattern` parameter. * @param token A token that can be used to signal cancellation to the underlying search engine. * @returns A thenable that resolves to an array of resource identifiers. Will return no results if no * {@link workspace.workspaceFolders workspace folders} are opened. From 27d1fc77f4fee8fdf442f9ae3f1175c1105760aa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 16 Feb 2024 16:52:57 -0600 Subject: [PATCH 0431/1863] 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 4e9bed0f3545a..180d51c15499a 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 0cbc11c26ca83176463af31ad0001ff9baabb677 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:19:41 -0800 Subject: [PATCH 0432/1863] use menu contributions in issue reporter (#205176) * merge changes to branch * groundwork for issue command * removed all the extra random imports * working flow and rerender/repopulate data * cleaner working version;' * cleanup * ensures that IPC is replied to in both cases * added data overwrite * cleanedup * more cleanup * cleanup part 2 * comment and make sure to clear uri * removed unused var * ensures we don't send ipc early before open reporter call * removed console logs * addressing some of the comments * move is running location * nicely working version * removed incorrect package.json location * re-enables dropdown, switch to extensionidentifier * removed tokey * fixes what happens when extensions change mid-await * use proposed contribIssueReporter * finally working in all scenarios * make sure we clear data well enough --- .../issue/issueReporterService.ts | 91 ++++++++++++++++--- src/vs/platform/actions/common/actions.ts | 1 + src/vs/platform/issue/common/issue.ts | 5 +- .../issue/electron-main/issueMainService.ts | 17 +++- .../actions/common/menusExtensionPoint.ts | 6 ++ .../common/extensionsApiProposals.ts | 1 + .../issue/electron-sandbox/issueService.ts | 38 +++++++- .../vscode.proposed.contribIssueReporter.d.ts | 7 ++ 8 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 82de2be993fd4..c8f4276e7f702 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -49,6 +49,9 @@ export class IssueReporter extends Disposable { private receivedPerformanceInfo = false; private shouldQueueSearch = false; private hasBeenSubmitted = false; + private openReporter = false; + private loadingExtensionData = false; + private changeExtension: string = ''; private delayedSubmit = new Delayer(300); private readonly previewButton!: Button; @@ -299,6 +302,16 @@ export class IssueReporter extends Disposable { } } + private async sendReporterMenu(extension: IssueReporterExtensionData): Promise { + try { + const data = await this.issueMainService.$sendReporterMenu(extension.id, extension.name); + return data; + } catch (e) { + console.error(e); + return undefined; + } + } + private setEventHandlers(): void { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); @@ -497,6 +510,10 @@ export class IssueReporter extends Disposable { return false; } + if (this.loadingExtensionData) { + return false; + } + if (issueType === IssueType.Bug && this.receivedSystemInfo) { return true; } @@ -820,6 +837,17 @@ export class IssueReporter extends Disposable { show(extensionDataBlock); } + // only if we know comes from the open reporter command + if (fileOnExtension && this.openReporter) { + (extensionDataTextArea as HTMLTextAreaElement).readOnly = true; + setTimeout(() => { + // delay to make sure from command or not + if (this.openReporter) { + show(extensionDataBlock); + } + }, 100); + } + if (issueType === IssueType.Bug) { if (!fileOnMarketplace) { show(blockContainer); @@ -1168,20 +1196,45 @@ export class IssueReporter extends Disposable { this.addEventListener('extension-selector', 'change', async (e: Event) => { this.clearExtensionData(); const selectedExtensionId = (e.target).value; + this.changeExtension = selectedExtensionId; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension) { - selectedExtension.data = undefined; - selectedExtension.uri = undefined; + if (selectedExtension && !this.loadingExtensionData) { + const iconElement = document.createElement('span'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + this.setLoading(iconElement); + const openReporterData = await this.sendReporterMenu(selectedExtension); + if (openReporterData && (this.changeExtension === selectedExtensionId)) { + this.removeLoading(iconElement, true); + this.configuration.data = openReporterData; + } else if (openReporterData && this.changeExtension !== selectedExtensionId) { + this.removeLoading(iconElement, this.openReporter); + } else { + this.removeLoading(iconElement); + // if not using command, should have no configuration data in fields we care about and check later. + this.configuration.data.issueBody = undefined; + this.configuration.data.data = undefined; + this.configuration.data.uri = undefined; + + // case when previous extension was opened from normal openIssueReporter command + selectedExtension.data = undefined; + selectedExtension.uri = undefined; + } + } + if (this.changeExtension === selectedExtensionId) { + // repopulates the fields with the new data given the selected extension. + this.updateExtensionStatus(matches[0]); + this.openReporter = false; } - this.updateExtensionStatus(matches[0]); } else { this.issueReporterModel.update({ selectedExtension: undefined }); this.clearSearchResults(); + this.clearExtensionData(); this.validateSelectedExtension(); + this.updateExtensionStatus(matches[0]); } }); } @@ -1193,12 +1246,15 @@ export class IssueReporter extends Disposable { private clearExtensionData(): void { this.issueReporterModel.update({ extensionData: undefined }); + this.configuration.data.issueBody = undefined; this.configuration.data.data = undefined; this.configuration.data.uri = undefined; } private async updateExtensionStatus(extension: IssueReporterExtensionData) { this.issueReporterModel.update({ selectedExtension: extension }); + + // uses this.configuuration.data to ensure that data is coming from `openReporter` command. const template = this.configuration.data.issueBody; if (template) { const descriptionTextArea = this.getElementById('description')!; @@ -1212,13 +1268,16 @@ export class IssueReporter extends Disposable { const data = this.configuration.data.data; if (data) { + this.issueReporterModel.update({ extensionData: data }); + extension.data = data; const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; show(extensionDataBlock); - this.issueReporterModel.update({ extensionData: data }); + this.renderBlocks(); } const uri = this.configuration.data.uri; if (uri) { + extension.uri = uri; this.updateIssueReporterUri(extension); } @@ -1227,7 +1286,6 @@ export class IssueReporter extends Disposable { const toActivate = await this.getReporterStatus(extension); extension.hasIssueDataProviders = toActivate[0]; extension.hasIssueUriRequestHandler = toActivate[1]; - this.renderBlocks(); } if (extension.hasIssueUriRequestHandler && extension.hasIssueDataProviders) { @@ -1273,13 +1331,12 @@ export class IssueReporter extends Disposable { this.setLoading(iconElement); await this.getIssueDataFromExtension(extension); this.removeLoading(iconElement); - } else { - this.validateSelectedExtension(); - this.issueReporterModel.update({ extensionData: extension.data ?? undefined }); - const title = (this.getElementById('issue-title')).value; - this.searchExtensionIssues(title); } + this.validateSelectedExtension(); + const title = (this.getElementById('issue-title')).value; + this.searchExtensionIssues(title); + this.updatePreviewButtonState(); this.renderBlocks(); } @@ -1296,6 +1353,10 @@ export class IssueReporter extends Disposable { return; } + if (this.loadingExtensionData) { + return; + } + const hasValidGitHubUrl = this.getExtensionGitHubUrl(); if (hasValidGitHubUrl || (extension.hasIssueUriRequestHandler && !extension.hasIssueDataProviders)) { this.previewButton.enabled = true; @@ -1308,6 +1369,8 @@ export class IssueReporter extends Disposable { private setLoading(element: HTMLElement) { // Show loading this.receivedExtensionData = false; + this.openReporter = true; + this.loadingExtensionData = true; this.updatePreviewButtonState(); const extensionDataCaption = this.getElementById('extension-id')!; @@ -1323,7 +1386,9 @@ export class IssueReporter extends Disposable { this.renderBlocks(); } - private removeLoading(element: HTMLElement) { + private removeLoading(element: HTMLElement, fromReporter: boolean = false) { + this.openReporter = fromReporter; + this.loadingExtensionData = false; this.updatePreviewButtonState(); const extensionDataCaption = this.getElementById('extension-id')!; @@ -1335,6 +1400,8 @@ export class IssueReporter extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); hideLoading.removeChild(element); + + this.renderBlocks(); } private setExtensionValidationMessage(): void { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 15c6a1dfe59ff..98cd2259f98f8 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -167,6 +167,7 @@ export class MenuId { static readonly InteractiveCellDelete = new MenuId('InteractiveCellDelete'); static readonly InteractiveCellExecute = new MenuId('InteractiveCellExecute'); static readonly InteractiveInputExecute = new MenuId('InteractiveInputExecute'); + static readonly IssueReporter = new MenuId('IssueReporter'); static readonly NotebookToolbar = new MenuId('NotebookToolbar'); static readonly NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); static readonly NotebookCellTitle = new MenuId('NotebookCellTitle'); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 76da6c0fee438..d1c4e29bb63b4 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -70,8 +70,8 @@ export interface IssueReporterData extends WindowData { restrictedMode: boolean; isUnsupported: boolean; githubAccessToken: string; - readonly issueTitle?: string; - readonly issueBody?: string; + issueTitle?: string; + issueBody?: string; data?: string; uri?: UriComponents; } @@ -138,5 +138,6 @@ export interface IIssueMainService { $getIssueReporterData(extensionId: string): Promise; $getIssueReporterTemplate(extensionId: string): Promise; $getReporterStatus(extensionId: string, extensionName: string): Promise; + $sendReporterMenu(extensionId: string, extensionName: string): Promise; $closeReporter(): Promise; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 3998b23ead912..8e00947b2eac4 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -179,7 +179,6 @@ export class IssueMainService implements IIssueMainService { this.issueReporterWindow.on('close', () => { this.issueReporterWindow = null; - issueReporterDisposables.dispose(); }); @@ -187,14 +186,13 @@ export class IssueMainService implements IIssueMainService { if (this.issueReporterWindow) { this.issueReporterWindow.close(); this.issueReporterWindow = null; - issueReporterDisposables.dispose(); } }); } } - if (this.issueReporterWindow) { + else if (this.issueReporterWindow) { this.focusWindow(this.issueReporterWindow); } } @@ -455,6 +453,19 @@ export class IssueMainService implements IIssueMainService { return (result ?? defaultResult) as boolean[]; } + + async $sendReporterMenu(extensionId: string, extensionName: string): Promise { + const window = this.issueReporterWindowCheck(); + const replyChannel = `vscode:triggerReporterMenu`; + const cts = new CancellationTokenSource(); + window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error('Error: Extension timed out waiting for menu response'); + cts.cancel(); + }); + return result as IssueReporterData | undefined; + } + async $closeReporter(): Promise { this.issueReporterWindow?.close(); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 3a88ed4a23f5f..31c61213f4d5d 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -321,6 +321,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.InteractiveCellTitle, description: localize('interactive.cell.title', "The contributed interactive cell title menu"), }, + { + key: 'issue/reporter', + id: MenuId.IssueReporter, + description: localize('issue.reporter', "The contributed issue reporter menu"), + proposed: 'contribIssueReporter' + }, { key: 'testing/item/context', id: MenuId.TestItem, diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 12ddfb110ec7d..53e7755722664 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({ contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', + contribIssueReporter: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', contribMergeEditorMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 8578bc63d354e..f91f39db09e86 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -10,7 +10,7 @@ import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType, ExtensionIdentifierSet } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -28,6 +28,8 @@ import { IIntegrityService } from 'vs/workbench/services/integrity/common/integr import { ILogService } from 'vs/platform/log/common/log'; import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { mainWindow } from 'vs/base/browser/window'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -35,6 +37,7 @@ export class NativeIssueService implements IWorkbenchIssueService { private readonly _handlers = new Map(); private readonly _providers = new Map(); private readonly _activationEventReader = new ImplicitActivationAwareReader(); + private extensionIdentifierSet: ExtensionIdentifierSet = new ExtensionIdentifierSet(); constructor( @IIssueMainService private readonly issueMainService: IIssueMainService, @@ -49,6 +52,8 @@ export class NativeIssueService implements IWorkbenchIssueService { @IIntegrityService private readonly integrityService: IIntegrityService, @IExtensionService private readonly extensionService: IExtensionService, @ILogService private readonly logService: ILogService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { ipcRenderer.on('vscode:triggerIssueUriRequestHandler', async (event: unknown, request: { replyChannel: string; extensionId: string }) => { const result = await this.getIssueReporterUri(request.extensionId, CancellationToken.None); @@ -82,6 +87,32 @@ export class NativeIssueService implements IWorkbenchIssueService { const result = [this._providers.has(extensionId.toLowerCase()), this._handlers.has(extensionId.toLowerCase())]; ipcRenderer.send('vscode:triggerReporterStatusResponse', result); }); + ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { + const extensionId = arg.extensionId; + + // creates menu from contributed + const menu = this.menuService.createMenu(MenuId.IssueReporter, this.contextKeyService); + + // render menu and dispose + const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); + actions.forEach(async action => { + try { + if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { + this.extensionIdentifierSet.add(extensionId); + await action.run(); + } + } catch (error) { + console.error(error); + } + }); + + if (this.extensionIdentifierSet.size === 0) { + // send undefined to indicate no action was taken + ipcRenderer.send('vscode:triggerReporterMenuResponse', undefined); + } + + menu.dispose(); + }); } async openReporter(dataOverrides: Partial = {}): Promise { @@ -154,6 +185,11 @@ export class NativeIssueService implements IWorkbenchIssueService { isUnsupported, githubAccessToken }, dataOverrides); + + if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { + ipcRenderer.send('vscode:triggerReporterMenuResponse', issueReporterData); + this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); + } return this.issueMainService.openReporter(issueReporterData); } diff --git a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts b/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts new file mode 100644 index 0000000000000..522d33344678a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `issue reporter`-submenu contribution point +// https://github.com/microsoft/vscode/issues/196863 From 588fb3856b796674ea2f1bfe09503e7a9d6268cc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sat, 17 Feb 2024 16:13:33 +0100 Subject: [PATCH 0433/1863] [Accessibility] Status bar items with no command can still have focus, but do not show it (fix #205388) (#205435) --- src/vs/workbench/browser/parts/statusbar/statusbarPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index e8a133fd4b4b7..ae4fd401456ef 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -591,7 +591,7 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { } /* Status bar item focus outline */ - .monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus-visible:not(.disabled) { + .monaco-workbench .part.statusbar > .items-container > .statusbar-item a:focus-visible { outline: 1px solid ${this.getColor(activeContrastBorder) ?? itemBorderColor}; outline-offset: ${borderColor ? '-2px' : '-1px'}; } From a8f73340be02966c3816a2f23cb7e446a3a7cb9b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:58:12 +0100 Subject: [PATCH 0434/1863] Fix for smoketest extension installation timeout (#205444) hover smoke test fix #204771 --- test/automation/src/extensions.ts | 2 +- test/automation/src/scm.ts | 8 ++++---- test/automation/src/search.ts | 2 +- test/automation/src/statusbar.ts | 2 +- test/smoke/src/areas/extensions/extensions.test.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index 41dc79d442da8..b092a2e8b4d1f 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -54,7 +54,7 @@ export class Extensions extends Viewlet { await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[data-extension-id="${id}"] .extension-list-item .monaco-action-bar .action-item:not(.disabled) .extension-action.install`); await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) .extension-action.uninstall`); if (waitUntilEnabled) { - await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled)[title="Disable this extension"]`); + await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) a[aria-label="Disable this extension"]`); } } } diff --git a/test/automation/src/scm.ts b/test/automation/src/scm.ts index 7186fee17ca1a..9f950f2b16a77 100644 --- a/test/automation/src/scm.ts +++ b/test/automation/src/scm.ts @@ -10,10 +10,10 @@ import { findElement, findElements, Code } from './code'; const VIEWLET = 'div[id="workbench.view.scm"]'; const SCM_INPUT = `${VIEWLET} .scm-editor textarea`; const SCM_RESOURCE = `${VIEWLET} .monaco-list-row .resource`; -const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Refresh"]`; -const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[title="Commit"]`; -const SCM_RESOURCE_CLICK = (name: string) => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .label-name`; -const SCM_RESOURCE_ACTION_CLICK = (name: string, actionName: string) => `${SCM_RESOURCE} .monaco-icon-label[title*="${name}"] .actions .action-label[title="${actionName}"]`; +const REFRESH_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Refresh"]`; +const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`; +const SCM_RESOURCE_CLICK = (name: string) => `${SCM_RESOURCE} .monaco-icon-label[aria-label*="${name}"] .label-name`; +const SCM_RESOURCE_ACTION_CLICK = (name: string, actionName: string) => `${SCM_RESOURCE} .monaco-icon-label[aria-label*="${name}"] .actions .action-label[aria-label="${actionName}"]`; interface Change { name: string; diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index 844003955e75f..567ec20fa915d 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -102,7 +102,7 @@ export class Search extends Viewlet { } async setReplaceText(text: string): Promise { - await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox textarea[title="Replace"]`, text); + await this.code.waitForSetValue(`${VIEWLET} .search-widget .replace-container .monaco-inputbox textarea[aria-label="Replace"]`, text); } async replaceFileMatch(filename: string, expectedText: string): Promise { diff --git a/test/automation/src/statusbar.ts b/test/automation/src/statusbar.ts index 86254146975c9..423a7585c04f0 100644 --- a/test/automation/src/statusbar.ts +++ b/test/automation/src/statusbar.ts @@ -35,7 +35,7 @@ export class StatusBar { } async waitForStatusbarText(title: string, text: string): Promise { - await this.code.waitForTextContent(`${this.mainSelector} .statusbar-item[title="${title}"]`, text); + await this.code.waitForTextContent(`${this.mainSelector} .statusbar-item[aria-label="${title}"]`, text); } private getSelector(element: StatusBarElement): string { diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index bc65a8631c44d..c78cbe8708973 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -7,7 +7,7 @@ import { Application, Logger } from '../../../../automation'; import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { - describe.skip('Extensions', () => { + describe('Extensions', () => { // Shared before/after handling installAllHandlers(logger); From cdfa3491ce6c6cdd581b376df9bb08f66f74bc62 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 21:47:34 +0100 Subject: [PATCH 0435/1863] rename suggestions: don't focus unless there're already elements --- .../contrib/rename/browser/renameInputField.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 742c5a72cbaca..5db0fd648bf41 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -254,6 +254,9 @@ export class RenameInputField implements IContentWidget { } } + /** + * @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult} + */ getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -463,7 +466,10 @@ export class CandidatesView { this._listWidget.rerender(); } - public focusNext() { + public focusNext(): void { + if (this._listWidget.length === 0) { + return; + } if (this._listWidget.isDOMFocused()) { this._listWidget.focusNext(); } else { @@ -476,7 +482,10 @@ export class CandidatesView { /** * @returns true if focus is moved to previous element */ - public focusPrevious() { + public focusPrevious(): boolean { + if (this._listWidget.length === 0) { + return false; + } this._listWidget.domFocus(); const focusedIx = this._listWidget.getFocus()[0]; if (focusedIx !== 0) { From 89d33f932bd862024b7d168870f33b5bf3804583 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 22:21:57 +0100 Subject: [PATCH 0436/1863] rename suggestions: use `Promise.allSettled` so that if one provider throws, we still get names from other providers --- .../editor/contrib/rename/browser/rename.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 6b9c5285a27cf..8d8035b5cf743 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { alert } from 'vs/base/browser/ui/aria/aria'; +import * as arrays from 'vs/base/common/arrays'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; +import { assertType, isDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; @@ -210,11 +211,20 @@ class RenameController implements IEditorContribution { const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled - const newNameCandidates = Promise.all( - this._languageFeaturesService.newSymbolNamesProvider - .all(model) - .map(provider => provider.provideNewSymbolNames(model, loc.range, cts2.token)) // TODO@ulugbekna: make sure this works regardless if the result is then-able - ).then((candidates) => candidates.filter((c): c is NewSymbolName[] => !!c).flat()); + const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); + const newSymbolNames = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, cts2.token)); + const newNameCandidates = Promise.allSettled(newSymbolNames).then(namesListResults => { + if (cts2.token.isCancellationRequested) { + return []; + } + const newNames: NewSymbolName[] = []; + for (const namesListResult of namesListResults) { + if (namesListResult.status === 'fulfilled' && isDefined(namesListResult.value)) { + arrays.pushMany(newNames, namesListResult.value); + } + } + return newNames; + }); const selection = this.editor.getSelection(); let selectionStart = 0; From 90e788d1fd9e5b627dbe8959444da08b5b064458 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sat, 17 Feb 2024 22:54:42 +0100 Subject: [PATCH 0437/1863] rename suggestions: fix old candidates being shown when re-triggering/re-opening rename widget in the same document position quickly --- .../editor/contrib/rename/browser/rename.ts | 24 ++++-------- .../rename/browser/renameInputField.ts | 37 ++++++++++++------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 8d8035b5cf743..ae1f9b06e113a 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { alert } from 'vs/base/browser/ui/aria/aria'; -import * as arrays from 'vs/base/common/arrays'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType, isDefined } from 'vs/base/common/types'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions'; @@ -21,7 +20,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; -import { NewSymbolName, Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; +import { Rejection, RenameLocation, RenameProvider, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; @@ -211,20 +210,11 @@ class RenameController implements IEditorContribution { const cts2 = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range, this._cts.token); const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled + + const renameCandidatesCts = new CancellationTokenSource(cts2.token); const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); - const newSymbolNames = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, cts2.token)); - const newNameCandidates = Promise.allSettled(newSymbolNames).then(namesListResults => { - if (cts2.token.isCancellationRequested) { - return []; - } - const newNames: NewSymbolName[] = []; - for (const namesListResult of namesListResults) { - if (namesListResult.status === 'fulfilled' && isDefined(namesListResult.value)) { - arrays.pushMany(newNames, namesListResult.value); - } - } - return newNames; - }); + // TODO@ulugbekna: providers should get timeout token (use createTimeoutCancellation(x)) + const newSymbolNameProvidersResults = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, renameCandidatesCts.token)); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -236,7 +226,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newNameCandidates, cts2.token); + const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 5db0fd648bf41..038fb1fd1dfe9 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -8,10 +8,11 @@ import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import * as arrays from 'vs/base/common/arrays'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { assertType, isDefined } from 'vs/base/common/types'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -20,7 +21,7 @@ import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { NewSymbolName, NewSymbolNameTag } from 'vs/editor/common/languages'; +import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -257,7 +258,7 @@ export class RenameInputField implements IContentWidget { /** * @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult} */ - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: Promise, token: CancellationToken): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: ProviderResult[], cts: CancellationTokenSource): Promise { this._domNode!.classList.toggle('preview', supportPreview); @@ -269,7 +270,9 @@ export class RenameInputField implements IContentWidget { const disposeOnDone = new DisposableStore(); - candidates.then(candidates => this._showRenameCandidates(candidates, value, token)); + disposeOnDone.add(toDisposable(() => cts.dispose(true))); // @ulugbekna: this may result in `this.cancelInput` being called twice, but it should be safe since we set it to undefined after 1st call + + this._updateRenameCandidates(candidates, value, cts.token); return new Promise(resolve => { @@ -301,7 +304,7 @@ export class RenameInputField implements IContentWidget { }); }; - disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); + disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true))); if (!_sticky) { disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); } @@ -328,21 +331,29 @@ export class RenameInputField implements IContentWidget { }, 100); } - private _showRenameCandidates(candidates: NewSymbolName[], currentName: string, token: CancellationToken): void { - if (token.isCancellationRequested) { + private async _updateRenameCandidates(candidates: ProviderResult[], currentName: string, token: CancellationToken) { + const namesListResults = await raceCancellation(Promise.allSettled(candidates), token); + + if (namesListResults === undefined) { return; } + const newNames = namesListResults.flatMap(namesListResult => + namesListResult.status === 'fulfilled' && isDefined(namesListResult.value) + ? namesListResult.value + : [] + ); + // deduplicate and filter out the current value - candidates = arrays.distinct(candidates, candidate => candidate.newSymbolName); - candidates = candidates.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + const distinctNames = arrays.distinct(newNames, v => v.newSymbolName); + const validDistinctNames = distinctNames.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); - if (candidates.length < 1) { + if (validDistinctNames.length < 1) { return; } // show the candidates - this._candidatesView!.setCandidates(candidates); + this._candidatesView!.setCandidates(validDistinctNames); // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates this._editor.layoutContentWidget(this); From 6e539e3b7bed24d888ee56bb56c23cafb35b855a Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sun, 18 Feb 2024 15:11:07 +0100 Subject: [PATCH 0438/1863] rename suggestions: fix: allow clicking on a rename candidate with a mouse fixes https://github.com/microsoft/vscode/issues/205376 --- .../rename/browser/renameInputField.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 038fb1fd1dfe9..106d866178acc 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -110,7 +110,10 @@ export class RenameInputField implements IContentWidget { this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - this._candidatesView = new CandidatesView(this._domNode, { fontInfo: this._editor.getOption(EditorOption.fontInfo) }); + this._candidatesView = new CandidatesView(this._domNode, { + fontInfo: this._editor.getOption(EditorOption.fontInfo), + onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now + }); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -374,7 +377,7 @@ export class CandidatesView { private _lineHeight: number; private _availableHeight: number; - constructor(parent: HTMLElement, opts: { fontInfo: FontInfo }) { + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo; onSelectionChange: () => void }) { this._availableHeight = 0; @@ -426,6 +429,12 @@ export class CandidatesView { } ); + this._listWidget.onDidChangeSelection(e => { + if (e.elements.length > 0) { + opts.onSelectionChange(); + } + }); + this._listWidget.style(defaultListStyles); } @@ -464,7 +473,18 @@ export class CandidatesView { } public get focusedCandidate(): string | undefined { - return this._listWidget.isDOMFocused() ? this._listWidget.getFocusedElements()[0].newSymbolName : undefined; + if (this._listWidget.length === 0) { + return; + } + const selectedElement = this._listWidget.getSelectedElements()[0]; + if (selectedElement !== undefined) { + return selectedElement.newSymbolName; + } + const focusedElement = this._listWidget.getFocusedElements()[0]; + if (focusedElement !== undefined) { + return focusedElement.newSymbolName; + } + return; } public updateFont(fontInfo: FontInfo): void { From b48ad70a420c8a49f64c605c44eb74bf60eaf676 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 18 Feb 2024 17:41:26 +0000 Subject: [PATCH 0439/1863] Allow https URIs as chat references (#205482) Fix #203822 --- .../contrib/chat/browser/chatListRenderer.ts | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 8ab85eb1297b6..609da660c84a1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -24,7 +24,7 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { marked } from 'vs/base/common/marked/marked'; -import { FileAccess, Schemas } from 'vs/base/common/network'; +import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { equalsIgnoreCase } from 'vs/base/common/strings'; @@ -68,7 +68,6 @@ 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 { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const $ = dom.$; @@ -143,7 +142,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { - this.editorService.openEditor({ - resource: 'uri' in e.element.reference ? e.element.reference.uri : e.element.reference, - options: { - ...e.editorOptions, - ...{ - selection: 'range' in e.element.reference ? e.element.reference.range : undefined + this.openerService.open( + 'uri' in e.element.reference ? e.element.reference.uri : e.element.reference, + { + fromUserGesture: true, + editorOptions: { + ...e.editorOptions, + ...{ + selection: 'range' in e.element.reference ? e.element.reference.range : undefined + } } - } - }); + }); } })); listDisposables.add(list.onContextMenu((e) => { @@ -1204,7 +1204,7 @@ class ContentReferencesListPool extends Disposable { 'ChatListRenderer', container, new ContentReferencesListDelegate(), - [new ContentReferencesListRenderer(resourceLabels)], + [this.instantiationService.createInstance(ContentReferencesListRenderer, resourceLabels)], { alwaysConsumeMouseWheel: false, accessibilityProvider: { @@ -1256,7 +1256,9 @@ class ContentReferencesListRenderer implements IListRenderer Date: Sun, 18 Feb 2024 21:40:06 +0100 Subject: [PATCH 0440/1863] rename controller & action: add tracing --- .../editor/contrib/rename/browser/rename.ts | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index ae1f9b06e113a..cdcadcf58f7d7 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -160,12 +160,15 @@ class RenameController implements IEditorContribution { async run(): Promise { + const trace = this._logService.trace.bind(this._logService, '[rename]'); + // set up cancellation token to prevent reentrant rename, this // is the parent to the resolve- and rename-tokens this._cts.dispose(true); this._cts = new CancellationTokenSource(); if (!this.editor.hasModel()) { + trace('editor has no model'); return undefined; } @@ -173,6 +176,7 @@ class RenameController implements IEditorContribution { const skeleton = new RenameSkeleton(this.editor.getModel(), position, this._languageFeaturesService.renameProvider); if (!skeleton.hasProvider()) { + trace('skeleton has no provider'); return undefined; } @@ -181,11 +185,13 @@ class RenameController implements IEditorContribution { let loc: RenameLocation & Rejection | undefined; try { + trace('resolving rename location'); const resolveLocationOperation = skeleton.resolveRenameLocation(cts1.token); this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; - + trace('resolved rename location'); } catch (e) { + trace('resolve rename location failed', JSON.stringify(e, null, '\t')); MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); return undefined; @@ -194,15 +200,18 @@ class RenameController implements IEditorContribution { } if (!loc) { + trace('returning early - no loc'); return undefined; } if (loc.rejectReason) { + trace(`returning early - rejected with reason: ${loc.rejectReason}`, loc.rejectReason); MessageController.get(this.editor)?.showMessage(loc.rejectReason, position); return undefined; } if (cts1.token.isCancellationRequested) { + trace('returning early - cts1 cancelled'); return undefined; } @@ -215,6 +224,7 @@ class RenameController implements IEditorContribution { const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model); // TODO@ulugbekna: providers should get timeout token (use createTimeoutCancellation(x)) const newSymbolNameProvidersResults = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, renameCandidatesCts.token)); + trace(`requested new symbol names from ${newSymbolNamesProviders.length} providers`); const selection = this.editor.getSelection(); let selectionStart = 0; @@ -225,11 +235,14 @@ class RenameController implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } + trace('creating rename input field and awaiting its result'); const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); + trace('received response from rename input field'); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { + trace(`returning early - rename input field response - ${inputFieldResult}`); if (inputFieldResult) { this.editor.focus(); } @@ -239,13 +252,20 @@ class RenameController implements IEditorContribution { this.editor.focus(); + trace('requesting rename edits'); const renameOperation = raceCancellation(skeleton.provideRenameEdits(inputFieldResult.newName, cts2.token), cts2.token).then(async renameResult => { - if (!renameResult || !this.editor.hasModel()) { + if (!renameResult) { + trace('returning early - no rename edits result'); + return; + } + if (!this.editor.hasModel()) { + trace('returning early - no model after rename edits are provided'); return; } if (renameResult.rejectReason) { + trace(`returning early - rejected with reason: ${renameResult.rejectReason}`); this._notificationService.info(renameResult.rejectReason); return; } @@ -253,6 +273,8 @@ class RenameController implements IEditorContribution { // collapse selection to active end this.editor.setSelection(Range.fromPositions(this.editor.getSelection().getPosition())); + trace('applying edits'); + this._bulkEditService.apply(renameResult, { editor: this.editor, showPreview: inputFieldResult.wantsPreview, @@ -261,15 +283,19 @@ class RenameController implements IEditorContribution { quotableLabel: nls.localize('quotableLabel', "Renaming {0} to {1}", loc?.text, inputFieldResult.newName), respectAutoSaveConfig: true }).then(result => { + trace('edits applied'); if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, inputFieldResult.newName, result.ariaSummary)); } }).catch(err => { + trace(`error when applying edits ${JSON.stringify(err, null, '\t')}`); this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits")); this._logService.error(err); }); }, err => { + trace('error when providing rename edits', JSON.stringify(err, null, '\t')); + this._notificationService.error(nls.localize('rename.failed', "Rename failed to compute edits")); this._logService.error(err); @@ -277,6 +303,8 @@ class RenameController implements IEditorContribution { cts2.dispose(); }); + trace('returning rename operation'); + this._progressService.showWhile(renameOperation, 250); return renameOperation; @@ -342,10 +370,15 @@ export class RenameAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const logService = accessor.get(ILogService); + const controller = RenameController.get(editor); + if (controller) { + logService.trace('[RenameAction] got controller, running...'); return controller.run(); } + logService.trace('[RenameAction] returning early - controller missing'); return Promise.resolve(); } } From 227096140942ce2b08a2358d55b6f28fa1dce573 Mon Sep 17 00:00:00 2001 From: NriotHrreion Date: Mon, 19 Feb 2024 09:27:00 +0800 Subject: [PATCH 0441/1863] fix #205353 --- src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts index d6f6249675c3a..8b4814a92c9f3 100644 --- a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts +++ b/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts @@ -331,10 +331,7 @@ export class HoverWidget extends Widget implements IHoverWidget { }; const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e)); - const top = Math.min(...targetBounds.map(e => e.top)); - const right = Math.max(...targetBounds.map(e => e.right)); - const bottom = Math.max(...targetBounds.map(e => e.bottom)); - const left = Math.min(...targetBounds.map(e => e.left)); + const { top, right, bottom, left } = targetBounds[0]; const width = right - left; const height = bottom - top; From 3c1ceb6a1f464f2d2299683e88510346ad33a2b6 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:50:01 -0800 Subject: [PATCH 0442/1863] issue reporter selection handling, adds `extensionId` to ipc (#205507) * fixed IPC handling * IT ALL WORKS NOW * fix quotes --- .../issue/issueReporterService.ts | 55 +++++++++++-------- .../issue/electron-main/issueMainService.ts | 4 +- .../issue/electron-sandbox/issueService.ts | 7 +-- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index c8f4276e7f702..6bfb60db1f70d 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -51,7 +51,7 @@ export class IssueReporter extends Disposable { private hasBeenSubmitted = false; private openReporter = false; private loadingExtensionData = false; - private changeExtension: string = ''; + private selectedExtension = ''; private delayedSubmit = new Delayer(300); private readonly previewButton!: Button; @@ -1196,45 +1196,48 @@ export class IssueReporter extends Disposable { this.addEventListener('extension-selector', 'change', async (e: Event) => { this.clearExtensionData(); const selectedExtensionId = (e.target).value; - this.changeExtension = selectedExtensionId; + this.selectedExtension = selectedExtensionId; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); const selectedExtension = this.issueReporterModel.getData().selectedExtension; - if (selectedExtension && !this.loadingExtensionData) { + if (selectedExtension) { const iconElement = document.createElement('span'); iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); this.setLoading(iconElement); const openReporterData = await this.sendReporterMenu(selectedExtension); - if (openReporterData && (this.changeExtension === selectedExtensionId)) { - this.removeLoading(iconElement, true); - this.configuration.data = openReporterData; - } else if (openReporterData && this.changeExtension !== selectedExtensionId) { - this.removeLoading(iconElement, this.openReporter); - } else { + if (openReporterData) { + if (this.selectedExtension === selectedExtensionId) { + this.removeLoading(iconElement, true); + this.configuration.data = openReporterData; + } else if (this.selectedExtension !== selectedExtensionId) { + } + } + else { + if (!this.loadingExtensionData) { + iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.loading), 'codicon-modifier-spin'); + } this.removeLoading(iconElement); // if not using command, should have no configuration data in fields we care about and check later. - this.configuration.data.issueBody = undefined; - this.configuration.data.data = undefined; - this.configuration.data.uri = undefined; + this.clearExtensionData(); // case when previous extension was opened from normal openIssueReporter command selectedExtension.data = undefined; selectedExtension.uri = undefined; } - } - if (this.changeExtension === selectedExtensionId) { - // repopulates the fields with the new data given the selected extension. + if (this.selectedExtension === selectedExtensionId) { + // repopulates the fields with the new data given the selected extension. + this.updateExtensionStatus(matches[0]); + this.openReporter = false; + } + } else { + this.issueReporterModel.update({ selectedExtension: undefined }); + this.clearSearchResults(); + this.clearExtensionData(); + this.validateSelectedExtension(); this.updateExtensionStatus(matches[0]); - this.openReporter = false; } - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.clearExtensionData(); - this.validateSelectedExtension(); - this.updateExtensionStatus(matches[0]); } }); } @@ -1381,6 +1384,9 @@ export class IssueReporter extends Disposable { const showLoading = this.getElementById('ext-loading')!; show(showLoading); + while (showLoading.firstChild) { + showLoading.removeChild(showLoading.firstChild); + } showLoading.append(element); this.renderBlocks(); @@ -1399,8 +1405,9 @@ export class IssueReporter extends Disposable { const hideLoading = this.getElementById('ext-loading')!; hide(hideLoading); - hideLoading.removeChild(element); - + if (hideLoading.firstChild) { + hideLoading.removeChild(element); + } this.renderBlocks(); } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 8e00947b2eac4..32f2be6af4533 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -459,8 +459,8 @@ export class IssueMainService implements IIssueMainService { const replyChannel = `vscode:triggerReporterMenu`; const cts = new CancellationTokenSource(); window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error('Error: Extension timed out waiting for menu response'); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse:${extensionId}', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error('Error: Extension ${extensionId} timed out waiting for menu response'); cts.cancel(); }); return result as IssueReporterData | undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index f91f39db09e86..039ce81f5a94b 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -106,11 +106,10 @@ export class NativeIssueService implements IWorkbenchIssueService { } }); - if (this.extensionIdentifierSet.size === 0) { + if (!this.extensionIdentifierSet.has(extensionId)) { // send undefined to indicate no action was taken - ipcRenderer.send('vscode:triggerReporterMenuResponse', undefined); + ipcRenderer.send('vscode:triggerReporterMenuResponse:${extensionId}', undefined); } - menu.dispose(); }); } @@ -187,7 +186,7 @@ export class NativeIssueService implements IWorkbenchIssueService { }, dataOverrides); if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { - ipcRenderer.send('vscode:triggerReporterMenuResponse', issueReporterData); + ipcRenderer.send('vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}', issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); } return this.issueMainService.openReporter(issueReporterData); From 378b4c969b89d3ee42ca0cc07eb91d2cd1bfbe7c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 19 Feb 2024 00:17:54 -0800 Subject: [PATCH 0443/1863] use vsce-sign module from npm (#205511) * update distro * use vsce-sign module * update distro * update distro --- build/.moduleignore | 9 +++++---- build/gulpfile.vscode.js | 2 +- package.json | 2 +- .../node/extensionSignatureVerificationService.ts | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/build/.moduleignore b/build/.moduleignore index 87dafa92b0f45..e40224556c62d 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -63,10 +63,11 @@ native-watchdog/build/** native-watchdog/src/** !native-watchdog/build/Release/*.node -node-vsce-sign/** -!node-vsce-sign/src/main.js -!node-vsce-sign/package.json -!node-vsce-sign/bin/** +@vscode/vsce-sign/** +!@vscode/vsce-sign/src/main.d.ts +!@vscode/vsce-sign/src/main.js +!@vscode/vsce-sign/package.json +!@vscode/vsce-sign/bin/** windows-foreground-love/binding.gyp windows-foreground-love/build/** diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bfd5c896e2f93..e1507e0424f8f 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -281,7 +281,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op '**/node-pty/lib/worker/conoutSocketWorker.js', '**/node-pty/lib/shared/conout.js', '**/*.wasm', - '**/node-vsce-sign/bin/*', + '**/@vscode/vsce-sign/bin/*', ], 'node_modules.asar')); let all = es.merge( diff --git a/package.json b/package.json index dcd8d8916b33f..78f245d387a49 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "660e1818ca4ddccb41c09b6a44360c278ae1610e", + "distro": "ca316c089fc5ea2836f210e13cf89d4bd52a3309", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts index 4f33aa1f48e77..66d1ffc28e20a 100644 --- a/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts +++ b/src/vs/platform/extensionManagement/node/extensionSignatureVerificationService.ts @@ -54,7 +54,7 @@ export class ExtensionSignatureVerificationService implements IExtensionSignatur if (!this.moduleLoadingPromise) { this.moduleLoadingPromise = new Promise( (resolve, reject) => require( - ['node-vsce-sign'], + ['@vscode/vsce-sign'], async (obj) => { const instance = obj; From a7844a63496b68384b686d733703d33a601442bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 11:24:18 +0100 Subject: [PATCH 0444/1863] Other files don't open when launched with an unsaved workspace (fix #205453) (#205517) * Other files don't open when launched with an unsaved workspace (fix #205453) * polish --- .../contrib/workspace/browser/workspace.contribution.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 9bad5ae61a302..37fb5d347ddf9 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -103,11 +103,12 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben return !isSingleFolderWorkspaceIdentifier(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); } - private async registerListeners(): Promise { - await this.workspaceTrustManagementService.workspaceResolved; + private registerListeners(): void { // Open files trust request this._register(this.workspaceTrustRequestService.onDidInitiateOpenFilesTrustRequest(async () => { + await this.workspaceTrustManagementService.workspaceResolved; + // Details const markdownDetails = [ this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY ? @@ -148,6 +149,8 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben // Workspace trust request this._register(this.workspaceTrustRequestService.onDidInitiateWorkspaceTrustRequest(async requestOptions => { + await this.workspaceTrustManagementService.workspaceResolved; + // Title const message = this.useWorkspaceLanguage ? localize('workspaceTrust', "Do you trust the authors of the files in this workspace?") : From 2819e44d2cd729a447d6b5b8b7f002e2228e216c Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 19 Feb 2024 11:40:19 +0100 Subject: [PATCH 0445/1863] update scrollbars when dimensions change --- .../components/accessibleDiffViewer.css | 38 ++++++++++--------- .../components/accessibleDiffViewer.ts | 8 +++- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css index cd20cfb12f304..640909467f4bd 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.css @@ -3,53 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-diff-editor .diff-review-line-number { - text-align: right; - display: inline-block; - color: var(--vscode-editorLineNumber-foreground); +.monaco-component.diff-review { + user-select: none; + -webkit-user-select: none; + z-index: 99; } .monaco-diff-editor .diff-review { position: absolute; - user-select: none; - -webkit-user-select: none; - z-index: 99; + +} + +.monaco-component.diff-review .diff-review-line-number { + text-align: right; + display: inline-block; + color: var(--vscode-editorLineNumber-foreground); } -.monaco-diff-editor .diff-review-summary { +.monaco-component.diff-review .diff-review-summary { padding-left: 10px; } -.monaco-diff-editor .diff-review-shadow { +.monaco-component.diff-review .diff-review-shadow { position: absolute; box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset; } -.monaco-diff-editor .diff-review-row { +.monaco-component.diff-review .diff-review-row { white-space: pre; } -.monaco-diff-editor .diff-review-table { +.monaco-component.diff-review .diff-review-table { display: table; min-width: 100%; } -.monaco-diff-editor .diff-review-row { +.monaco-component.diff-review .diff-review-row { display: table-row; width: 100%; } -.monaco-diff-editor .diff-review-spacer { +.monaco-component.diff-review .diff-review-spacer { display: inline-block; width: 10px; vertical-align: middle; } -.monaco-diff-editor .diff-review-spacer > .codicon { +.monaco-component.diff-review .diff-review-spacer > .codicon { font-size: 9px !important; } -.monaco-diff-editor .diff-review-actions { +.monaco-component.diff-review .diff-review-actions { display: inline-block; position: absolute; right: 10px; @@ -57,12 +61,12 @@ z-index: 100; } -.monaco-diff-editor .diff-review-actions .action-label { +.monaco-component.diff-review .diff-review-actions .action-label { width: 16px; height: 16px; margin: 2px 0; } -.monaco-diff-editor .revertButton { +.monaco-component.diff-review .revertButton { cursor: pointer; } diff --git a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts index 3d4e7ef30676e..3c27606e5d69e 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer.ts @@ -354,7 +354,7 @@ class View extends Disposable { super(); this.domNode = this._element; - this.domNode.className = 'diff-review monaco-editor-background'; + this.domNode.className = 'monaco-component diff-review monaco-editor-background'; const actionBarContainer = document.createElement('div'); actionBarContainer.className = 'diff-review-actions'; @@ -381,6 +381,12 @@ class View extends Disposable { this._scrollbar = this._register(new DomScrollableElement(this._content, {})); reset(this.domNode, this._scrollbar.getDomNode(), actionBarContainer); + this._register(autorun(r => { + this._height.read(r); + this._width.read(r); + this._scrollbar.scanDomNode(); + })); + this._register(toDisposable(() => { reset(this.domNode); })); this._register(applyStyle(this.domNode, { width: this._width, height: this._height })); From bc525c0f3386f0d3e09fbad83506d841a282d442 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 19 Feb 2024 11:53:12 +0100 Subject: [PATCH 0446/1863] Update distro hash in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78f245d387a49..e413eef05b284 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "ca316c089fc5ea2836f210e13cf89d4bd52a3309", + "distro": "664b4b796ea2343e71889a507e125feb14390bdf", "author": { "name": "Microsoft Corporation" }, From 3ee2bef56a5fd06b6c4858253493781f50d5797a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:54:09 +0100 Subject: [PATCH 0447/1863] Fix custom hover closing context view for breadcrumbs (#205518) Fix custom hover closes context view for breadcrumbs --- .../base/browser/ui/iconLabel/iconHoverDelegate.ts | 1 + src/vs/base/browser/ui/iconLabel/iconLabel.ts | 12 ++++++++---- src/vs/platform/hover/browser/hover.ts | 9 +++++++++ .../browser/parts/editor/breadcrumbsControl.ts | 4 ++-- .../browser/parts/editor/breadcrumbsPicker.ts | 7 +++++-- .../browser/outline/documentSymbolsTree.ts | 3 ++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index d6d9096b5f1de..f0209856dc318 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -62,6 +62,7 @@ export interface IHoverDelegate { 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/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 6a7edc12dcfd0..c9d62a9bc4e56 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -7,7 +7,7 @@ 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 } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMatch } from 'vs/base/common/filters'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; @@ -187,9 +187,13 @@ export class IconLabel extends Disposable { return; } - const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); - if (hoverDisposable) { - this.customHovers.set(htmlElement, hoverDisposable); + if (this.hoverDelegate.showNativeHover) { + setupNativeHover(htmlElement, tooltip); + } else { + const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip); + if (hoverDisposable) { + this.customHovers.set(htmlElement, hoverDisposable); + } } } diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 5bbd3ff00b978..750adf61b466e 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -283,3 +283,12 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate } } } + +// TODO@benibenj remove this, only temp fix for contextviews +export const nativeHoverDelegate: IHoverDelegate = { + showHover: function (): IHoverWidget | undefined { + throw new Error('Native hover function not implemented.'); + }, + delay: 0, + showNativeHover: true +}; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index a3b752ff1f2fa..4d9e3906b4e87 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 { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; class OutlineItem extends BreadcrumbsItem { @@ -230,7 +230,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._hoverDelegate = getDefaultHoverDelegate('mouse'); + this._hoverDelegate = nativeHoverDelegate; 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 9ca0b4310a585..26f77202d53e9 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -31,6 +31,8 @@ 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; @@ -213,12 +215,13 @@ class FileRenderer implements ITreeRenderer, index: number, templateData: IResourceLabel): void { @@ -374,7 +377,7 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { 'BreadcrumbsFilePicker', container, new FileVirtualDelegate(), - [this._instantiationService.createInstance(FileRenderer, labels)], + [this._instantiationService.createInstance(FileRenderer, labels, nativeHoverDelegate)], 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 450c690d6a863..4e8761ffb1d0b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -26,6 +26,7 @@ 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; @@ -125,7 +126,7 @@ export class DocumentSymbolRenderer implements ITreeRenderer Date: Mon, 19 Feb 2024 12:19:57 +0100 Subject: [PATCH 0448/1863] Close custom hover on escape (#205527) close custom hover on escape --- src/vs/platform/hover/browser/hover.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 750adf61b466e..93974adc1a9fb 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +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 { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { addStandardDisposableListener } from 'vs/base/browser/dom'; +import { KeyCode } from 'vs/base/common/keyCodes'; export const IHoverService = createDecorator('hoverService'); @@ -245,6 +247,8 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this._delay; } + private hoverDisposables = this._register(new DisposableStore()); + constructor( public readonly placement: 'mouse' | 'element', private readonly instantHover: boolean, @@ -264,6 +268,18 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined { const overrideOptions = typeof this.overrideOptions === 'function' ? this.overrideOptions(options, focus) : this.overrideOptions; + + // close hover on escape + this.hoverDisposables.clear(); + const targets = options.target instanceof HTMLElement ? [options.target] : options.target.targetElements; + for (const target of targets) { + this.hoverDisposables.add(addStandardDisposableListener(target, 'keydown', (e) => { + if (e.equals(KeyCode.Escape)) { + this.hoverService.hideHover(); + } + })); + } + return this.hoverService.showHover({ ...options, persistence: { @@ -278,6 +294,7 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate } onDidHideHover(): void { + this.hoverDisposables.clear(); if (this.instantHover) { this.lastHoverHideTime = Date.now(); } From 735d80e0e243d0e8e4b5bb9b63bfb3f8673a9d74 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 19 Feb 2024 04:09:35 -0800 Subject: [PATCH 0449/1863] revert early disposal of code actions (#205508) revert early disposal of actinos --- .../codeAction/browser/codeActionController.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index a29f4334e1fdc..5511dde958bc6 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -112,12 +112,7 @@ export class CodeActionController extends Disposable implements IEditorContribut command.arguments[0] = { ...command.arguments[0], autoSend: false }; } } - try { - this._lightBulbWidget.value?.hide(); - await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); - } finally { - actions.dispose(); - } + await this._applyCodeAction(actionItem, false, false, ApplyCodeActionReason.FromAILightbulb); return; } await this.showCodeActionList(actions, at, { includeDisabledActions: false, fromLightbulb: true }); @@ -284,11 +279,7 @@ export class CodeActionController extends Disposable implements IEditorContribut const delegate: IActionListDelegate = { onSelect: async (action: CodeActionItem, preview?: boolean) => { - try { - await this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); - } finally { - actions.dispose(); - } + this._applyCodeAction(action, /* retrigger */ true, !!preview, ApplyCodeActionReason.FromCodeActions); this._actionWidgetService.hide(); currentDecorations.clear(); }, From 8ec6cec39b6892254ae2cfd774fd7ed00a428ad2 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 19 Feb 2024 13:52:10 +0100 Subject: [PATCH 0450/1863] report client OS / DE --- src/vs/base/common/desktopEnvironmentInfo.ts | 101 ++++++++++++++++++ .../node/sharedProcess/sharedProcessMain.ts | 34 ++++++ 2 files changed, 135 insertions(+) create mode 100644 src/vs/base/common/desktopEnvironmentInfo.ts diff --git a/src/vs/base/common/desktopEnvironmentInfo.ts b/src/vs/base/common/desktopEnvironmentInfo.ts new file mode 100644 index 0000000000000..b6e4c107db134 --- /dev/null +++ b/src/vs/base/common/desktopEnvironmentInfo.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { env } from 'vs/base/common/process'; + +// Define the enumeration for Desktop Environments +enum DesktopEnvironment { + UNKNOWN = 'UNKNOWN', + CINNAMON = 'CINNAMON', + DEEPIN = 'DEEPIN', + GNOME = 'GNOME', + KDE3 = 'KDE3', + KDE4 = 'KDE4', + KDE5 = 'KDE5', + KDE6 = 'KDE6', + PANTHEON = 'PANTHEON', + UNITY = 'UNITY', + XFCE = 'XFCE', + UKUI = 'UKUI', + LXQT = 'LXQT', +} + +const kXdgCurrentDesktopEnvVar = 'XDG_CURRENT_DESKTOP'; +const kKDESessionEnvVar = 'KDE_SESSION_VERSION'; + +export function getDesktopEnvironment(): DesktopEnvironment { + const xdgCurrentDesktop = env[kXdgCurrentDesktopEnvVar]; + if (xdgCurrentDesktop) { + const values = xdgCurrentDesktop.split(':').map(value => value.trim()).filter(value => value.length > 0); + for (const value of values) { + switch (value) { + case 'Unity': { + const desktopSessionUnity = env['DESKTOP_SESSION']; + if (desktopSessionUnity && desktopSessionUnity.includes('gnome-fallback')) { + return DesktopEnvironment.GNOME; + } + + return DesktopEnvironment.UNITY; + } + case 'Deepin': + return DesktopEnvironment.DEEPIN; + case 'GNOME': + return DesktopEnvironment.GNOME; + case 'X-Cinnamon': + return DesktopEnvironment.CINNAMON; + case 'KDE': { + const kdeSession = env[kKDESessionEnvVar]; + if (kdeSession === '5') { return DesktopEnvironment.KDE5; } + if (kdeSession === '6') { return DesktopEnvironment.KDE6; } + return DesktopEnvironment.KDE4; + } + case 'Pantheon': + return DesktopEnvironment.PANTHEON; + case 'XFCE': + return DesktopEnvironment.XFCE; + case 'UKUI': + return DesktopEnvironment.UKUI; + case 'LXQt': + return DesktopEnvironment.LXQT; + } + } + } + + const desktopSession = env['DESKTOP_SESSION']; + if (desktopSession) { + switch (desktopSession) { + case 'deepin': + return DesktopEnvironment.DEEPIN; + case 'gnome': + case 'mate': + return DesktopEnvironment.GNOME; + case 'kde4': + case 'kde-plasma': + return DesktopEnvironment.KDE4; + case 'kde': + if (kKDESessionEnvVar in env) { + return DesktopEnvironment.KDE4; + } + return DesktopEnvironment.KDE3; + case 'xfce': + case 'xubuntu': + return DesktopEnvironment.XFCE; + case 'ukui': + return DesktopEnvironment.UKUI; + } + } + + if ('GNOME_DESKTOP_SESSION_ID' in env) { + return DesktopEnvironment.GNOME; + } + if ('KDE_FULL_SESSION' in env) { + if (kKDESessionEnvVar in env) { + return DesktopEnvironment.KDE4; + } + return DesktopEnvironment.KDE3; + } + + return DesktopEnvironment.UNKNOWN; +} diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 82b79418af073..3c0de38db4322 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -116,6 +116,8 @@ import { RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityR import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { SharedProcessRawConnection, SharedProcessLifecycle } from 'vs/platform/sharedProcess/common/sharedProcess'; +import { getOSReleaseInfo } from 'vs/base/node/osReleaseInfo'; +import { getDesktopEnvironment } from 'vs/base/common/desktopEnvironmentInfo'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -172,6 +174,9 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Report Profiles Info this.reportProfilesInfo(telemetryService, userDataProfilesService); this._register(userDataProfilesService.onDidChangeProfiles(() => this.reportProfilesInfo(telemetryService, userDataProfilesService))); + + // Report Client OS/DE Info + this.reportClientOSInfo(telemetryService, logService); }); // Instantiate Contributions @@ -458,6 +463,35 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { }); } + private async reportClientOSInfo(telemetryService: ITelemetryService, logService: ILogService): Promise { + if (isLinux) { + const releaseInfo = await getOSReleaseInfo(logService.error.bind(logService)); + const desktopEnvironment = getDesktopEnvironment(); + if (releaseInfo) { + type ClientPlatformInfoClassification = { + platformId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system without any version information.' }; + platformVersionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system version excluding any name information or release code.' }; + platformIdLike: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the operating system the current OS derivate is closely related to.' }; + desktopEnvironment: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A string identifying the desktop environment the user is using.' }; + owner: 'benibenj'; + comment: 'Provides insight into the distro and desktop environment information on Linux.'; + }; + type ClientPlatformInfoEvent = { + platformId: string; + platformVersionId: string | undefined; + platformIdLike: string | undefined; + desktopEnvironment: string | undefined; + }; + telemetryService.publicLog2('clientPlatformInfo', { + platformId: releaseInfo.id, + platformVersionId: releaseInfo.version_id, + platformIdLike: releaseInfo.id_like, + desktopEnvironment: desktopEnvironment + }); + } + } + } + handledClientConnection(e: MessageEvent): boolean { // This filter on message port messages will look for From 7329258fc98c1c4596efd08294cd582b17d68534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Mon, 19 Feb 2024 13:56:47 +0100 Subject: [PATCH 0451/1863] Don't run `onDidBlurEditorWidget` and `onDidFocusEditorText` if inline edit is disabled (#205378) --- .../inlineEdit/browser/inlineEditController.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 2b5db89b91377..81d6fdb9d54bc 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -110,7 +110,13 @@ export class InlineEditController extends Disposable { })); //Clear suggestions on lost focus - this._register(editor.onDidBlurEditorWidget(() => { + const editorBlurSingal = observableSignalFromEvent('InlineEditController.editorBlurSignal', editor.onDidBlurEditorWidget); + this._register(autorun(reader => { + /** @description InlineEditController.editorBlur */ + if (!this._enabled.read(reader)) { + return; + } + editorBlurSingal.read(reader); // This is a hidden setting very useful for debugging if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) { return; @@ -121,11 +127,14 @@ export class InlineEditController extends Disposable { })); //Invoke provider on focus - this._register(editor.onDidFocusEditorText(async () => { - if (!this._enabled.get()) { + const editorFocusSignal = observableSignalFromEvent('InlineEditController.editorFocusSignal', editor.onDidFocusEditorText); + this._register(autorun(reader => { + /** @description InlineEditController.editorFocus */ + if (!this._enabled.read(reader)) { return; } - await this.getInlineEdit(editor, true); + editorFocusSignal.read(reader); + this.getInlineEdit(editor, true); })); From 69f86adcad727b6ff21913a81ea816f5a59dc2e4 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 13:59:55 +0100 Subject: [PATCH 0452/1863] rename widget: lots of traces to identify why rename widget doesn't appear sometimes --- .../editor/contrib/rename/browser/rename.ts | 2 +- .../rename/browser/renameInputField.ts | 34 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index cdcadcf58f7d7..fcf0db314c29f 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -315,7 +315,7 @@ class RenameController implements IEditorContribution { } cancelRenameInput(): void { - this._renameInputField.cancelInput(true); + this._renameInputField.cancelInput(true, 'cancelRenameInput command'); } focusNextRenameSuggestion(): void { diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 106d866178acc..520ed6d0e0e9e 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -25,6 +25,7 @@ import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/commo import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; import { defaultListStyles } from 'vs/platform/theme/browser/defaultStyles'; import { editorWidgetBackground, @@ -72,6 +73,7 @@ export class RenameInputField implements IContentWidget { @IThemeService private readonly _themeService: IThemeService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, + @ILogService private readonly _logService: ILogService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); this._focusedContextKey = CONTEXT_RENAME_INPUT_FOCUSED.bindTo(contextKeyService); @@ -206,9 +208,10 @@ export class RenameInputField implements IContentWidget { } afterRender(position: ContentWidgetPositionPreference | null): void { + this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore - this.cancelInput(true); + this.cancelInput(true, 'afterRender (because position is null)'); return; } @@ -241,10 +244,12 @@ export class RenameInputField implements IContentWidget { private _currentCancelInput?: (focusEditor: boolean) => void; acceptInput(wantsPreview: boolean): void { + this._trace(`invoking acceptInput`); this._currentAcceptInput?.(wantsPreview); } - cancelInput(focusEditor: boolean): void { + cancelInput(focusEditor: boolean, caller: string): void { + this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } @@ -280,6 +285,7 @@ export class RenameInputField implements IContentWidget { return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { + this._trace('invoking _currentCancelInput'); this._currentAcceptInput = undefined; this._currentCancelInput = undefined; this._candidatesView?.clearCandidates(); @@ -288,12 +294,13 @@ export class RenameInputField implements IContentWidget { }; this._currentAcceptInput = (wantsPreview) => { + this._trace('invoking _currentAcceptInput'); assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); const candidateName = this._candidatesView.focusedCandidate; if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { - this.cancelInput(true); + this.cancelInput(true, '_currentAcceptInput (because candidateName is undefined or input.value is empty)'); return; } @@ -307,9 +314,9 @@ export class RenameInputField implements IContentWidget { }); }; - disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true))); + disposeOnDone.add(cts.token.onCancellationRequested(() => this.cancelInput(true, 'cts.token.onCancellationRequested'))); if (!_sticky) { - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus(), 'editor.onDidBlurEditorWidget'))); } this._show(); @@ -321,6 +328,7 @@ export class RenameInputField implements IContentWidget { } private _show(): void { + this._trace('invoking _show'); this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth); this._visible = true; this._visibleContextKey.set(true); @@ -335,9 +343,13 @@ export class RenameInputField implements IContentWidget { } private async _updateRenameCandidates(candidates: ProviderResult[], currentName: string, token: CancellationToken) { + const trace = (...args: any[]) => this._trace('_updateRenameCandidates', ...args); + + trace('start'); const namesListResults = await raceCancellation(Promise.allSettled(candidates), token); if (namesListResults === undefined) { + trace('returning early - received updateRenameCandidates results - undefined'); return; } @@ -346,27 +358,39 @@ export class RenameInputField implements IContentWidget { ? namesListResult.value : [] ); + trace(`received updateRenameCandidates results - total (unfiltered) ${newNames.length} candidates.`); // deduplicate and filter out the current value const distinctNames = arrays.distinct(newNames, v => v.newSymbolName); + trace(`distinct candidates - ${distinctNames.length} candidates.`); + const validDistinctNames = distinctNames.filter(({ newSymbolName }) => newSymbolName.trim().length > 0 && newSymbolName !== this._input?.value && newSymbolName !== currentName); + trace(`valid distinct candidates - ${newNames.length} candidates.`); if (validDistinctNames.length < 1) { + trace('returning early - no valid distinct candidates'); return; } // show the candidates + trace('setting candidates'); this._candidatesView!.setCandidates(validDistinctNames); // ask editor to re-layout given that the widget is now of a different size after rendering rename candidates + trace('asking editor to re-layout'); this._editor.layoutContentWidget(this); } private _hide(): void { + this._trace('invoked _hide'); this._visible = false; this._visibleContextKey.reset(); this._editor.layoutContentWidget(this); } + + private _trace(...args: any[]) { + this._logService.trace('RenameInputField', ...args); + } } export class CandidatesView { From 26fdfc6c98f742a2ea967cbc259aab4ecbae5bbc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 19 Feb 2024 14:19:29 +0100 Subject: [PATCH 0453/1863] move accessible diff view above status actions (#205538) --- 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 7fd28a442ac6a..f65363a736b28 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -179,13 +179,13 @@ export class InlineChatWidget { h('div.messageActions@messageActions') ]), h('div.followUps.hidden@followUps'), + h('div.accessibleViewer@accessibleViewer'), h('div.status@status', [ h('div.label.info.hidden@infoLabel'), h('div.actions.hidden@statusToolbar'), h('div.label.status.hidden@statusLabel'), h('div.actions.hidden@feedbackToolbar'), ]), - h('div.accessibleViewer@accessibleViewer') ] ); From d83cc26e1b1046e6464fe1a363287bc4baf4e2c8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 14:37:31 +0100 Subject: [PATCH 0454/1863] voice - avoid cancellation token and disposable at the same time (#205532) * voice - avoid cancellation token and disposable at the same time * fix tests * . * . --- .../workbench/api/browser/mainThreadSpeech.ts | 45 +++++++++++-------- .../contrib/chat/common/voiceChat.ts | 10 ++--- .../actions/voiceChatActions.ts | 2 +- .../chat/test/common/voiceChat.test.ts | 14 +++--- .../browser/dictation/editorDictation.ts | 2 +- .../electron-sandbox/inlineChatQuickVoice.ts | 1 - .../contrib/speech/common/speechService.ts | 4 +- .../contrib/terminal/browser/terminalVoice.ts | 7 +-- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index d670ffd66a1f6..389fa1b87097b 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -3,8 +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 { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostContext, ExtHostSpeechShape, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -43,43 +42,53 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const registration = this.speechService.registerSpeechProvider(identifier, { metadata, createSpeechToTextSession: token => { + if (token.isCancellationRequested) { + return { + onDidChange: Event.None + }; + } + const disposables = new DisposableStore(); - const cts = new CancellationTokenSource(token); const session = Math.random(); this.proxy.$createSpeechToTextSession(handle, session); - disposables.add(token.onCancellationRequested(() => this.proxy.$cancelSpeechToTextSession(session))); const onDidChange = disposables.add(new Emitter()); this.speechToTextSessions.set(session, { onDidChange }); + disposables.add(token.onCancellationRequested(() => { + this.proxy.$cancelSpeechToTextSession(session); + this.speechToTextSessions.delete(session); + disposables.dispose(); + })); + return { - onDidChange: onDidChange.event, - dispose: () => { - cts.dispose(true); - this.speechToTextSessions.delete(session); - disposables.dispose(); - } + onDidChange: onDidChange.event }; }, createKeywordRecognitionSession: token => { + if (token.isCancellationRequested) { + return { + onDidChange: Event.None + }; + } + const disposables = new DisposableStore(); - const cts = new CancellationTokenSource(token); const session = Math.random(); this.proxy.$createKeywordRecognitionSession(handle, session); - disposables.add(token.onCancellationRequested(() => this.proxy.$cancelKeywordRecognitionSession(session))); const onDidChange = disposables.add(new Emitter()); this.keywordRecognitionSessions.set(session, { onDidChange }); + disposables.add(token.onCancellationRequested(() => { + this.proxy.$cancelKeywordRecognitionSession(session); + this.keywordRecognitionSessions.delete(session); + disposables.dispose(); + })); + return { - onDidChange: onDidChange.event, - dispose: () => { - cts.dispose(true); - this.keywordRecognitionSessions.delete(session); - disposables.dispose(); - } + onDidChange: onDidChange.event }; } }); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index ba4156495dbb3..c0cf6daa574a2 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -41,7 +41,7 @@ export interface IVoiceChatTextEvent extends ISpeechToTextEvent { readonly waitingForInput?: boolean; } -export interface IVoiceChatSession extends IDisposable { +export interface IVoiceChatSession { readonly onDidChange: Event; } @@ -131,12 +131,13 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { createVoiceChatSession(token: CancellationToken, options: IVoiceChatSessionOptions): IVoiceChatSession { const disposables = new DisposableStore(); + disposables.add(token.onCancellationRequested(() => disposables.dispose())); let detectedAgent = false; let detectedSlashCommand = false; const emitter = disposables.add(new Emitter()); - const session = disposables.add(this.speechService.createSpeechToTextSession(token, 'chat')); + const session = this.speechService.createSpeechToTextSession(token, 'chat'); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: @@ -212,8 +213,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { })); return { - onDidChange: emitter.event, - dispose: () => disposables.dispose() + onDidChange: emitter.event }; } 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 75867e30b4b51..e56a11a9ca647 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -292,7 +292,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = session.disposables.add(this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' })); + const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' }); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index fd4184ab8eb97..eaaea9096dd2c 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -74,8 +74,7 @@ suite('VoiceChat', () => { createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { return { - onDidChange: emitter.event, - dispose: () => { } + onDidChange: emitter.event }; } @@ -89,12 +88,11 @@ suite('VoiceChat', () => { let service: VoiceChatService; let event: IVoiceChatTextEvent | undefined; - let session: ISpeechToTextSession | undefined; function createSession(options: IVoiceChatSessionOptions) { - session?.dispose(); - - session = disposables.add(service.createVoiceChatSession(CancellationToken.None, options)); + const cts = new CancellationTokenSource(); + disposables.add(toDisposable(() => cts.dispose(true))); + const session = service.createVoiceChatSession(cts.token, options); disposables.add(session.onDidChange(e => { event = e; })); diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 0150448c6bd92..b4ed9df731d13 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -231,7 +231,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { const cts = new CancellationTokenSource(); disposables.add(toDisposable(() => cts.dispose(true))); - const session = disposables.add(this.speechService.createSpeechToTextSession(cts.token)); + const session = this.speechService.createSpeechToTextSession(cts.token); disposables.add(session.onDidChange(e => { if (cts.token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts index 16c1d70d90b88..b75bb758c6ce2 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts @@ -263,7 +263,6 @@ export class InlineChatQuickVoice implements IEditorContribution { const done = (abort: boolean) => { cts.dispose(true); - session.dispose(); listener.dispose(); this._widget.hide(); this._ctxQuickChatInProgress.reset(); diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 1e6e442eaefe5..4e4ff9345e66a 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -33,7 +33,7 @@ export interface ISpeechToTextEvent { readonly text?: string; } -export interface ISpeechToTextSession extends IDisposable { +export interface ISpeechToTextSession { readonly onDidChange: Event; } @@ -48,7 +48,7 @@ export interface IKeywordRecognitionEvent { readonly text?: string; } -export interface IKeywordRecognitionSession extends IDisposable { +export interface IKeywordRecognitionSession { readonly onDidChange: Event; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts index 57464855e57f2..9ff52e29fc560 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalVoice.ts @@ -5,7 +5,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; @@ -87,8 +87,9 @@ export class TerminalVoiceSession extends Disposable { this._sendText(); this.stop(); }, voiceTimeout)); - this._cancellationTokenSource = this._register(new CancellationTokenSource()); - const session = this._disposables.add(this._speechService.createSpeechToTextSession(this._cancellationTokenSource!.token)); + this._cancellationTokenSource = new CancellationTokenSource(); + this._register(toDisposable(() => this._cancellationTokenSource?.dispose(true))); + const session = this._speechService.createSpeechToTextSession(this._cancellationTokenSource?.token); this._disposables.add(session.onDidChange((e) => { if (this._cancellationTokenSource?.token.isCancellationRequested) { From b2b60ac5b3fd3d359994b8f2f79ab54c0c6523b6 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Mon, 19 Feb 2024 14:39:49 +0100 Subject: [PATCH 0455/1863] fix: memory leak in code editor widget (#205488) --- src/vs/editor/browser/widget/codeEditorContributions.ts | 8 ++++---- src/vs/editor/browser/widget/codeEditorWidget.ts | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditorContributions.ts index f556e1440f1c5..840bcc4f5258a 100644 --- a/src/vs/editor/browser/widget/codeEditorContributions.ts +++ b/src/vs/editor/browser/widget/codeEditorContributions.ts @@ -5,7 +5,7 @@ import { getWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -111,10 +111,10 @@ export class CodeEditorContributions extends Disposable { this._instantiateSome(EditorContributionInstantiation.BeforeFirstInteraction); } - public onAfterModelAttached(): void { - this._register(runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => { + public onAfterModelAttached(): IDisposable { + return runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.AfterFirstRender); - }, 50)); + }, 50); } private _instantiateSome(instantiation: EditorContributionInstantiation): void { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 63be27baefbb6..5466f913d244b 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -242,6 +242,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _overflowWidgetsDomNode: HTMLElement | undefined; private readonly _id: number; private readonly _configuration: IEditorConfiguration; + private _contributionsDisposable: IDisposable | undefined; protected readonly _actions = new Map(); @@ -523,7 +524,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._onDidChangeModel.fire(e); this._postDetachModelCleanup(detachedModel); - this._contributions.onAfterModelAttached(); + this._contributionsDisposable = this._contributions.onAfterModelAttached(); } private _removeDecorationTypes(): void { @@ -1871,6 +1872,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } private _detachModel(): ITextModel | null { + this._contributionsDisposable?.dispose(); + this._contributionsDisposable = undefined; if (!this._modelData) { return null; } @@ -1887,7 +1890,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) { this._domElement.removeChild(this._bannerDomNode); } - return model; } From a6921e0bc8720b2f5552a1d435b54bce471ef1a3 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 14:47:46 +0100 Subject: [PATCH 0456/1863] rename widget: fix not showing on 2nd try --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 520ed6d0e0e9e..4f9f1b15be8b8 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -179,9 +179,12 @@ export class RenameInputField implements IContentWidget { const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); const editorBox = getDomNodePagePosition(this._editor.getDomNode()); - const cursorBox = this._editor.getScrolledVisiblePosition(this._position!); - this._nPxAvailableAbove = cursorBox.top + editorBox.top; + // FIXME@ulugbekna: can getVisibleRanges() be empty? if so what to do about it + const firstLineInViewport = this._editor.getVisibleRanges()[0].startLineNumber; + const cursorBoxTop = this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + + this._nPxAvailableAbove = cursorBoxTop + editorBox.top; this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; const lineHeight = this._editor.getOption(EditorOption.lineHeight); From 05bf957b312e16532c7669004df043e7c580af1d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 13:53:58 +0000 Subject: [PATCH 0457/1863] Rename the chat agent API to "participant" (#205477) * Start renaming chat API from "agent" to "participant" * Rename the rest of the API * Rename in integration test * Update integration test api proposals * Bump distro --- extensions/vscode-api-tests/package.json | 4 +- .../src/singlefolder-tests/chat.test.ts | 48 ++--- package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 16 +- .../api/common/extHostChatAgents2.ts | 86 ++++----- .../api/common/extHostChatVariables.ts | 2 +- .../api/common/extHostTypeConverters.ts | 40 ++--- src/vs/workbench/api/common/extHostTypes.ts | 18 +- .../browser/actions/chatCodeblockActions.ts | 6 +- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 4 +- .../common/extensionsApiProposals.ts | 6 +- ...s => vscode.proposed.chatParticipant.d.ts} | 168 +++++++++--------- ...de.proposed.chatParticipantAdditions.d.ts} | 122 ++++++------- ...code.proposed.defaultChatParticipant.d.ts} | 20 +-- .../vscode.proposed.interactive.d.ts | 2 +- 16 files changed, 273 insertions(+), 275 deletions(-) rename src/vscode-dts/{vscode.proposed.chatAgents2.d.ts => vscode.proposed.chatParticipant.d.ts} (68%) rename src/vscode-dts/{vscode.proposed.chatAgents2Additions.d.ts => vscode.proposed.chatParticipantAdditions.d.ts} (63%) rename src/vscode-dts/{vscode.proposed.defaultChatAgent.d.ts => vscode.proposed.defaultChatParticipant.d.ts} (53%) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 85f70af3f4f38..adf3c5fae9f46 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -7,9 +7,9 @@ "enabledApiProposals": [ "activeComment", "authSession", - "chatAgents2", + "chatParticipant", "languageModels", - "defaultChatAgent", + "defaultChatParticipant", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index bdb033322ebd7..28621d1bb6bcb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, ChatAgentContext, ChatAgentRequest, ChatAgentResult2, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { CancellationToken, ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -21,15 +21,15 @@ suite('chat', () => { disposeAll(disposables); }); - function getDeferredForRequest(): DeferredPromise { - const deferred = new DeferredPromise(); - disposables.push(setupAgent()(request => deferred.complete(request.request))); + function getDeferredForRequest(): DeferredPromise { + const deferred = new DeferredPromise(); + disposables.push(setupParticipant()(request => deferred.complete(request.request))); return deferred; } - function setupAgent(): Event<{ request: ChatAgentRequest; context: ChatAgentContext }> { - const emitter = new EventEmitter<{ request: ChatAgentRequest; context: ChatAgentContext }>(); + function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> { + const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>(); disposables.push(); disposables.push(interactive.registerInteractiveSessionProvider('provider', { prepareSession: (_token: CancellationToken): ProviderResult => { @@ -40,23 +40,23 @@ suite('chat', () => { }, })); - const agent = chat.createChatAgent('agent', (request, context, _progress, _token) => { + const participant = chat.createChatParticipant('participant', (request, context, _progress, _token) => { emitter.fire({ request, context }); return null; }); - agent.isDefault = true; - agent.commandProvider = { + participant.isDefault = true; + participant.commandProvider = { provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; - disposables.push(agent); + disposables.push(participant); return emitter.event; } - test('agent and slash command', async () => { - const onRequest = setupAgent(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + test('participant and slash command', async () => { + const onRequest = setupParticipant(); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); let i = 0; onRequest(request => { @@ -64,16 +64,16 @@ suite('chat', () => { assert.deepStrictEqual(request.request.command, 'hello'); assert.strictEqual(request.request.prompt, 'friend'); i++; - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); } else { assert.strictEqual(request.context.history.length, 1); - assert.strictEqual(request.context.history[0].agent.agent, 'agent'); + assert.strictEqual(request.context.history[0].participant.participant, 'participant'); assert.strictEqual(request.context.history[0].command, 'hello'); } }); }); - test('agent and variable', async () => { + test('participant and variable', async () => { disposables.push(chat.registerVariable('myVar', 'My variable', { resolve(_name, _context, _token) { return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; @@ -81,7 +81,7 @@ suite('chat', () => { })); const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant hi #myVar' }); const request = await deferred.p; assert.strictEqual(request.prompt, 'hi #myVar'); assert.strictEqual(request.variables[0].values[0].value, 'myValue'); @@ -97,25 +97,25 @@ suite('chat', () => { }, })); - const deferred = new DeferredPromise(); - const agent = chat.createChatAgent('agent', (_request, _context, _progress, _token) => { + const deferred = new DeferredPromise(); + const participant = chat.createChatParticipant('participant', (_request, _context, _progress, _token) => { return { metadata: { key: 'value' } }; }); - agent.isDefault = true; - agent.commandProvider = { + participant.isDefault = true; + participant.commandProvider = { provideCommands: (_token) => { return [{ name: 'hello', description: 'Hello' }]; } }; - agent.followupProvider = { + participant.followupProvider = { provideFollowups(result, _token) { deferred.complete(result); return []; }, }; - disposables.push(agent); + disposables.push(participant); - interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); const result = await deferred.p; assert.deepStrictEqual(result.metadata, { key: 'value' }); }); diff --git a/package.json b/package.json index e413eef05b284..df171934c3409 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "664b4b796ea2343e71889a507e125feb14390bdf", + "distro": "af73a537ea203329debad3df7ca7b42b4799473f", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b0b9c8c0b4731..46292e1bb115d 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1410,15 +1410,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatAgents2'); + checkProposedApiEnabled(extension, 'chatParticipant'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, - createChatAgent(name: string, handler: vscode.ChatAgentExtendedRequestHandler) { - checkProposedApiEnabled(extension, 'chatAgents2'); + createChatParticipant(name: string, handler: vscode.ChatExtendedRequestHandler) { + checkProposedApiEnabled(extension, 'chatParticipant'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, }; @@ -1472,9 +1472,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // types Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, - ChatAgentResultFeedbackKind: extHostTypes.ChatAgentResultFeedbackKind, + ChatResultFeedbackKind: extHostTypes.ChatResultFeedbackKind, ChatVariableLevel: extHostTypes.ChatVariableLevel, - ChatAgentCompletionItem: extHostTypes.ChatAgentCompletionItem, + ChatCompletionItem: extHostTypes.ChatCompletionItem, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, @@ -1664,7 +1664,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I LogLevel: LogLevel, EditSessionIdentityMatch: EditSessionIdentityMatch, InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection, - ChatAgentCopyKind: extHostTypes.ChatAgentCopyKind, + ChatCopyKind: extHostTypes.ChatCopyKind, InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, StackFrameFocus: extHostTypes.StackFrameFocus, ThreadFocus: extHostTypes.ThreadFocus, @@ -1678,8 +1678,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseProgressPart: extHostTypes.ChatResponseProgressPart, ChatResponseReferencePart: extHostTypes.ChatResponseReferencePart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, - ChatAgentRequestTurn: extHostTypes.ChatAgentRequestTurn, - ChatAgentResponseTurn: extHostTypes.ChatAgentResponseTurn, + ChatRequestTurn: extHostTypes.ChatRequestTurn, + ChatResponseTurn: extHostTypes.ChatResponseTurn, LanguageModelSystemMessage: extHostTypes.LanguageModelSystemMessage, LanguageModelUserMessage: extHostTypes.LanguageModelUserMessage, LanguageModelAssistantMessage: extHostTypes.LanguageModelAssistantMessage, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 110b89a042b66..59a5684877c6f 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -31,7 +31,7 @@ class ChatAgentResponseStream { private _stopWatch = StopWatch.create(false); private _isClosed: boolean = false; private _firstProgress: number | undefined; - private _apiObject: vscode.ChatAgentExtendedResponseStream | undefined; + private _apiObject: vscode.ChatExtendedResponseStream | undefined; constructor( private readonly _extension: IExtensionDescription, @@ -165,7 +165,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } - createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentExtendedRequestHandler): vscode.ChatAgent2 { + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { const handle = ExtHostChatAgents2._idPool++; const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); this._agents.set(handle, agent); @@ -219,22 +219,22 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[]> { + private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { - const res: (vscode.ChatAgentRequestTurn | vscode.ChatAgentResponseTurn)[] = []; + const res: (vscode.ChatRequestTurn | vscode.ChatResponseTurn)[] = []; for (const h of context.history) { const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatAgentResult2 = request.agentId === h.request.agentId ? + const result: vscode.ChatResult = request.agentId === h.request.agentId ? ehResult : { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatAgentRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', agent: h.request.agentId })); + res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); - res.push(new extHostTypes.ChatAgentResponseTurn(parts, result, { extensionId: '', agent: h.request.agentId }, h.request.command)); + res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); } return res; @@ -271,13 +271,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } const ehResult = typeConvert.ChatAgentResult.to(result); - let kind: extHostTypes.ChatAgentResultFeedbackKind; + let kind: extHostTypes.ChatResultFeedbackKind; switch (vote) { case InteractiveSessionVoteDirection.Down: - kind = extHostTypes.ChatAgentResultFeedbackKind.Unhelpful; + kind = extHostTypes.ChatResultFeedbackKind.Unhelpful; break; case InteractiveSessionVoteDirection.Up: - kind = extHostTypes.ChatAgentResultFeedbackKind.Helpful; + kind = extHostTypes.ChatResultFeedbackKind.Helpful; break; } agent.acceptFeedback(reportIssue ? @@ -333,8 +333,8 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { class ExtHostChatAgent { - private _commandProvider: vscode.ChatAgentCommandProvider | undefined; - private _followupProvider: vscode.ChatAgentFollowupProvider | undefined; + private _commandProvider: vscode.ChatCommandProvider | undefined; + private _followupProvider: vscode.ChatFollowupProvider | undefined; private _description: string | undefined; private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; @@ -343,11 +343,11 @@ class ExtHostChatAgent { private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; - private _onDidReceiveFeedback = new Emitter(); - private _onDidPerformAction = new Emitter(); + private _onDidReceiveFeedback = new Emitter(); + private _onDidPerformAction = new Emitter(); private _supportIssueReporting: boolean | undefined; - private _agentVariableProvider?: { provider: vscode.ChatAgentCompletionItemProvider; triggerCharacters: string[] }; - private _welcomeMessageProvider?: vscode.ChatAgentWelcomeMessageProvider | undefined; + private _agentVariableProvider?: { provider: vscode.ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; + private _welcomeMessageProvider?: vscode.ChatWelcomeMessageProvider | undefined; private _isSticky: boolean | undefined; constructor( @@ -355,18 +355,18 @@ class ExtHostChatAgent { public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, - private _requestHandler: vscode.ChatAgentExtendedRequestHandler, + private _requestHandler: vscode.ChatExtendedRequestHandler, ) { } - acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + acceptFeedback(feedback: vscode.ChatResultFeedback) { this._onDidReceiveFeedback.fire(feedback); } - acceptAction(event: vscode.ChatAgentUserActionEvent) { + acceptAction(event: vscode.ChatUserActionEvent) { this._onDidPerformAction.fire(event); } - async invokeCompletionProvider(query: string, token: CancellationToken): Promise { + async invokeCompletionProvider(query: string, token: CancellationToken): Promise { if (!this._agentVariableProvider) { return []; } @@ -385,7 +385,7 @@ class ExtHostChatAgent { return result .map(c => { if ('isSticky2' in c) { - checkProposedApiEnabled(this.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(this.extension, 'chatParticipantAdditions'); } return { @@ -398,7 +398,7 @@ class ExtHostChatAgent { }); } - async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + async provideFollowups(result: vscode.ChatResult, token: CancellationToken): Promise { if (!this._followupProvider) { return []; } @@ -430,7 +430,7 @@ class ExtHostChatAgent { }); } - async provideSampleQuestions(token: CancellationToken): Promise { + async provideSampleQuestions(token: CancellationToken): Promise { if (!this._welcomeMessageProvider || !this._welcomeMessageProvider.provideSampleQuestions) { return []; } @@ -442,7 +442,7 @@ class ExtHostChatAgent { return content; } - get apiAgent(): vscode.ChatAgent2 { + get apiAgent(): vscode.ChatParticipant { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -527,20 +527,20 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get isDefault() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._isDefault; }, set isDefault(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._isDefault = v; updateMetadataSoon(); }, get helpTextPrefix() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPrefix; }, set helpTextPrefix(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); if (!that._isDefault) { throw new Error('helpTextPrefix is only available on the default chat agent'); } @@ -549,11 +549,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get helpTextPostfix() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPostfix; }, set helpTextPostfix(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); if (!that._isDefault) { throw new Error('helpTextPostfix is only available on the default chat agent'); } @@ -562,11 +562,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get isSecondary() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._isSecondary; }, set isSecondary(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._isSecondary = v; updateMetadataSoon(); }, @@ -578,19 +578,19 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get supportIssueReporting() { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); return that._supportIssueReporting; }, set supportIssueReporting(v) { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); that._supportIssueReporting = v; updateMetadataSoon(); }, get onDidReceiveFeedback() { return that._onDidReceiveFeedback.event; }, - set agentVariableProvider(v) { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + set participantVariableProvider(v) { + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); that._agentVariableProvider = v; if (v) { if (!v.triggerCharacters.length) { @@ -602,20 +602,20 @@ class ExtHostChatAgent { that._proxy.$unregisterAgentCompletionsProvider(that._handle); } }, - get agentVariableProvider() { - checkProposedApiEnabled(that.extension, 'chatAgents2Additions'); + get participantVariableProvider() { + checkProposedApiEnabled(that.extension, 'chatParticipantAdditions'); return that._agentVariableProvider; }, set welcomeMessageProvider(v) { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._welcomeMessageProvider = v; updateMetadataSoon(); }, get welcomeMessageProvider() { - checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._welcomeMessageProvider; }, - onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') + onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatParticipantAdditions') ? undefined! : this._onDidPerformAction.event , @@ -633,10 +633,10 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatAgent2; + } satisfies vscode.ChatParticipant; } - invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, response: vscode.ChatAgentExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { + invoke(request: vscode.ChatRequest, context: vscode.ChatContext, response: vscode.ChatExtendedResponseStream, token: CancellationToken): vscode.ProviderResult { return this._requestHandler(request, context, response, token); } } diff --git a/src/vs/workbench/api/common/extHostChatVariables.ts b/src/vs/workbench/api/common/extHostChatVariables.ts index ca3b265007b1f..e56e51676d430 100644 --- a/src/vs/workbench/api/common/extHostChatVariables.ts +++ b/src/vs/workbench/api/common/extHostChatVariables.ts @@ -32,7 +32,7 @@ export class ExtHostChatVariables implements ExtHostChatVariablesShape { } try { if (item.resolver.resolve2) { - checkProposedApiEnabled(item.extension, 'chatAgents2Additions'); + checkProposedApiEnabled(item.extension, 'chatParticipantAdditions'); const stream = new ChatVariableResolverResponseStream(requestId, this._proxy); const value = await item.resolver.resolve2(item.data.name, { prompt: messageText }, stream.apiObject, token); if (value) { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index ae82d84eae3e3..175a921311243 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2195,10 +2195,10 @@ export namespace DataTransfer { } export namespace ChatFollowup { - export function from(followup: vscode.ChatAgentFollowup, request: IChatAgentRequest | undefined): IChatFollowup { + export function from(followup: vscode.ChatFollowup, request: IChatAgentRequest | undefined): IChatFollowup { return { kind: 'reply', - agentId: followup.agentId ?? request?.agentId ?? '', + agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, title: followup.title, @@ -2206,11 +2206,11 @@ export namespace ChatFollowup { }; } - export function to(followup: IChatFollowup): vscode.ChatAgentFollowup { + export function to(followup: IChatFollowup): vscode.ChatFollowup { return { prompt: followup.message, title: followup.title, - agentId: followup.agentId, + participant: followup.agentId, command: followup.subCommand, tooltip: followup.tooltip, }; @@ -2490,13 +2490,13 @@ export namespace ChatResponsePart { } export namespace ChatResponseProgress { - export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatProgressDto | undefined { + export function from(extension: IExtensionDescription, progress: vscode.ChatExtendedProgress): extHostProtocol.IChatProgressDto | undefined { if ('markdownContent' in progress) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: MarkdownString.from(progress.markdownContent), kind: 'markdownContent' }; } else if ('content' in progress) { if ('vulnerabilities' in progress && progress.vulnerabilities) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: progress.content, vulnerabilities: progress.vulnerabilities, kind: 'vulnerability' }; } @@ -2504,7 +2504,7 @@ export namespace ChatResponseProgress { return { content: progress.content, kind: 'content' }; } - checkProposedApiEnabled(extension, 'chatAgents2Additions'); + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return { content: MarkdownString.from(progress.content), kind: 'markdownContent' }; } else if ('documents' in progress) { return { @@ -2534,9 +2534,9 @@ export namespace ChatResponseProgress { name: progress.title, kind: 'inlineReference' }; - } else if ('agentName' in progress) { - checkProposedApiEnabled(extension, 'chatAgents2Additions'); - return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' }; + } else if ('participant' in progress) { + checkProposedApiEnabled(extension, 'chatParticipantAdditions'); + return { agentName: progress.participant, command: progress.command, kind: 'agentDetection' }; } else if ('message' in progress) { return { content: MarkdownString.from(progress.message), kind: 'progressMessage' }; } else { @@ -2544,7 +2544,7 @@ export namespace ChatResponseProgress { } } - export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatAgentProgress | undefined { + export function to(progress: extHostProtocol.IChatProgressDto): vscode.ChatProgress | undefined { switch (progress.kind) { case 'markdownContent': case 'inlineReference': @@ -2574,7 +2574,7 @@ export namespace ChatResponseProgress { } } - export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatAgentContentProgress | undefined { + export function toProgressContent(progress: extHostProtocol.IChatContentProgressDto, commandsConverter: Command.ICommandsConverter): vscode.ChatContentProgress | undefined { switch (progress.kind) { case 'markdownContent': // For simplicity, don't sent back the 'extended' types, so downgrade markdown to just some text @@ -2600,7 +2600,7 @@ export namespace ChatResponseProgress { } export namespace ChatAgentRequest { - export function to(request: IChatAgentRequest): vscode.ChatAgentRequest { + export function to(request: IChatAgentRequest): vscode.ChatRequest { return { prompt: request.message, command: request.command, @@ -2610,7 +2610,7 @@ export namespace ChatAgentRequest { } export namespace ChatAgentResolvedVariable { - export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatAgentResolvedVariable { + export function to(request: { name: string; range: IOffsetRange; values: IChatRequestVariableValue[] }): vscode.ChatResolvedVariable { return { name: request.name, range: [request.range.start, request.range.endExclusive], @@ -2620,7 +2620,7 @@ export namespace ChatAgentResolvedVariable { } export namespace ChatAgentCompletionItem { - export function from(item: vscode.ChatAgentCompletionItem): extHostProtocol.IChatAgentCompletionItem { + export function from(item: vscode.ChatCompletionItem): extHostProtocol.IChatAgentCompletionItem { return { label: item.label, values: item.values.map(ChatVariable.from), @@ -2632,7 +2632,7 @@ export namespace ChatAgentCompletionItem { } export namespace ChatAgentResult { - export function to(result: IChatAgentResult): vscode.ChatAgentResult2 { + export function to(result: IChatAgentResult): vscode.ChatResult { return { errorDetails: result.errorDetails, metadata: result.metadata, @@ -2641,7 +2641,7 @@ export namespace ChatAgentResult { } export namespace ChatAgentUserActionEvent { - export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatAgentUserActionEvent | undefined { + export function to(result: IChatAgentResult, event: IChatUserActionEvent, commandsConverter: CommandsConverter): vscode.ChatUserActionEvent | undefined { if (event.action.kind === 'vote') { // Is the "feedback" type return; @@ -2649,10 +2649,10 @@ export namespace ChatAgentUserActionEvent { const ehResult = ChatAgentResult.to(result); if (event.action.kind === 'command') { - const commandAction: vscode.ChatAgentCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatAgentCommandButton }; + const commandAction: vscode.ChatCommandAction = { kind: 'command', commandButton: ChatResponseProgress.toProgressContent(event.action.commandButton, commandsConverter) as vscode.ChatCommandButton }; return { action: commandAction, result: ehResult }; } else if (event.action.kind === 'followUp') { - const followupAction: vscode.ChatAgentFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; + const followupAction: vscode.ChatFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) }; return { action: followupAction, result: ehResult }; } else { return { action: event.action, result: ehResult }; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 611314d600fe2..db0384507653c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4164,7 +4164,7 @@ export enum InteractiveSessionVoteDirection { Up = 1 } -export enum ChatAgentCopyKind { +export enum ChatCopyKind { Action = 1, Toolbar = 2 } @@ -4175,7 +4175,7 @@ export enum ChatVariableLevel { Full = 3 } -export class ChatAgentCompletionItem implements vscode.ChatAgentCompletionItem { +export class ChatCompletionItem implements vscode.ChatCompletionItem { label: string | CompletionItemLabel; insertText?: string; values: vscode.ChatVariableValue[]; @@ -4200,7 +4200,7 @@ export enum InteractiveEditorResponseFeedbackKind { Bug = 4 } -export enum ChatAgentResultFeedbackKind { +export enum ChatResultFeedbackKind { Unhelpful = 0, Helpful = 1, } @@ -4260,21 +4260,21 @@ export class ChatResponseReferencePart { } -export class ChatAgentRequestTurn implements vscode.ChatAgentRequestTurn { +export class ChatRequestTurn implements vscode.ChatRequestTurn { constructor( readonly prompt: string, readonly command: string | undefined, - readonly variables: vscode.ChatAgentResolvedVariable[], - readonly agent: { extensionId: string; agent: string }, + readonly variables: vscode.ChatResolvedVariable[], + readonly participant: { extensionId: string; participant: string }, ) { } } -export class ChatAgentResponseTurn implements vscode.ChatAgentResponseTurn { +export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( readonly response: ReadonlyArray, - readonly result: vscode.ChatAgentResult2, - readonly agent: { extensionId: string; agent: string }, + readonly result: vscode.ChatResult, + readonly participant: { extensionId: string; participant: string }, readonly command?: string ) { } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index cee0785180404..27f5c9f07f8ce 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatAgentCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -112,7 +112,7 @@ export function registerChatCodeBlockActions() { action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, - copyKind: ChatAgentCopyKind.Toolbar, + copyKind: ChatCopyKind.Toolbar, copiedCharacters: context.code.length, totalCharacters: context.code.length, copiedText: context.code, @@ -156,7 +156,7 @@ export function registerChatCodeBlockActions() { action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, - copyKind: ChatAgentCopyKind.Action, + copyKind: ChatCopyKind.Action, copiedText, copiedCharacters: copiedText.length, totalCharacters, diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 769476c60a78e..5da8d9b747b59 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -177,7 +177,7 @@ export interface IChatVoteAction { reportIssue?: boolean; } -export enum ChatAgentCopyKind { +export enum ChatCopyKind { // Keyboard shortcut or context menu Action = 1, Toolbar = 2 @@ -186,7 +186,7 @@ export enum ChatAgentCopyKind { export interface IChatCopyAction { kind: 'copy'; codeBlockIndex: number; - copyKind: ChatAgentCopyKind; + copyKind: ChatCopyKind; copiedCharacters: number; totalCharacters: number; copiedText: string; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index fe8185212e411..630e7e853e8bf 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -26,7 +26,7 @@ import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageMode import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatAgentCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -226,7 +226,7 @@ export class ChatService extends Disposable implements IChatService { } else if (action.action.kind === 'copy') { this.telemetryService.publicLog2('interactiveSessionCopy', { providerId: action.providerId, - copyKind: action.action.copyKind === ChatAgentCopyKind.Action ? 'action' : 'toolbar' + copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar' }); } else if (action.action.kind === 'insert') { this.telemetryService.publicLog2('interactiveSessionInsert', { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 53e7755722664..0ba3e2b1ea22e 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,8 +11,8 @@ export const allApiProposals = Object.freeze({ 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', - chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', - chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', + chatParticipant: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipant.d.ts', + chatParticipantAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', @@ -44,7 +44,7 @@ export const allApiProposals = Object.freeze({ customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', debugFocus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugFocus.d.ts', debugVisualization: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.debugVisualization.d.ts', - defaultChatAgent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts', + defaultChatParticipant: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', diffContentOptions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts', documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts similarity index 68% rename from src/vscode-dts/vscode.proposed.chatAgents2.d.ts rename to src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 783c1bcf78e06..f4b2242ed4609 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -6,70 +6,70 @@ declare module 'vscode' { // TODO@API name: Turn? - export class ChatAgentRequestTurn { + export class ChatRequestTurn { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. + * Information about variables used in this request are is stored in {@link ChatRequest.variables}. * - * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. */ readonly prompt: string; /** - * The name of the chat agent and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension to which this request was directed. */ - readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly participant: { readonly extensionId: string; readonly participant: string }; /** - * The name of the {@link ChatAgentCommand command} that was selected for this request. + * The name of the {@link ChatCommand command} that was selected for this request. */ readonly command: string | undefined; /** * The variables that were referenced in this message. */ - readonly variables: ChatAgentResolvedVariable[]; + readonly variables: ChatResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agent: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } // TODO@API name: Turn? - export class ChatAgentResponseTurn { + export class ChatResponseTurn { /** - * The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented. + * The content that was received from the chat participant. Only the progress parts that represent actual content (not metadata) are represented. */ readonly response: ReadonlyArray; /** - * The result that was received from the chat agent. + * The result that was received from the chat participant. */ - readonly result: ChatAgentResult2; + readonly result: ChatResult; /** - * The name of the chat agent and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension to which this request was directed. */ - readonly agent: { readonly extensionId: string; readonly agent: string }; + readonly participant: { readonly extensionId: string; readonly participant: string }; readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatAgentResult2, agentId: { extensionId: string; agent: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); } - export interface ChatAgentContext { + export interface ChatContext { /** * All of the chat messages so far in the current chat session. */ - readonly history: ReadonlyArray; + readonly history: ReadonlyArray; } /** * Represents an error result from a chat request. */ - export interface ChatAgentErrorDetails { + export interface ChatErrorDetails { /** * An error message that is shown to the user. */ @@ -93,11 +93,11 @@ declare module 'vscode' { /** * The result of a chat request. */ - export interface ChatAgentResult2 { + export interface ChatResult { /** * If the request resulted in an error, this property defines the error details. */ - errorDetails?: ChatAgentErrorDetails; + errorDetails?: ChatErrorDetails; /** * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. @@ -108,7 +108,7 @@ declare module 'vscode' { /** * Represents the type of user feedback received. */ - export enum ChatAgentResultFeedbackKind { + export enum ChatResultFeedbackKind { /** * The user marked the result as helpful. */ @@ -123,25 +123,24 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatResultFeedback { /** - * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, - * and it can be extended with arbitrary properties if needed. + * This instance of ChatResult has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. */ - readonly result: ChatAgentResult2; + readonly result: ChatResult; /** * The kind of feedback that was received. */ - readonly kind: ChatAgentResultFeedbackKind; + readonly kind: ChatResultFeedbackKind; } - export interface ChatAgentCommand { + export interface ChatCommand { /** * A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. * - * **Note**: The name should be unique among the commands provided by this agent. + * **Note**: The name should be unique among the commands provided by this participant. */ readonly name: string; @@ -157,17 +156,16 @@ declare module 'vscode' { /** * Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message. - * If this is not set, the chat input will fall back to the agent after submitting this command. */ readonly isSticky?: boolean; } - export interface ChatAgentCommandProvider { + export interface ChatCommandProvider { /** - * Returns a list of commands that its agent is capable of handling. A command - * can be selected by the user and will then be passed to the {@link ChatAgentRequestHandler handler} - * via the {@link ChatAgentRequest.command command} property. + * Returns a list of commands that its participant is capable of handling. A command + * can be selected by the user and will then be passed to the {@link ChatRequestHandler handler} + * via the {@link ChatRequest.command command} property. * * * @param token A cancellation token. @@ -175,26 +173,26 @@ declare module 'vscode' { * an empty array. */ // TODO@API Q: should we provide the current history or last results for extra context? - provideCommands(token: CancellationToken): ProviderResult; + provideCommands(token: CancellationToken): ProviderResult; } /** * A followup question suggested by the model. */ - export interface ChatAgentFollowup { + export interface ChatFollowup { /** * The message to send to the chat. */ prompt: string; /** - * By default, the followup goes to the same agent/command. But this property can be set to invoke a different agent. - * TODO@API do extensions need to specify the extensionID of the agent here as well? + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant. + * TODO@API do extensions need to specify the extensionID of the participant here as well? */ - agentId?: string; + participant?: string; /** - * By default, the followup goes to the same agent/command. But this property can be set to invoke a different command. + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. */ command?: string; @@ -213,40 +211,40 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface ChatAgentFollowupProvider { + export interface ChatFollowupProvider { /** * - * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. + * @param result The same instance of the result object that was returned by the chat participant, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; + provideFollowups(result: ChatResult, token: CancellationToken): ProviderResult; } /** - * A chat request handler is a callback that will be invoked when a request is made to a chat agent. + * A chat request handler is a callback that will be invoked when a request is made to a chat participant. */ - export type ChatAgentRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult; + export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - export interface ChatAgent2 { + export interface ChatParticipant { /** - * The short name by which this agent is referred to in the UI, e.g `workspace`. + * The short name by which this participant is referred to in the UI, e.g `workspace`. */ readonly name: string; /** - * The full name of this agent. + * The full name of this participant. */ fullName: string; /** - * A human-readable description explaining what this agent does. + * A human-readable description explaining what this participant does. */ description: string; /** - * Icon for the agent shown in UI. + * Icon for the participant shown in UI. */ iconPath?: Uri | { /** @@ -260,27 +258,27 @@ declare module 'vscode' { } | ThemeIcon; /** - * The handler for requests to this agent. + * The handler for requests to this participant. */ - requestHandler: ChatAgentRequestHandler; + requestHandler: ChatRequestHandler; /** - * This provider will be called to retrieve the agent's commands. + * This provider will be called to retrieve the participant's commands. */ - commandProvider?: ChatAgentCommandProvider; + commandProvider?: ChatCommandProvider; /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: ChatAgentFollowupProvider; + followupProvider?: ChatFollowupProvider; /** - * When the user clicks this agent in `/help`, this text will be submitted to this command + * When the user clicks this participant in `/help`, this text will be submitted to this command */ sampleRequest?: string; /** - * Whether invoking the agent puts the chat into a persistent mode, where the agent is automatically added to the chat input for the next message. + * Whether invoking the participant puts the chat into a persistent mode, where the participant is automatically added to the chat input for the next message. */ isSticky?: boolean; @@ -288,13 +286,13 @@ declare module 'vscode' { * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes * a result. * - * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was - * previously returned from this chat agent. + * The passed {@link ChatResultFeedback.result result} is guaranteed to be the same instance that was + * previously returned from this chat participant. */ - onDidReceiveFeedback: Event; + onDidReceiveFeedback: Event; /** - * Dispose this agent and free resources + * Dispose this participant and free resources */ dispose(): void; } @@ -302,7 +300,7 @@ declare module 'vscode' { /** * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. */ - export interface ChatAgentResolvedVariable { + export interface ChatResolvedVariable { /** * The name of the variable. @@ -313,7 +311,7 @@ declare module 'vscode' { readonly name: string; /** - * The start and end index of the variable in the {@link ChatAgentRequest.prompt prompt}. + * The start and end index of the variable in the {@link ChatRequest.prompt prompt}. * * *Note* that the indices take the leading `#`-character into account which means they can * used to modify the prompt as-is. @@ -324,47 +322,47 @@ declare module 'vscode' { readonly values: ChatVariableValue[]; } - export interface ChatAgentRequest { + export interface ChatRequest { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}. + * Information about variables used in this request are is stored in {@link ChatRequest.variables}. * - * *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command} + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. */ readonly prompt: string; /** - * The name of the {@link ChatAgentCommand command} that was selected for this request. + * The name of the {@link ChatCommand command} that was selected for this request. */ readonly command: string | undefined; /** * The list of variables and their values that are referenced in the prompt. * - * *Note* that the prompt contains varibale references as authored and that it is up to the agent + * *Note* that the prompt contains varibale references as authored and that it is up to the participant * to further modify the prompt, for instance by inlining variable values or creating links to * headings which contain the resolved values. vvariables are sorted in reverse by their range * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies * string-manipulation of the prompt. */ // TODO@API Q? are there implicit variables that are not part of the prompt? - readonly variables: readonly ChatAgentResolvedVariable[]; + readonly variables: readonly ChatResolvedVariable[]; } - export interface ChatAgentResponseStream { + export interface ChatResponseStream { /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. * - * @see {@link ChatAgentResponseStream.push} + * @see {@link ChatResponseStream.push} * @param value A markdown string or a string that should be interpreted as markdown. * @returns This stream. */ - markdown(value: string | MarkdownString): ChatAgentResponseStream; + markdown(value: string | MarkdownString): ChatResponseStream; /** * Push an anchor part to this stream. Short-hand for @@ -374,7 +372,7 @@ declare module 'vscode' { * @param title An optional title that is rendered with value * @returns This stream. */ - anchor(value: Uri | Location, title?: string): ChatAgentResponseStream; + anchor(value: Uri | Location, title?: string): ChatResponseStream; /** * Push a command button part to this stream. Short-hand for @@ -383,7 +381,7 @@ declare module 'vscode' { * @param command A Command that will be executed when the button is clicked. * @returns This stream. */ - button(command: Command): ChatAgentResponseStream; + button(command: Command): ChatResponseStream; /** * Push a filetree part to this stream. Short-hand for @@ -393,7 +391,7 @@ declare module 'vscode' { * @param baseUri The base uri to which this file tree is relative to. * @returns This stream. */ - filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatAgentResponseStream; + filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatResponseStream; /** * Push a progress part to this stream. Short-hand for @@ -405,7 +403,7 @@ declare module 'vscode' { // TODO@API is this always inline or not // TODO@API is this markdown or string? // TODO@API this influences the rendering, it inserts new lines which is likely a bug - progress(value: string): ChatAgentResponseStream; + progress(value: string): ChatResponseStream; /** * Push a reference to this stream. Short-hand for @@ -418,14 +416,14 @@ declare module 'vscode' { */ // TODO@API support non-file uris, like http://example.com // TODO@API support mapped edits - reference(value: Uri | Location): ChatAgentResponseStream; + reference(value: Uri | Location): ChatResponseStream; /** * Pushes a part to this stream. * * @param part A response part, rendered or metadata */ - push(part: ChatResponsePart): ChatAgentResponseStream; + push(part: ChatResponsePart): ChatResponseStream; } // TODO@API should the name suffix differentiate between rendered items (XYZPart) @@ -483,17 +481,17 @@ declare module 'vscode' { export namespace chat { /** - * Create a new {@link ChatAgent2 chat agent} instance. + * Create a new {@link ChatParticipant chat participant} instance. * - * @param name Short name by which the agent is referred to in the UI. The name must be unique for the extension - * contributing the agent but can collide with names from other extensions. - * @param handler A request handler for the agent. - * @returns A new chat agent + * @param name Short name by which the participant is referred to in the UI. The name must be unique for the extension + * contributing the participant but can collide with names from other extensions. + * @param handler A request handler for the participant. + * @returns A new chat participant */ - export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2; + export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant; /** - * Register a variable which can be used in a chat request to any agent. + * Register a variable which can be used in a chat request to any participant. * @param name The name of the variable, to be used in the chat input as `#name`. * @param description A description of the variable for the chat input suggest widget. * @param resolver Will be called to provide the chat variable's value when it is used. @@ -519,7 +517,7 @@ declare module 'vscode' { level: ChatVariableLevel; /** - * The variable's value, which can be included in an LLM prompt as-is, or the chat agent may decide to read the value and do something else with it. + * The variable's value, which can be included in an LLM prompt as-is, or the chat participant may decide to read the value and do something else with it. */ value: string | Uri; diff --git a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts similarity index 63% rename from src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts rename to src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 40955904331c7..f9ed2a208bd62 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -5,70 +5,70 @@ declare module 'vscode' { - export interface ChatAgent2 { - onDidPerformAction: Event; + export interface ChatParticipant { + onDidPerformAction: Event; supportIssueReporting?: boolean; } - export interface ChatAgentErrorDetails { + export interface ChatErrorDetails { /** - * If set to true, the message content is completely hidden. Only ChatAgentErrorDetails#message will be shown. + * If set to true, the message content is completely hidden. Only ChatErrorDetails#message will be shown. */ responseIsRedacted?: boolean; } /** @deprecated */ - export interface ChatAgentMarkdownContent { + export interface ChatMarkdownContent { markdownContent: MarkdownString; } // TODO@API fit this into the stream - export interface ChatAgentDetectedAgent { - agentName: string; - command?: ChatAgentCommand; + export interface ChatDetectedParticipant { + participant: string; + command?: ChatCommand; } // TODO@API fit this into the stream - export interface ChatAgentVulnerability { + export interface ChatVulnerability { title: string; description: string; // id: string; // Later we will need to be able to link these across multiple content chunks. } // TODO@API fit this into the stream - export interface ChatAgentContent { - vulnerabilities?: ChatAgentVulnerability[]; + export interface ChatContent { + vulnerabilities?: ChatVulnerability[]; } /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentContentProgress = - | ChatAgentContent - | ChatAgentInlineContentReference - | ChatAgentCommandButton; + export type ChatContentProgress = + | ChatContent + | ChatInlineContentReference + | ChatCommandButton; /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentMetadataProgress = - | ChatAgentUsedContext - | ChatAgentContentReference - | ChatAgentProgressMessage; + export type ChatMetadataProgress = + | ChatUsedContext + | ChatContentReference + | ChatProgressMessage; /** - * @deprecated use ChatAgentResponseStream instead + * @deprecated use ChatResponseStream instead */ - export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + export type ChatProgress = ChatContentProgress | ChatMetadataProgress; /** @deprecated */ - export interface ChatAgentProgressMessage { + export interface ChatProgressMessage { message: string; } /** @deprecated */ - export interface ChatAgentContentReference { + export interface ChatContentReference { /** * The resource that was referenced. */ @@ -78,7 +78,7 @@ declare module 'vscode' { /** * A reference to a piece of content that will be rendered inline with the markdown content. */ - export interface ChatAgentInlineContentReference { + export interface ChatInlineContentReference { /** * The resource being referenced. */ @@ -93,62 +93,62 @@ declare module 'vscode' { /** * Displays a {@link Command command} as a button in the chat response. */ - export interface ChatAgentCommandButton { + export interface ChatCommandButton { command: Command; } /** * A piece of the chat response's content. Will be merged with other progress pieces as needed, and rendered as markdown. */ - export interface ChatAgentContent { + export interface ChatContent { /** * The content as a string of markdown source. */ content: string; } - export interface ChatAgentDocumentContext { + export interface ChatDocumentContext { uri: Uri; version: number; ranges: Range[]; } // TODO@API fit this into the stream - export interface ChatAgentUsedContext { - documents: ChatAgentDocumentContext[]; + export interface ChatUsedContext { + documents: ChatDocumentContext[]; } - export interface ChatAgentResponseStream { + export interface ChatResponseStream { /** * @deprecated use above methods instread */ - report(value: ChatAgentProgress): void; + report(value: ChatProgress): void; } /** @deprecated */ - export type ChatAgentExtendedProgress = ChatAgentProgress - | ChatAgentMarkdownContent - | ChatAgentDetectedAgent; + export type ChatExtendedProgress = ChatProgress + | ChatMarkdownContent + | ChatDetectedParticipant; - export type ChatAgentExtendedResponseStream = ChatAgentResponseStream & { + export type ChatExtendedResponseStream = ChatResponseStream & { /** * @deprecated */ - report(value: ChatAgentExtendedProgress): void; + report(value: ChatExtendedProgress): void; }; - export interface ChatAgent2 { + export interface ChatParticipant { /** - * Provide a set of variables that can only be used with this agent. + * Provide a set of variables that can only be used with this participant. */ - agentVariableProvider?: { provider: ChatAgentCompletionItemProvider; triggerCharacters: string[] }; + participantVariableProvider?: { provider: ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; } - export interface ChatAgentCompletionItemProvider { - provideCompletionItems(query: string, token: CancellationToken): ProviderResult; + export interface ChatParticipantCompletionItemProvider { + provideCompletionItems(query: string, token: CancellationToken): ProviderResult; } - export class ChatAgentCompletionItem { + export class ChatCompletionItem { label: string | CompletionItemLabel; values: ChatVariableValue[]; insertText?: string; @@ -158,36 +158,36 @@ declare module 'vscode' { constructor(label: string | CompletionItemLabel, values: ChatVariableValue[]); } - export type ChatAgentExtendedRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentExtendedResponseStream, token: CancellationToken) => ProviderResult; + export type ChatExtendedRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatExtendedResponseStream, token: CancellationToken) => ProviderResult; export namespace chat { /** - * Create a chat agent with the extended progress type + * Create a chat participant with the extended progress type */ - export function createChatAgent(name: string, handler: ChatAgentExtendedRequestHandler): ChatAgent2; + export function createChatParticipant(name: string, handler: ChatExtendedRequestHandler): ChatParticipant; } /* * User action events */ - export enum ChatAgentCopyKind { + export enum ChatCopyKind { // Keyboard shortcut or context menu Action = 1, Toolbar = 2 } - export interface ChatAgentCopyAction { + export interface ChatCopyAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'copy'; codeBlockIndex: number; - copyKind: ChatAgentCopyKind; + copyKind: ChatCopyKind; copiedCharacters: number; totalCharacters: number; copiedText: string; } - export interface ChatAgentInsertAction { + export interface ChatInsertAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'insert'; codeBlockIndex: number; @@ -195,33 +195,33 @@ declare module 'vscode' { newFile?: boolean; } - export interface ChatAgentTerminalAction { + export interface ChatTerminalAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'runInTerminal'; codeBlockIndex: number; languageId?: string; } - export interface ChatAgentCommandAction { + export interface ChatCommandAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'command'; - commandButton: ChatAgentCommandButton; + commandButton: ChatCommandButton; } - export interface ChatAgentFollowupAction { + export interface ChatFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; - followup: ChatAgentFollowup; + followup: ChatFollowup; } - export interface ChatAgentBugReportAction { + export interface ChatBugReportAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'bug'; } - export interface ChatAgentUserActionEvent { - readonly result: ChatAgentResult2; - readonly action: ChatAgentCopyAction | ChatAgentInsertAction | ChatAgentTerminalAction | ChatAgentCommandAction | ChatAgentFollowupAction | ChatAgentBugReportAction; + export interface ChatUserActionEvent { + readonly result: ChatResult; + readonly action: ChatCopyAction | ChatInsertAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction; } export interface ChatVariableValue { @@ -231,7 +231,7 @@ declare module 'vscode' { kind?: string; } - export interface ChatAgentCommand { + export interface ChatCommand { readonly isSticky2?: { /** * Indicates that the command should be automatically repopulated. diff --git a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts similarity index 53% rename from src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts rename to src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index 3a4c675a96eb1..1c71b40e122db 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -5,35 +5,35 @@ declare module 'vscode' { - export type ChatAgentWelcomeMessageContent = string | MarkdownString; + export type ChatWelcomeMessageContent = string | MarkdownString; - export interface ChatAgentWelcomeMessageProvider { - provideWelcomeMessage(token: CancellationToken): ProviderResult; - provideSampleQuestions?(token: CancellationToken): ProviderResult; + export interface ChatWelcomeMessageProvider { + provideWelcomeMessage(token: CancellationToken): ProviderResult; + provideSampleQuestions?(token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatParticipant { /** - * When true, this agent is invoked by default when no other agent is being invoked + * When true, this participant is invoked by default when no other participant is being invoked */ isDefault?: boolean; /** - * When true, this agent is invoked when the user submits their query using ctrl/cmd+enter + * When true, this participant is invoked when the user submits their query using ctrl/cmd+enter * TODO@API name */ isSecondary?: boolean; /** - * A string that will be added before the listing of chat agents in `/help`. + * A string that will be added before the listing of chat participants in `/help`. */ helpTextPrefix?: string | MarkdownString; /** - * A string that will be appended after the listing of chat agents in `/help`. + * A string that will be appended after the listing of chat participants in `/help`. */ helpTextPostfix?: string | MarkdownString; - welcomeMessageProvider?: ChatAgentWelcomeMessageProvider; + welcomeMessageProvider?: ChatWelcomeMessageProvider; } } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index bd580590d68f6..5644d00ce833e 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -125,7 +125,7 @@ declare module 'vscode' { inputPlaceholder?: string; } - export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatAgentFollowup[]; + export type InteractiveWelcomeMessageContent = string | MarkdownString | ChatFollowup[]; export interface InteractiveSessionProvider { prepareSession(token: CancellationToken): ProviderResult; From 875855a4445749034dfccd596f3c5234481c1e40 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 19 Feb 2024 15:26:08 +0100 Subject: [PATCH 0458/1863] fix: memory leak in code editor widget (#205488) (#205542) Documentation: CSS setting is incorrect. --- extensions/css-language-features/package.nls.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index 5e9129c84e17e..d6e25a57a43a1 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -12,7 +12,7 @@ "css.lint.emptyRules.desc": "Do not use empty rulesets.", "css.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "css.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "css.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "css.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "css.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "css.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "css.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", @@ -47,7 +47,7 @@ "less.lint.emptyRules.desc": "Do not use empty rulesets.", "less.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "less.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "less.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "less.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "less.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "less.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "less.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", @@ -81,7 +81,7 @@ "scss.lint.emptyRules.desc": "Do not use empty rulesets.", "scss.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", "scss.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", - "scss.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", + "scss.lint.hexColorLength.desc": "Hex colors must consist of 3, 4, 6 or 8 hex numbers.", "scss.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "scss.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "scss.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", From c28d1668eeccc0c4f895fea3554329b8bc341984 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:35:05 +0100 Subject: [PATCH 0459/1863] rename widget: clean up computing cursor-box top --- .../contrib/rename/browser/renameInputField.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 4f9f1b15be8b8..d620226129bae 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -180,9 +180,7 @@ export class RenameInputField implements IContentWidget { const bodyBox = getClientArea(this.getDomNode().ownerDocument.body); const editorBox = getDomNodePagePosition(this._editor.getDomNode()); - // FIXME@ulugbekna: can getVisibleRanges() be empty? if so what to do about it - const firstLineInViewport = this._editor.getVisibleRanges()[0].startLineNumber; - const cursorBoxTop = this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + const cursorBoxTop = this._getTopForPosition(); this._nPxAvailableAbove = cursorBoxTop + editorBox.top; this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; @@ -391,6 +389,18 @@ export class RenameInputField implements IContentWidget { this._editor.layoutContentWidget(this); } + private _getTopForPosition(): number { + const visibleRanges = this._editor.getVisibleRanges(); + let firstLineInViewport: number; + if (visibleRanges.length > 0) { + firstLineInViewport = visibleRanges[0].startLineNumber; + } else { + this._logService.warn('RenameInputField#_getTopForPosition: this should not happen - visibleRanges is empty'); + firstLineInViewport = Math.max(1, this._position!.lineNumber - 5); // @ulugbekna: fallback to current line minus 5 + } + return this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport); + } + private _trace(...args: any[]) { this._logService.trace('RenameInputField', ...args); } From 47becdc1522087833766792fb84854daab7b9f04 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:44:53 +0100 Subject: [PATCH 0460/1863] rename suggestions: add `dispose()` to `CandidatesView` --- .../contrib/rename/browser/renameInputField.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index d620226129bae..292c37d6bf1ef 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -107,15 +107,15 @@ export class RenameInputField implements IContentWidget { this._input.className = 'rename-input'; this._input.type = 'text'; this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); - // TODO@ulugbekna: is using addDisposableListener's right way to do it? this._disposables.add(addDisposableListener(this._input, 'focus', () => { this._focusedContextKey.set(true); })); this._disposables.add(addDisposableListener(this._input, 'blur', () => { this._focusedContextKey.reset(); })); this._domNode.appendChild(this._input); - this._candidatesView = new CandidatesView(this._domNode, { - fontInfo: this._editor.getOption(EditorOption.fontInfo), - onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now - }); + this._candidatesView = this._disposables.add( + new CandidatesView(this._domNode, { + fontInfo: this._editor.getOption(EditorOption.fontInfo), + onSelectionChange: () => this.acceptInput(false) // we don't allow preview with mouse click for now + })); this._label = document.createElement('div'); this._label.className = 'rename-label'; @@ -562,6 +562,10 @@ export class CandidatesView { } return focusedIx > 0; } + + dispose() { + this._listWidget.dispose(); + } } export class CandidateView { // TODO@ulugbekna: remove export From 372baec4acad52c05e6d051ac940d00fd972f146 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 15:49:41 +0100 Subject: [PATCH 0461/1863] rename suggestions: clean up --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 292c37d6bf1ef..60049237806c5 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -406,7 +406,7 @@ export class RenameInputField implements IContentWidget { } } -export class CandidatesView { +class CandidatesView { private readonly _listWidget: List; private readonly _listContainer: HTMLDivElement; @@ -568,7 +568,7 @@ export class CandidatesView { } } -export class CandidateView { // TODO@ulugbekna: remove export +class CandidateView { // TODO@ulugbekna: accessibility From 11b3d6b71a638e8ac6b8dfa6cf756bd0005e3a48 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 19 Feb 2024 15:39:11 +0100 Subject: [PATCH 0462/1863] Fixes #205446 --- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index cffa05225e621..1cb31624f0652 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -92,7 +92,10 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor /** @description Updates name */ const resources = this._resources.read(reader) ?? []; const label = this.label ?? localize('name', "Multi Diff Editor"); - this._name = label + localize('files', " ({0} files)", resources?.length ?? 0); + this._name = label + localize({ + key: 'files', + comment: ['the number of files being shown'] + }, " ({0} files)", resources?.length ?? 0); this._onDidChangeLabel.fire(); })); } From 43d55cbf884234f8421fa07f59f2f0fe2d534d98 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:03:48 +0100 Subject: [PATCH 0463/1863] Localize Inno Updater (#205279) * without translations * Update messages.en.isl * translations, hopefully correct encoding * inno updater v0.11.0 --- build/win32/Cargo.lock | 2 +- build/win32/Cargo.toml | 2 +- build/win32/code.iss | 2 +- build/win32/i18n/messages.de.isl | 3 ++- build/win32/i18n/messages.en.isl | 1 + build/win32/i18n/messages.es.isl | 1 + build/win32/i18n/messages.fr.isl | 3 ++- build/win32/i18n/messages.hu.isl | 3 ++- build/win32/i18n/messages.it.isl | 3 ++- build/win32/i18n/messages.ja.isl | 3 ++- build/win32/i18n/messages.ko.isl | 3 ++- build/win32/i18n/messages.pt-br.isl | 3 ++- build/win32/i18n/messages.ru.isl | 3 ++- build/win32/i18n/messages.tr.isl | 3 ++- build/win32/i18n/messages.zh-cn.isl | 3 ++- build/win32/i18n/messages.zh-tw.isl | 3 ++- build/win32/inno_updater.exe | Bin 464896 -> 451072 bytes 17 files changed, 27 insertions(+), 14 deletions(-) diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index fb5217556906a..18edefc752d97 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -109,7 +109,7 @@ dependencies = [ [[package]] name = "inno_updater" -version = "0.10.1" +version = "0.11.0" dependencies = [ "byteorder", "crc", diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml index cf3cc9de80be5..3925505c22525 100644 --- a/build/win32/Cargo.toml +++ b/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.10.1" +version = "0.11.0" authors = ["Microsoft "] build = "build.rs" diff --git a/build/win32/code.iss b/build/win32/code.iss index f8d231f58583f..fca3d1e9d9b85 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1519,7 +1519,7 @@ begin StopTunnelServiceIfNeeded(); - Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists()) + ' "{cm:UpdatingVisualStudioCode}"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); end; if ShouldRestartTunnelService then diff --git a/build/win32/i18n/messages.de.isl b/build/win32/i18n/messages.de.isl index 6a9f29aa9c27e..8d065e6c10ac1 100644 --- a/build/win32/i18n/messages.de.isl +++ b/build/win32/i18n/messages.de.isl @@ -6,4 +6,5 @@ AddToPath=Zu PATH hinzuf RunAfter=%1 nach der Installation ausf�hren Other=Andere: SourceFile=%1-Quelldatei -OpenWithCodeContextMenu=Mit %1 �ffnen \ No newline at end of file +OpenWithCodeContextMenu=Mit %1 �ffnen +UpdatingVisualStudioCode=Visual Studio Code wird aktualisiert... \ No newline at end of file diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index 986eba00d3e58..a5cc58201543e 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -14,3 +14,4 @@ RunAfter=Run %1 after installation Other=Other: SourceFile=%1 Source File OpenWithCodeContextMenu=Open w&ith %1 +UpdatingVisualStudioCode=Updating Visual Studio Code... diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index 0ba4d0c44f2b0..66b7534a20770 100644 --- a/build/win32/i18n/messages.es.isl +++ b/build/win32/i18n/messages.es.isl @@ -7,3 +7,4 @@ RunAfter=Ejecutar %1 despu Other=Otros: SourceFile=Archivo de origen %1 OpenWithCodeContextMenu=Abrir &con %1 +UpdatingVisualStudioCode=Actualizando Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.fr.isl b/build/win32/i18n/messages.fr.isl index df14041862513..348d6be00495f 100644 --- a/build/win32/i18n/messages.fr.isl +++ b/build/win32/i18n/messages.fr.isl @@ -6,4 +6,5 @@ AddToPath=Ajouter RunAfter=Ex�cuter %1 apr�s l'installation Other=Autre�: SourceFile=Fichier source %1 -OpenWithCodeContextMenu=Ouvrir avec %1 \ No newline at end of file +OpenWithCodeContextMenu=Ouvrir avec %1 +UpdatingVisualStudioCode=Mise � jour de Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.hu.isl b/build/win32/i18n/messages.hu.isl index b64553da8e6de..ef3862ad35b10 100644 --- a/build/win32/i18n/messages.hu.isl +++ b/build/win32/i18n/messages.hu.isl @@ -6,4 +6,5 @@ AddToPath=Hozz RunAfter=%1 ind�t�sa a telep�t�s ut�n Other=Egy�b: SourceFile=%1 forr�sf�jl -OpenWithCodeContextMenu=Megnyit�s a k�vetkez�vel: %1 \ No newline at end of file +OpenWithCodeContextMenu=Megnyit�s a k�vetkez�vel: %1 +UpdatingVisualStudioCode=A Visual Studio Code friss�t�se... \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index 08248c4ce1ba8..bc23825844a3b 100644 --- a/build/win32/i18n/messages.it.isl +++ b/build/win32/i18n/messages.it.isl @@ -6,4 +6,5 @@ AddToPath=Aggiungi a PATH (disponibile dopo il riavvio) RunAfter=Esegui %1 dopo l'installazione Other=Altro: SourceFile=File di origine %1 -OpenWithCodeContextMenu=Apri con %1 \ No newline at end of file +OpenWithCodeContextMenu=Apri con %1 +UpdatingVisualStudioCode=Aggiornamento di Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.ja.isl b/build/win32/i18n/messages.ja.isl index 9675060e94afd..ef10366b46993 100644 --- a/build/win32/i18n/messages.ja.isl +++ b/build/win32/i18n/messages.ja.isl @@ -6,4 +6,5 @@ AddToPath=PATH RunAfter=�C���X�g�[����� %1 �����s���� Other=���̑�: SourceFile=%1 �\�[�X �t�@�C�� -OpenWithCodeContextMenu=%1 �ŊJ�� \ No newline at end of file +OpenWithCodeContextMenu=%1 �ŊJ�� +UpdatingVisualStudioCode=Visual Studio Code ���X�V���Ă��܂�... \ No newline at end of file diff --git a/build/win32/i18n/messages.ko.isl b/build/win32/i18n/messages.ko.isl index 5a510558bbd2b..f938c75e289b1 100644 --- a/build/win32/i18n/messages.ko.isl +++ b/build/win32/i18n/messages.ko.isl @@ -6,4 +6,5 @@ AddToPath=PATH RunAfter=��ġ �� %1 ���� Other=��Ÿ: SourceFile=%1 ���� ���� -OpenWithCodeContextMenu=%1(��)�� ���� \ No newline at end of file +OpenWithCodeContextMenu=%1(��)�� ���� +UpdatingVisualStudioCode=Visual Studio Code ������Ʈ ��... \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index e327e8fd1a066..e85aede38622a 100644 --- a/build/win32/i18n/messages.pt-br.isl +++ b/build/win32/i18n/messages.pt-br.isl @@ -6,4 +6,5 @@ AddToPath=Adicione em PATH (dispon RunAfter=Executar %1 ap�s a instala��o Other=Outros: SourceFile=Arquivo Fonte %1 -OpenWithCodeContextMenu=Abrir com %1 \ No newline at end of file +OpenWithCodeContextMenu=Abrir com %1 +UpdatingVisualStudioCode=Atualizando o Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.ru.isl b/build/win32/i18n/messages.ru.isl index bca3b864a5f9e..2b1d906e55dd4 100644 --- a/build/win32/i18n/messages.ru.isl +++ b/build/win32/i18n/messages.ru.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=��������� %1 ����� ��������� Other=������: SourceFile=�������� ���� %1 -OpenWithCodeContextMenu=������� � ������� %1 \ No newline at end of file +OpenWithCodeContextMenu=������� � ������� %1 +UpdatingVisualStudioCode=���������� Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.tr.isl b/build/win32/i18n/messages.tr.isl index b13e5e27bd2af..5eff39c24a76a 100644 --- a/build/win32/i18n/messages.tr.isl +++ b/build/win32/i18n/messages.tr.isl @@ -6,4 +6,5 @@ AddToPath=PATH'e ekle (yeniden ba RunAfter=Kurulumdan sonra %1 uygulamas�n� �al��t�r. Other=Di�er: SourceFile=%1 Kaynak Dosyas� -OpenWithCodeContextMenu=%1 �le A� \ No newline at end of file +OpenWithCodeContextMenu=%1 �le A� +UpdatingVisualStudioCode=Visual Studio Code g�ncelleniyor... \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-cn.isl b/build/win32/i18n/messages.zh-cn.isl index 8fa136f6d5a9b..629bf9ea40135 100644 --- a/build/win32/i18n/messages.zh-cn.isl +++ b/build/win32/i18n/messages.zh-cn.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=��װ������ %1 Other=����: SourceFile=%1 Դ�ļ� -OpenWithCodeContextMenu=ͨ�� %1 �� \ No newline at end of file +OpenWithCodeContextMenu=ͨ�� %1 �� +UpdatingVisualStudioCode=���ڸ��� Visual Studio Code... \ No newline at end of file diff --git a/build/win32/i18n/messages.zh-tw.isl b/build/win32/i18n/messages.zh-tw.isl index 40c5fa92d793e..8ed1f5a5061d7 100644 --- a/build/win32/i18n/messages.zh-tw.isl +++ b/build/win32/i18n/messages.zh-tw.isl @@ -6,4 +6,5 @@ AddToPath= RunAfter=�w�˫���� %1 Other=��L: SourceFile=%1 �ӷ��ɮ� -OpenWithCodeContextMenu=�H %1 �}�� \ No newline at end of file +OpenWithCodeContextMenu=�H %1 �}�� +UpdatingVisualStudioCode=���b��s Visual Studio Code... \ No newline at end of file diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index fa2fd26a466dedee7755c873a88cdb7168c38737..b87cbd47f24f5d235de44db8d5ef3f2aa180bfdc 100644 GIT binary patch literal 451072 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 Lz@_z{9RH2mp5|MNdvx!hjRWtX4U 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∋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
    `; - } - 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 b5e2de0ed521b..9f6cc07ba5f90 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 0663/1863] 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 7200372acca8c..650b852e1e438 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 0664/1863] 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 62977ee07e3b1..331aba1b55f45 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 0665/1863] 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 f943347519e1d..06aefade7adf3 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 0666/1863] 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 4ee4c07f0cf6c..b1ef64dae9d97 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 6d89b981afc0c..e28b1e14e281d 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 0667/1863] 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 16550bdef8d13..01eb0df109ed2 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 538c754e1b331..f885f89333a65 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 29b99b8cc419b..b512d5da601d3 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 0f04b3f2455b9..18764746b774e 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 1072a60aedfc6..2d9dcdde5e668 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 4faf49116c0ef..1f795367c89ab 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 76ffed1feeb4a..48e060c3e8f33 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 0000000000000..7a4f22aecf7a0 --- /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 0668/1863] 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 698d711f4dc52..df6ec99660c42 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 c2b41545d793a..5aaa8bcc55198 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 5bf35b8ffc28f..c0e0544a93baa 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 465c1dc1181a6..b59529ffd8128 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 4f5eb5ca01519..fe2ceb43d61ff 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 ec127689cb02a..bb76dd67d6cc0 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 2b455fa8dc6db..a52cad5c5f0e3 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 c5fd28d6a8e75..e45a95008c3ff 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 c4f2a2def5a92..3d9af339abecf 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 139928ec9fe21..b9d319bad857a 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 bb63d92e9d2b6..44467e7e01df1 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 180a378ac813d..a3556f80678ea 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 0669/1863] 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 797a4ee92a4c8..236148f08ffd9 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 65b718f3f0503..20b128c9af4c0 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 b512d5da601d3..869fc7dc50dc8 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 d0289669abf7e..c2e0b93f7b902 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 1d903c4081517..1502ef3f565ae 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 24e2448a9b3cb..17f9e88b338f4 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 48e060c3e8f33..7e70af867c335 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 ca93adc467b7a..0ebb5a9f69db0 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 6d037174bed4a..48da6b3f96d36 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 ac934456627d5..42ffb1111edf4 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 cb4af3dd4e9ab..eb83a7ae27ee7 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 42cb5e59e803e..34cf4cce31174 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 0000000000000..93f9761211b71 --- /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 0670/1863] 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 af49847a810f2..a7debba4e6c78 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 f3d6b5c9f1628..38357608b8607 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 10158c8d75cc4..87c58811d8d52 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 0671/1863] 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 8ac086cada8c3..9b18b5cd02829 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 8b22da1420429..45a4086cc9eb6 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 277a619ddc7a0..0bf6401663db5 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 0672/1863] 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 20b128c9af4c0..1b9c7eb2b082b 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 aa45f8f58aaad..e51c875984d63 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 816f02273615a..68cbe33049269 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 4fb94e3f1c850..4eec0964cd28c 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 7a9e140b5a5b7..b6aa1ffdadfe2 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 258a5bf18e9ad..59b053f149d9a 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 0673/1863] 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 1b9c7eb2b082b..8a99d63cb4c30 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 e51c875984d63..ea10161e75f36 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 4eec0964cd28c..2e1ff171d1f87 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 59b053f149d9a..775e4d5e1dc46 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 0674/1863] 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 3a44ead957b66..3dd6ec94d023c 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 0675/1863] 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 75ca16a1f249c..ae407d07b67c1 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 878246a2e26b8..d7e2d3850d310 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 0676/1863] 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 4ad9e6548079e..2d514b09b39a2 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 996a8a2e65792..69ca28691de6d 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 4e677142d69a4..1119354a07a14 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 8dc11b1af6b86..d79928406dd16 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 6c99db8759917..d03d8c9b492e1 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 8f4ca1d9d6adc..65cf6d01bc33c 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 e2de1a220961f..58807d6a591ff 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 dd421641fb8fc..15b3d151ca367 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 0c60dbd1e6825..e58e399be2f3b 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 dfd7c6fc5b47b..c115a9927af3c 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 6514979da3dc8..2df70a23b3276 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 `

    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%0AhF8_-S$ zAdb#Tp|^6u!R2Zd{HKY{(XGbAanj&5Spk!&hPyPwSVDRaS8;eJFqb|n(<`YvpR;t4 z=12@sS0?+Uh}`t%0B!9x+L<)IbLj7Oh5pMXt-qXJtK(HsW58xYV69~SZ5vr^i844$ z6+PBK^>(lqLeyz8n;?ZMoQt|$z|B=Lm1;rgJxr$?bdbosrGo+rskCb5{SMH2n{zU# ztpqe)B|uw6pe&!^w1B7D13cwV{4^Znd}^+kOnn*n$iKrTxJ)-ZCJE&?gB+Rv3aJz< zU->I@sfW8XkwQoPZH)^4Wlk~lbt<=+0dDf|vjjxY3^Vclt!QpjAzzlkZH78}{&^p3 i3H`R0I literal 464896 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 z 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νDc0~ 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 z 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 dfd4aKNDE0aFS_y5EW{2Q&4>}>!5 From b341631192428bf13e3131390c06fa9bd384c0ea Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 16:22:26 +0100 Subject: [PATCH 0464/1863] rename suggestions: dispose listeners & clear focused-elements list on blur --- .../contrib/rename/browser/renameInputField.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 60049237806c5..e02e89b8f4208 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -414,8 +414,12 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; + private _disposables: DisposableStore; + constructor(parent: HTMLElement, opts: { fontInfo: FontInfo; onSelectionChange: () => void }) { + this._disposables = new DisposableStore(); + this._availableHeight = 0; this._lineHeight = opts.fontInfo.lineHeight; @@ -466,11 +470,15 @@ class CandidatesView { } ); - this._listWidget.onDidChangeSelection(e => { + this._disposables.add(this._listWidget.onDidChangeSelection(e => { if (e.elements.length > 0) { opts.onSelectionChange(); } - }); + })); + + this._disposables.add(this._listWidget.onDidBlur(e => { + this._listWidget.setFocus([]); + })); this._listWidget.style(defaultListStyles); } @@ -565,6 +573,7 @@ class CandidatesView { dispose() { this._listWidget.dispose(); + this._disposables.dispose(); } } From b6feb4ad668df02d175ae85ae9c87122380f0df4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 19 Feb 2024 16:52:44 +0100 Subject: [PATCH 0465/1863] Update style of settings in release notes (#205551) * WIP for updating styling of release notes button * Keyboard nav + fix test --- .../browser/markdownSettingRenderer.ts | 10 +- .../browser/markdownSettingRenderer.test.ts | 7 +- .../update/browser/releaseNotesEditor.ts | 154 +++++++++--------- 3 files changed, 85 insertions(+), 86 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 62bd755a1bfad..1d767009ce236 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,7 +14,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; -const codeSettingRegex = /^/; +const codeSettingRegex = /^/; +const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -40,7 +41,7 @@ export class SimpleSettingRenderer { getHtmlRenderer(): (html: string) => string { return (html): string => { - const match = codeSettingRegex.exec(html); + const match = codeSettingRegex.exec(html) ?? codeFeatureRegex.exec(html); if (match && match.length === 4) { const settingId = match[2]; const rendered = this.render(settingId, match[3], match[1] === 'codefeature'); @@ -165,7 +166,10 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); const title = nls.localize('changeSettingTitle', "Try feature"); - return ``; + return ` + + ${setting.key} + `; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index ed9ac26957794..884e3b50429b8 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -70,10 +70,13 @@ suite('Markdown Setting Renderer Test', () => { test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; + const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ``); + ` + + example.booleanSetting + `); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index de8129bbdf038..3feabd2ea4eb2 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -258,49 +258,92 @@ export class ReleaseNotesManager { ${DEFAULT_MARKDOWN_STYLES} ${css} + /* codesetting */ + + code:has(.codesetting)+code { + display: none; + } + + code:has(.codesetting) { + background-color: var(--vscode-textPreformat-background); + color: var(--vscode-textPreformat-foreground); + padding-left: 1px; + margin-right: 3px; + padding-right: 0px; + } + + code:has(.codesetting):focus { + border: 1px solid var(--vscode-button-border, transparent); + } + .codesetting { - color: var(--vscode-button-foreground); - background-color: var(--vscode-button-background); - width: fit-content; + color: var(--vscode-textPreformat-foreground); padding: 0px 1px 1px 0px; - font-size: 12px; + font-size: 0px; overflow: hidden; text-overflow: ellipsis; outline-offset: 2px !important; box-sizing: border-box; - border-radius: 2px; text-align: center; cursor: pointer; - border: 1px solid var(--vscode-button-border, transparent); - line-height: 9px; + display: inline; + margin-right: 3px; + } + .codesetting svg { + font-size: 12px; + text-align: center; + cursor: pointer; + border: 1px solid var(--vscode-button-secondaryBorder, transparent); outline: 1px solid transparent; + line-height: 9px; + margin-bottom: -5px; + padding-left: 0px; + padding-top: 2px; + padding-bottom: 2px; + padding-right: 2px; display: inline-block; - margin-top: 3px; - margin-bottom: -4px !important; + text-decoration: none; + text-rendering: auto; + text-transform: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + user-select: none; + -webkit-user-select: none; + } + .codesetting .setting-name { + font-size: 13px; + padding-left: 2px; + padding-right: 3px; + padding-top: 1px; + padding-bottom: 1px; + margin-left: -5px; + margin-top: -3px; } .codesetting:hover { - background-color: var(--vscode-button-hoverBackground); + color: var(--vscode-textPreformat-foreground) !important; + text-decoration: none !important; + } + code:has(.codesetting):hover { + filter: brightness(140%); text-decoration: none !important; - color: var(--vscode-button-hoverForeground) !important; } .codesetting:focus { outline: 0 !important; text-decoration: none !important; color: var(--vscode-button-hoverForeground) !important; - border: 1px solid var(--vscode-button-border, transparent); } - .codesetting svg { + .codesetting .separator { + width: 1px; + height: 14px; + margin-bottom: -3px; display: inline-block; - text-decoration: none; - text-rendering: auto; - text-align: center; - text-transform: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - user-select: none; - -webkit-user-select: none; + background-color: var(--vscode-editor-background); + font-size: 12px; + margin-right: 8px; } + /* codefeature */ + .codefeature-container { display: flex; } @@ -357,66 +400,6 @@ export class ReleaseNotesManager { content: "${nls.localize('enableFeature', "Enable this feature")}"; } - .codefeature-container { - display: flex; - } - - .codefeature { - position: relative; - display: inline-block; - width: 58px; - height: 30px; - } - - .codefeature-container input { - display: none; - } - - .toggle { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--vscode-disabledForeground); - transition: .4s; - border-radius: 30px; - } - - .toggle:before { - position: absolute; - content: ""; - height: 22px; - width: 22px; - left: 4px; - bottom: 4px; - background-color: var(--vscode-editor-foreground); - transition: .4s; - border-radius: 50%; - } - - input:checked+.codefeature > .toggle:before { - transform: translateX(26px); - } - - input:checked+.codefeature > .toggle { - background-color: var(--vscode-button-background); - } - - .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; } @@ -486,6 +469,15 @@ export class ReleaseNotesManager { } }); + window.addEventListener('keypress', event => { + if (event.keyCode === 13) { + if (event.target.children.length > 0 && event.target.children[0].href) { + const clientRect = event.target.getBoundingClientRect(); + vscode.postMessage({ type: 'clickSetting', value: { uri: event.target.children[0].href, x: clientRect.right , y: clientRect.bottom }}); + } + } + }); + input.addEventListener('change', event => { vscode.postMessage({ type: 'showReleaseNotes', value: input.checked }, '*'); }); From 9f4d6cf8c51a4c2c4343e81045608e7cfeb8a5aa Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 16:58:52 +0100 Subject: [PATCH 0466/1863] rename suggestions: fix overflowing candidate names - update list widget width --- .../rename/browser/renameInputField.ts | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index e02e89b8f4208..877275cb25f50 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -202,8 +202,7 @@ export class RenameInputField implements IContentWidget { const [accept, preview] = this._acceptKeybindings; this._label!.innerText = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Rename, Shift+F2 to Preview"'] }, "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); - this._domNode!.style.minWidth = `250px`; // to prevent from widening when candidates come in - this._domNode!.style.maxWidth = `400px`; // TODO@ulugbekna: what if we have a very long name? + this._domNode!.style.minWidth = `200px`; // to prevent from widening when candidates come in return null; } @@ -438,7 +437,7 @@ class CandidatesView { } getHeight(element: NewSymbolName): number { - return that.candidateViewHeight; + return that._candidateViewHeight; } }; @@ -483,9 +482,9 @@ class CandidatesView { this._listWidget.style(defaultListStyles); } - public get candidateViewHeight(): number { - const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); - return totalHeight; + dispose() { + this._listWidget.dispose(); + this._disposables.dispose(); } // height - max height allowed by parent element @@ -496,18 +495,12 @@ class CandidatesView { } } - private _pickListHeight(nCandidates: number) { - const heightToFitAllCandidates = this.candidateViewHeight * nCandidates; - const height = Math.min(heightToFitAllCandidates, this._availableHeight, this.candidateViewHeight * 7 /* max # of candidates we want to show at once */); - return height; - } - public setCandidates(candidates: NewSymbolName[]): void { const height = this._pickListHeight(candidates.length); this._listWidget.splice(0, 0, candidates); - this._listWidget.layout(height); + this._listWidget.layout(height, this._pickListWidth(candidates)); this._listContainer.style.height = `${height}px`; } @@ -571,10 +564,21 @@ class CandidatesView { return focusedIx > 0; } - dispose() { - this._listWidget.dispose(); - this._disposables.dispose(); + private get _candidateViewHeight(): number { + const { totalHeight } = CandidateView.getLayoutInfo({ lineHeight: this._lineHeight }); + return totalHeight; + } + + private _pickListHeight(nCandidates: number) { + const heightToFitAllCandidates = this._candidateViewHeight * nCandidates; + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * 7 /* max # of candidates we want to show at once */); + return height; } + + private _pickListWidth(candidates: NewSymbolName[]): number { + return Math.max(...candidates.map(c => c.newSymbolName.length)) * 7 /* approximate # of pixes taken by a single character */; + } + } class CandidateView { From 84ba0e1249f2015da173dd290f310f0844fc3fae Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 19 Feb 2024 16:32:49 +0100 Subject: [PATCH 0467/1863] Add github-project codicon to registry --- src/vs/base/common/codicons.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index c8ab637ebeb7c..27423d734fdd5 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -591,11 +591,13 @@ export const Codicon = { gitStash: register('git-stash', 0xec26), gitStashApply: register('git-stash-apply', 0xec27), gitStashPop: register('git-stash-pop', 0xec28), - coverage: register('coverage', 0xec2e), runAllCoverage: register('run-all-coverage', 0xec2d), runCoverage: register('run-all-coverage', 0xec2c), + coverage: register('coverage', 0xec2e), + githubProject: register('github-project', 0xec2f), // derived icons, that could become separate icons + // TODO: These mappings should go in the vscode-codicons mapping file dialogError: register('dialog-error', 'error'), dialogWarning: register('dialog-warning', 'warning'), From 4145304fd07bb82b1c7289ebda555058674b9a6c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 19 Feb 2024 17:10:34 +0100 Subject: [PATCH 0468/1863] rename suggestions: fix not showing preview when input box is empty but a rename suggestion is focused --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 877275cb25f50..dc5131bba439f 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -298,9 +298,10 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const candidateName = this._candidatesView.focusedCandidate; - if ((candidateName === undefined && this._input.value === value) || this._input.value.trim().length === 0) { - this.cancelInput(true, '_currentAcceptInput (because candidateName is undefined or input.value is empty)'); + const newName = this._candidatesView.focusedCandidate ?? this._input.value; + + if (newName === value || newName.trim().length === 0 /* is just whitespace */) { + this.cancelInput(true, '_currentAcceptInput (because newName === value || newName.trim().length === 0)'); return; } @@ -309,7 +310,7 @@ export class RenameInputField implements IContentWidget { this._candidatesView.clearCandidates(); resolve({ - newName: candidateName ?? this._input.value, + newName, wantsPreview: supportPreview && wantsPreview }); }; From c5327e56eda3cc9cccaf85be6b9f0e30bbf9825b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 16:13:54 +0000 Subject: [PATCH 0469/1863] Show variables in help text (#205553) --- .../api/common/extHostChatAgents2.ts | 15 ++++++++++++ .../contrib/chat/browser/chat.contribution.ts | 23 ++++++++++++++++++- .../contrib/chat/common/chatAgents.ts | 1 + ...scode.proposed.defaultChatParticipant.d.ts | 5 ++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 59a5684877c6f..b3318d5c31491 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -340,6 +340,7 @@ class ExtHostChatAgent { private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; private _isDefault: boolean | undefined; private _helpTextPrefix: string | vscode.MarkdownString | undefined; + private _helpTextVariablesPrefix: string | vscode.MarkdownString | undefined; private _helpTextPostfix: string | vscode.MarkdownString | undefined; private _sampleRequest?: string; private _isSecondary: boolean | undefined; @@ -470,6 +471,7 @@ class ExtHostChatAgent { isDefault: this._isDefault, isSecondary: this._isSecondary, helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), + helpTextVariablesPrefix: (!this._helpTextVariablesPrefix || typeof this._helpTextVariablesPrefix === 'string') ? this._helpTextVariablesPrefix : typeConvert.MarkdownString.from(this._helpTextVariablesPrefix), helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : typeConvert.MarkdownString.from(this._helpTextPostfix), sampleRequest: this._sampleRequest, supportIssueReporting: this._supportIssueReporting, @@ -548,6 +550,19 @@ class ExtHostChatAgent { that._helpTextPrefix = v; updateMetadataSoon(); }, + get helpTextVariablesPrefix() { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + return that._helpTextVariablesPrefix; + }, + set helpTextVariablesPrefix(v) { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); + if (!that._isDefault) { + throw new Error('helpTextVariablesPrefix is only available on the default chat agent'); + } + + that._helpTextVariablesPrefix = v; + updateMetadataSoon(); + }, get helpTextPostfix() { checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._helpTextPostfix; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index c07a0b1afb68c..528810a99a0a2 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -56,7 +56,7 @@ import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/a import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; -import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; @@ -228,6 +228,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { @IChatSlashCommandService slashCommandService: IChatSlashCommandService, @ICommandService commandService: ICommandService, @IChatAgentService chatAgentService: IChatAgentService, + @IChatVariablesService chatVariablesService: IChatVariablesService, ) { super(); this._store.add(slashCommandService.registerSlashCommand({ @@ -246,6 +247,8 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { }, async (prompt, progress) => { const defaultAgent = chatAgentService.getDefaultAgent(); const agents = chatAgentService.getAgents(); + + // Report prefix if (defaultAgent?.metadata.helpTextPrefix) { if (isMarkdownString(defaultAgent.metadata.helpTextPrefix)) { progress.report({ content: defaultAgent.metadata.helpTextPrefix, kind: 'markdownContent' }); @@ -255,6 +258,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { progress.report({ content: '\n\n', kind: 'content' }); } + // Report agent list const agentText = (await Promise.all(agents .filter(a => a.id !== defaultAgent?.id) .map(async a => { @@ -272,6 +276,23 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { return (agentLine + '\n' + commandText).trim(); }))).join('\n'); progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [SubmitAction.ID] } }), kind: 'markdownContent' }); + + // Report variables + if (defaultAgent?.metadata.helpTextVariablesPrefix) { + progress.report({ content: '\n\n', kind: 'content' }); + if (isMarkdownString(defaultAgent.metadata.helpTextVariablesPrefix)) { + progress.report({ content: defaultAgent.metadata.helpTextVariablesPrefix, kind: 'markdownContent' }); + } else { + progress.report({ content: defaultAgent.metadata.helpTextVariablesPrefix, kind: 'content' }); + } + + const variableText = Array.from(chatVariablesService.getVariables()) + .map(v => `* \`${chatVariableLeader}${v.name}\` - ${v.description}`) + .join('\n'); + progress.report({ content: '\n' + variableText, kind: 'content' }); + } + + // Report help text ending if (defaultAgent?.metadata.helpTextPostfix) { progress.report({ content: '\n\n', kind: 'content' }); if (isMarkdownString(defaultAgent.metadata.helpTextPostfix)) { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index a6ef4c6c3592c..3f9eb992697e9 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -70,6 +70,7 @@ export interface IChatAgentMetadata { description?: string; isDefault?: boolean; // The agent invoked when no agent is specified helpTextPrefix?: string | IMarkdownString; + helpTextVariablesPrefix?: string | IMarkdownString; helpTextPostfix?: string | IMarkdownString; isSecondary?: boolean; // Invoked by ctrl/cmd+enter fullName?: string; diff --git a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index 1c71b40e122db..e1c026a655740 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -29,6 +29,11 @@ declare module 'vscode' { */ helpTextPrefix?: string | MarkdownString; + /** + * A string that will be added before the listing of chat variables in `/help`. + */ + helpTextVariablesPrefix?: string | MarkdownString; + /** * A string that will be appended after the listing of chat participants in `/help`. */ From 683205493230f8d65208644527e0600e9ba19aee Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 19 Feb 2024 17:42:12 +0100 Subject: [PATCH 0470/1863] Add command that enables testing of release notes editor (#205556) Also fix double setting name --- .../browser/markdownSettingRenderer.ts | 2 +- .../browser/markdownSettingRenderer.test.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 6 ++-- .../update/browser/update.contribution.ts | 32 +++++++++++++++++-- .../contrib/update/browser/update.ts | 8 ++--- .../workbench/contrib/update/common/update.ts | 1 + 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 1d767009ce236..72f4dfd0e2ebb 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -169,7 +169,7 @@ export class SimpleSettingRenderer { return ` ${setting.key} - `; + `; } private renderFeature(setting: ISetting, newValue: string): string | undefined { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 884e3b50429b8..271cf0d2da5da 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -76,7 +76,7 @@ suite('Markdown Setting Renderer Test', () => { ` example.booleanSetting - `); + `); }); test('actions with no value', () => { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 3feabd2ea4eb2..a7303e1f8c95a 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -33,6 +33,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { SimpleSettingRenderer } from 'vs/workbench/contrib/markdown/browser/markdownSettingRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class ReleaseNotesManager { private readonly _simpleSettingRenderer: SimpleSettingRenderer; @@ -44,6 +45,7 @@ export class ReleaseNotesManager { private readonly disposables = new DisposableStore(); public constructor( + private readonly _useCurrentFile: boolean, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageService private readonly _languageService: ILanguageService, @@ -52,6 +54,7 @@ export class ReleaseNotesManager { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService private readonly _editorService: IEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, @IExtensionService private readonly _extensionService: IExtensionService, @IProductService private readonly _productService: IProductService, @@ -196,7 +199,7 @@ export class ReleaseNotesManager { const fetchReleaseNotes = async () => { let text; try { - text = await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + text = this._useCurrentFile ? this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue() : await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); } catch { throw new Error('Failed to fetch release notes'); } @@ -517,4 +520,3 @@ export class ReleaseNotesManager { } } } - diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 46b1d76365abb..fa3edab7d3acc 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -17,7 +17,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { isWindows } from 'vs/base/common/platform'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; +import { ShowCurrentReleaseNotesActionId, ShowCurrentReleaseNotesFromCurrentFileActionId } from 'vs/workbench/contrib/update/common/update'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -59,7 +59,7 @@ export class ShowCurrentReleaseNotesAction extends Action2 { const openerService = accessor.get(IOpenerService); try { - await showReleaseNotesInEditor(instantiationService, productService.version); + await showReleaseNotesInEditor(instantiationService, productService.version, false); } catch (err) { if (productService.releaseNotesUrl) { await openerService.open(URI.parse(productService.releaseNotesUrl)); @@ -70,7 +70,35 @@ export class ShowCurrentReleaseNotesAction extends Action2 { } } +export class ShowCurrentReleaseNotesFromCurrentFileAction extends Action2 { + + constructor() { + super({ + id: ShowCurrentReleaseNotesFromCurrentFileActionId, + title: { + ...localize2('showReleaseNotesCurrentFile', "Open Current File as Release Notes"), + mnemonicTitle: localize({ key: 'mshowReleaseNotes', comment: ['&& denotes a mnemonic'] }, "Show &&Release Notes"), + }, + category: localize2('developerCategory', "Developer"), + f1: true, + precondition: RELEASE_NOTES_URL + }); + } + + async run(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + const productService = accessor.get(IProductService); + + try { + await showReleaseNotesInEditor(instantiationService, productService.version, true); + } catch (err) { + throw new Error(localize('releaseNotesFromFileNone', "Cannot open the current file as Release Notes")); + } + } +} + registerAction2(ShowCurrentReleaseNotesAction); +registerAction2(ShowCurrentReleaseNotesFromCurrentFileAction); // Update diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index d57afcd149a5f..5d811ef439970 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -38,9 +38,9 @@ export const DOWNLOAD_URL = new RawContextKey('downloadUrl', ''); let releaseNotesManager: ReleaseNotesManager | undefined = undefined; -export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string) { +export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string, useCurrentFile: boolean) { if (!releaseNotesManager) { - releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager); + releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager, useCurrentFile); } return releaseNotesManager.show(version); @@ -61,7 +61,7 @@ async function openLatestReleaseNotesInBrowser(accessor: ServicesAccessor) { async function showReleaseNotes(accessor: ServicesAccessor, version: string) { const instantiationService = accessor.get(IInstantiationService); try { - await showReleaseNotesInEditor(instantiationService, version); + await showReleaseNotesInEditor(instantiationService, version, false); } catch (err) { try { await instantiationService.invokeFunction(openLatestReleaseNotesInBrowser); @@ -135,7 +135,7 @@ export class ProductContribution implements IWorkbenchContribution { // was there a major/minor update? if so, open release notes if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && currentVersion && isMajorMinorUpdate(lastVersion, currentVersion)) { - showReleaseNotesInEditor(instantiationService, productService.version) + showReleaseNotesInEditor(instantiationService, productService.version, false) .then(undefined, () => { notificationService.prompt( severity.Info, diff --git a/src/vs/workbench/contrib/update/common/update.ts b/src/vs/workbench/contrib/update/common/update.ts index c224d76703ab8..a5798049ce0e9 100644 --- a/src/vs/workbench/contrib/update/common/update.ts +++ b/src/vs/workbench/contrib/update/common/update.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes'; +export const ShowCurrentReleaseNotesFromCurrentFileActionId = 'developer.showCurrentFileAsReleaseNotes'; From 2cec75a8583c72d66d5df1d079e08b6934bd44a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 19 Feb 2024 17:43:23 +0100 Subject: [PATCH 0471/1863] Fixes #202837 --- src/vs/workbench/browser/parts/editor/textDiffEditor.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index fa7c515d416a0..455922bbcbfdd 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -195,6 +195,10 @@ export class TextDiffEditor extends AbstractTextEditor imp control.restoreViewState(editorViewState); + if (options?.revealIfVisible) { + control.revealFirstDiff(); + } + return true; } From 9b0d74345c40c29e6ceb4656123debb887714a41 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:45:24 +0100 Subject: [PATCH 0472/1863] Git - remove git.experimental.inputValidation setting (#205550) * Git - remove git.experimental.inputValidation setting * Fix compilation error * Fix migration code --- extensions/git/package.json | 14 ++------ extensions/git/package.nls.json | 2 +- extensions/git/src/diagnostics.ts | 30 ++++++++++++++-- extensions/git/src/repository.ts | 59 ++----------------------------- 4 files changed, 33 insertions(+), 72 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c2745213c681f..be52c007a9cf8 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2759,13 +2759,8 @@ "default": false }, "git.inputValidation": { - "type": "string", - "enum": [ - "always", - "warn", - "off" - ], - "default": "off", + "type": "boolean", + "default": false, "description": "%config.inputValidation%" }, "git.inputValidationLength": { @@ -2781,11 +2776,6 @@ "default": 50, "markdownDescription": "%config.inputValidationSubjectLength%" }, - "git.experimental.inputValidation": { - "type": "boolean", - "default": false, - "description": "%config.inputValidation%" - }, "git.detectSubmodules": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index f6bbb18454ddc..6eba0f44d8f71 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -198,7 +198,7 @@ "config.openAfterClone.prompt": "Always prompt for action.", "config.showInlineOpenFileAction": "Controls whether to show an inline Open File action in the Git changes view.", "config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.", - "config.inputValidation": "Controls when to show commit message input validation.", + "config.inputValidation": "Controls whether to show commit message input validation diagnostics.", "config.inputValidationLength": "Controls the commit message length threshold for showing a warning.", "config.inputValidationSubjectLength": "Controls the commit message subject length threshold for showing a warning. Unset it to inherit the value of `#git.inputValidationLength#`.", "config.detectSubmodules": "Controls whether to automatically detect Git submodules.", diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 3d5cba205c56a..9df39df517765 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -21,14 +21,38 @@ export class GitCommitInputBoxDiagnosticsManager { constructor(private readonly model: Model) { this.diagnostics = languages.createDiagnosticCollection(); - mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); - filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.experimental.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + this.migrateInputValidationSettings() + .then(() => { + mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + }); } public getDiagnostics(uri: Uri): ReadonlyArray { return this.diagnostics.get(uri) ?? []; } + private async migrateInputValidationSettings(): Promise { + try { + const config = workspace.getConfiguration('git'); + const inputValidation = config.inspect<'always' | 'warn' | 'off' | boolean>('inputValidation'); + + if (inputValidation === undefined) { + return; + } + + // Workspace setting + if (typeof inputValidation.workspaceValue === 'string') { + await config.update('inputValidation', inputValidation.workspaceValue !== 'off', false); + } + + // User setting + if (typeof inputValidation.globalValue === 'string') { + await config.update('inputValidation', inputValidation.workspaceValue !== 'off', true); + } + } catch { } + } + private onDidChangeConfiguration(): void { for (const repository of this.model.repositories) { this.onDidChangeTextDocument(repository.inputBox.document); @@ -37,7 +61,7 @@ export class GitCommitInputBoxDiagnosticsManager { private onDidChangeTextDocument(document: TextDocument): void { const config = workspace.getConfiguration('git'); - const inputValidation = config.get('experimental.inputValidation', false) === true; + const inputValidation = config.get('inputValidation', false); if (!inputValidation) { this.diagnostics.set(document.uri, undefined); return; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index cadb3c717f70c..edd250797e098 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -975,10 +975,9 @@ export class Repository implements Disposable { this.setCountBadge(); } - validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined { - let tooManyChangesWarning: SourceControlInputBoxValidation | undefined; + validateInput(text: string, _: number): SourceControlInputBoxValidation | undefined { if (this.isRepositoryHuge) { - tooManyChangesWarning = { + return { message: l10n.t('Too many changes were detected. Only the first {0} changes will be shown below.', this.isRepositoryHuge.limit), type: SourceControlInputBoxValidationType.Warning }; @@ -993,59 +992,7 @@ export class Repository implements Disposable { } } - const config = workspace.getConfiguration('git'); - const setting = config.get<'always' | 'warn' | 'off'>('inputValidation'); - - if (setting === 'off') { - return tooManyChangesWarning; - } - - if (/^\s+$/.test(text)) { - return { - message: l10n.t('Current commit message only contains whitespace characters'), - type: SourceControlInputBoxValidationType.Warning - }; - } - - let lineNumber = 0; - let start = 0; - let match: RegExpExecArray | null; - const regex = /\r?\n/g; - - while ((match = regex.exec(text)) && position > match.index) { - start = match.index + match[0].length; - lineNumber++; - } - - const end = match ? match.index : text.length; - - const line = text.substring(start, end); - - let threshold = config.get('inputValidationLength', 50); - - if (lineNumber === 0) { - const inputValidationSubjectLength = config.get('inputValidationSubjectLength', null); - - if (inputValidationSubjectLength !== null) { - threshold = inputValidationSubjectLength; - } - } - - if (line.length <= threshold) { - if (setting !== 'always') { - return tooManyChangesWarning; - } - - return { - message: l10n.t('{0} characters left in current line', threshold - line.length), - type: SourceControlInputBoxValidationType.Information - }; - } else { - return { - message: l10n.t('{0} characters over {1} in current line', line.length - threshold, threshold), - type: SourceControlInputBoxValidationType.Warning - }; - } + return undefined; } /** From 74724fbcb07547ade4c8602a3bf2cbb29172c738 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 19 Feb 2024 20:32:35 +0100 Subject: [PATCH 0473/1863] "Convert Indentation to {tabs,spaces}" only effects auxwindow (fix #205169) (#205577) --- src/vs/platform/hover/browser/hover.ts | 1 - .../parts/editor/breadcrumbsControl.ts | 3 +-- .../browser/parts/editor/editorStatus.ts | 5 ++--- .../browser/parts/statusbar/statusbarPart.ts | 21 ++++++++----------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 93974adc1a9fb..c9edff5d2a315 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -238,7 +238,6 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate private lastHoverHideTime = Number.MAX_VALUE; private timeLimit = 200; - private _delay: number; get delay(): number { if (this.instantHover && Date.now() - this.lastHoverHideTime < this.timeLimit) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 4d9e3906b4e87..2a9d829b3ff1e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -206,8 +206,7 @@ export class BreadcrumbsControl { @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @IConfigurationService configurationService: IConfigurationService, - @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, - @IInstantiationService instantiationService: IInstantiationService, + @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService ) { this.domNode = document.createElement('div'); this.domNode.classList.add('breadcrumbs-control'); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 968668456a4d2..e1ee2bc0d948b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -366,10 +366,9 @@ class EditorStatus extends Disposable { } private registerCommands(): void { - CommandsRegistry.registerCommand({ id: 'changeEditorIndentation', handler: () => this.showIndentationPicker() }); + CommandsRegistry.registerCommand({ id: `changeEditorIndentation${this.targetWindowId}`, handler: () => this.showIndentationPicker() }); } - private async showIndentationPicker(): Promise { const activeTextEditorControl = getCodeEditor(this.editorService.activeTextEditorControl); if (!activeTextEditorControl) { @@ -483,7 +482,7 @@ class EditorStatus extends Disposable { text, ariaLabel: text, tooltip: localize('selectIndentation', "Select Indentation"), - command: 'changeEditorIndentation' + command: `changeEditorIndentation${this.targetWindowId}` }; this.updateElement(this.indentationElement, props, 'status.editor.indentation', StatusbarAlignment.RIGHT, 100.4); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index ae4fd401456ef..664a333bb16ee 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -136,8 +136,14 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { private leftItemsContainer: HTMLElement | undefined; private rightItemsContainer: HTMLElement | undefined; - - private readonly hoverDelegate: WorkbenchHoverDelegate; + private readonly hoverDelegate = this._register(this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( + { + persistence: { + hideOnKeyDown: true, + sticky: focus + } + } + ))); private readonly compactEntriesDisposable = this._register(new MutableDisposable()); private readonly styleOverrides = new Set(); @@ -149,20 +155,11 @@ class StatusbarPart extends Part implements IStatusbarEntryContainer { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService private readonly storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IContextMenuService private contextMenuService: IContextMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); - this.hoverDelegate = this._register(instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, (_, focus?: boolean) => ( - { - persistence: { - hideOnKeyDown: true, - sticky: focus - } - } - ))); - this.registerListeners(); } From efc04b885ecbd2e48d81c2f28cfe540e9e70cb8d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 19:52:19 +0000 Subject: [PATCH 0474/1863] New proposal for chat variable resolver (#205572) * Tweak ChatFollowup * Remove API TODOs * New proposal for chat variable resolver * Bump distro * Enforce same-extension followup * Add participant proposal to integration test folder * Allow no participant for a followup --- extensions/vscode-api-tests/package.json | 1 + .../src/singlefolder-tests/chat.test.ts | 2 +- package.json | 2 +- .../workbench/api/common/extHost.api.impl.ts | 4 +- .../api/common/extHostChatAgents2.ts | 13 ++++- .../api/common/extHostTypeConverters.ts | 6 +- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.chatParticipant.d.ts | 55 ++----------------- .../vscode.proposed.chatVariableResolver.d.ts | 52 ++++++++++++++++++ 9 files changed, 78 insertions(+), 58 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index adf3c5fae9f46..e9323fc9c4355 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,6 +10,7 @@ "chatParticipant", "languageModels", "defaultChatParticipant", + "chatVariableResolver", "contribViewsRemote", "contribStatusBarItems", "createFileSystemWatcher", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 28621d1bb6bcb..44fa439679611 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -74,7 +74,7 @@ suite('chat', () => { }); test('participant and variable', async () => { - disposables.push(chat.registerVariable('myVar', 'My variable', { + disposables.push(chat.registerChatVariableResolver('myVar', 'My variable', { resolve(_name, _context, _token) { return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; } diff --git a/package.json b/package.json index df171934c3409..f734771d90735 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.87.0", - "distro": "af73a537ea203329debad3df7ca7b42b4799473f", + "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 46292e1bb115d..c83b498978294 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1409,8 +1409,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatProvider'); return extHostChatProvider.registerLanguageModel(extension, id, provider, metadata); }, - registerVariable(name: string, description: string, resolver: vscode.ChatVariableResolver) { - checkProposedApiEnabled(extension, 'chatParticipant'); + registerChatVariableResolver(name: string, description: string, resolver: vscode.ChatVariableResolver) { + checkProposedApiEnabled(extension, 'chatVariableResolver'); return extHostChatVariables.registerVariableResolver(extension, name, description, resolver); }, registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b3318d5c31491..a4236ff42e6c2 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -9,12 +9,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Iterable } from 'vs/base/common/iterator'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -261,6 +262,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const ehResult = typeConvert.ChatAgentResult.to(result); return (await agent.provideFollowups(ehResult, token)) + .filter(f => { + // The followup must refer to a participant that exists from the same extension + const isValid = !f.participant || Iterable.some( + this._agents.values(), + a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier)); + if (!isValid) { + this._logService.warn(`[@${agent.id}] ChatFollowup refers to an invalid participant: ${f.participant}`); + } + return isValid; + }) .map(f => typeConvert.ChatFollowup.from(f, request)); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 175a921311243..52ca8dfa1e292 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2201,18 +2201,16 @@ export namespace ChatFollowup { agentId: followup.participant ?? request?.agentId ?? '', subCommand: followup.command ?? request?.command, message: followup.prompt, - title: followup.title, - tooltip: followup.tooltip, + title: followup.label }; } export function to(followup: IChatFollowup): vscode.ChatFollowup { return { prompt: followup.message, - title: followup.title, + label: followup.title, participant: followup.agentId, command: followup.subCommand, - tooltip: followup.tooltip, }; } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0ba3e2b1ea22e..76ffed1feeb4a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -15,6 +15,7 @@ export const allApiProposals = Object.freeze({ chatParticipantAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', chatTab: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatTab.d.ts', + chatVariableResolver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts', codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts', 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', diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index f4b2242ed4609..61caeb78893cc 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,7 +5,6 @@ declare module 'vscode' { - // TODO@API name: Turn? export class ChatRequestTurn { /** @@ -36,7 +35,6 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } - // TODO@API name: Turn? export class ChatResponseTurn { /** @@ -185,9 +183,14 @@ declare module 'vscode' { */ prompt: string; + /** + * A title to show the user, when it is different than the message. + */ + label?: string; + /** * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant. - * TODO@API do extensions need to specify the extensionID of the participant here as well? + * Followups can only invoke a participant that was contributed by the same extension. */ participant?: string; @@ -195,17 +198,6 @@ declare module 'vscode' { * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. */ command?: string; - - /** - * A tooltip to show when hovering over the followup. - */ - tooltip?: string; - - /** - * A title to show the user, when it is different than the message. - */ - // TODO@API title vs tooltip? - title?: string; } /** @@ -400,9 +392,6 @@ declare module 'vscode' { * @param value * @returns This stream. */ - // TODO@API is this always inline or not - // TODO@API is this markdown or string? - // TODO@API this influences the rendering, it inserts new lines which is likely a bug progress(value: string): ChatResponseStream; /** @@ -414,8 +403,6 @@ declare module 'vscode' { * @param value A uri or location * @returns This stream. */ - // TODO@API support non-file uris, like http://example.com - // TODO@API support mapped edits reference(value: Uri | Location): ChatResponseStream; /** @@ -426,8 +413,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatResponseStream; } - // TODO@API should the name suffix differentiate between rendered items (XYZPart) - // and metadata like XYZItem export class ChatResponseTextPart { value: string; constructor(value: string); @@ -457,7 +442,6 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; - // TODO@API inline constructor(value: string); } @@ -489,21 +473,11 @@ declare module 'vscode' { * @returns A new chat participant */ export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant; - - /** - * Register a variable which can be used in a chat request to any participant. - * @param name The name of the variable, to be used in the chat input as `#name`. - * @param description A description of the variable for the chat input suggest widget. - * @param resolver Will be called to provide the chat variable's value when it is used. - */ - // TODO@API NAME: registerChatVariable, registerChatVariableResolver - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; } /** * The detail level of this chat variable value. */ - // TODO@API maybe for round2 export enum ChatVariableLevel { Short = 1, Medium = 2, @@ -526,21 +500,4 @@ declare module 'vscode' { */ description?: string; } - - export interface ChatVariableContext { - /** - * The message entered by the user, which includes this variable. - */ - prompt: string; - } - - export interface ChatVariableResolver { - /** - * A callback to resolve the value of a chat variable. - * @param name The name of the variable. - * @param context Contextual information about this chat request. - * @param token A cancellation token. - */ - resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; - } } diff --git a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts new file mode 100644 index 0000000000000..32f081ecc5d81 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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 namespace chat { + + /** + * Register a variable which can be used in a chat request to any participant. + * @param name The name of the variable, to be used in the chat input as `#name`. + * @param description A description of the variable for the chat input suggest widget. + * @param resolver Will be called to provide the chat variable's value when it is used. + */ + export function registerChatVariableResolver(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } + + export interface ChatVariableValue { + /** + * The detail level of this chat variable value. If possible, variable resolvers should try to offer shorter values that will consume fewer tokens in an LLM prompt. + */ + level: ChatVariableLevel; + + /** + * The variable's value, which can be included in an LLM prompt as-is, or the chat participant may decide to read the value and do something else with it. + */ + value: string | Uri; + + /** + * A description of this value, which could be provided to the LLM as a hint. + */ + description?: string; + } + + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + prompt: string; + } + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; + } +} From ea9f1dacfb5094506524378e62a22eca7ba4a75b Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:23:05 -0800 Subject: [PATCH 0475/1863] fix quote type in issue reporter (#205582) changed quote type --- src/vs/platform/issue/electron-main/issueMainService.ts | 4 ++-- .../services/issue/electron-sandbox/issueService.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 32f2be6af4533..0bcf0f31928fe 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -459,8 +459,8 @@ export class IssueMainService implements IIssueMainService { const replyChannel = `vscode:triggerReporterMenu`; const cts = new CancellationTokenSource(); window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterMenuResponse:${extensionId}', (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error('Error: Extension ${extensionId} timed out waiting for menu response'); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: IssueReporterData | undefined) => resolve(data))), 5000, () => { + this.logService.error(`Error: Extension ${extensionId} timed out waiting for menu response`); cts.cancel(); }); return result as IssueReporterData | undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 039ce81f5a94b..add5d98fc2e56 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -85,7 +85,7 @@ export class NativeIssueService implements IWorkbenchIssueService { } } const result = [this._providers.has(extensionId.toLowerCase()), this._handlers.has(extensionId.toLowerCase())]; - ipcRenderer.send('vscode:triggerReporterStatusResponse', result); + ipcRenderer.send(`vscode:triggerReporterStatusResponse`, result); }); ipcRenderer.on('vscode:triggerReporterMenu', async (event, arg) => { const extensionId = arg.extensionId; @@ -108,7 +108,7 @@ export class NativeIssueService implements IWorkbenchIssueService { if (!this.extensionIdentifierSet.has(extensionId)) { // send undefined to indicate no action was taken - ipcRenderer.send('vscode:triggerReporterMenuResponse:${extensionId}', undefined); + ipcRenderer.send(`vscode:triggerReporterMenuResponse:${extensionId}`, undefined); } menu.dispose(); }); @@ -186,7 +186,7 @@ export class NativeIssueService implements IWorkbenchIssueService { }, dataOverrides); if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { - ipcRenderer.send('vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}', issueReporterData); + ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); } return this.issueMainService.openReporter(issueReporterData); From 232e9a2bef554254ec3d370cdbbe6074bbd422c8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:25:13 +0100 Subject: [PATCH 0476/1863] Git - comment out GitIncomingChangesFileDecorationProvider (#205583) --- extensions/git/package.json | 40 ----- extensions/git/src/decorationProvider.ts | 194 +++++++++++------------ 2 files changed, 97 insertions(+), 137 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index be52c007a9cf8..f1b45e74665c8 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3198,46 +3198,6 @@ "highContrast": "#8db9e2", "highContrastLight": "#1258a7" } - }, - { - "id": "gitDecoration.incomingAddedForegroundColor", - "description": "%colors.incomingAdded%", - "defaults": { - "light": "#587c0c", - "dark": "#81b88b", - "highContrast": "#1b5225", - "highContrastLight": "#374e06" - } - }, - { - "id": "gitDecoration.incomingDeletedForegroundColor", - "description": "%colors.incomingDeleted%", - "defaults": { - "light": "#ad0707", - "dark": "#c74e39", - "highContrast": "#c74e39", - "highContrastLight": "#ad0707" - } - }, - { - "id": "gitDecoration.incomingRenamedForegroundColor", - "description": "%colors.incomingRenamed%", - "defaults": { - "light": "#007100", - "dark": "#73C991", - "highContrast": "#73C991", - "highContrastLight": "#007100" - } - }, - { - "id": "gitDecoration.incomingModifiedForegroundColor", - "description": "%colors.incomingModified%", - "defaults": { - "light": "#895503", - "dark": "#E2C08D", - "highContrast": "#E2C08D", - "highContrastLight": "#895503" - } } ], "configurationDefaults": { diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 9e3e356628d06..5167b1eb95ef5 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor, l10n } from 'vscode'; +import { window, workspace, Uri, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, ThemeColor } from 'vscode'; import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable } from './util'; -import { Change, GitErrorCodes, Status } from './api/git'; +import { GitErrorCodes, Status } from './api/git'; class GitIgnoreDecorationProvider implements FileDecorationProvider { @@ -153,100 +153,100 @@ class GitDecorationProvider implements FileDecorationProvider { } } -class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { - - private readonly _onDidChangeDecorations = new EventEmitter(); - readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; - - private decorations = new Map(); - private readonly disposables: Disposable[] = []; - - constructor(private readonly repository: Repository) { - this.disposables.push(window.registerFileDecorationProvider(this)); - repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); - } - - private async onDidChangeCurrentHistoryItemGroup(): Promise { - const newDecorations = new Map(); - await this.collectIncomingChangesFileDecorations(newDecorations); - const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); - - this.decorations = newDecorations; - this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); - } - - private async collectIncomingChangesFileDecorations(bucket: Map): Promise { - for (const change of await this.getIncomingChanges()) { - switch (change.status) { - case Status.INDEX_ADDED: - bucket.set(change.uri.toString(), { - badge: '↓A', - color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), - tooltip: l10n.t('Incoming Changes (added)'), - }); - break; - case Status.DELETED: - bucket.set(change.uri.toString(), { - badge: '↓D', - color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), - tooltip: l10n.t('Incoming Changes (deleted)'), - }); - break; - case Status.INDEX_RENAMED: - bucket.set(change.originalUri.toString(), { - badge: '↓R', - color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), - tooltip: l10n.t('Incoming Changes (renamed)'), - }); - break; - case Status.MODIFIED: - bucket.set(change.uri.toString(), { - badge: '↓M', - color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), - tooltip: l10n.t('Incoming Changes (modified)'), - }); - break; - default: { - bucket.set(change.uri.toString(), { - badge: '↓~', - color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), - tooltip: l10n.t('Incoming Changes'), - }); - break; - } - } - } - } - - private async getIncomingChanges(): Promise { - try { - const historyProvider = this.repository.historyProvider; - const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; - - if (!currentHistoryItemGroup?.base) { - return []; - } - - const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); - if (!ancestor) { - return []; - } - - const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); - return changes; - } catch (err) { - return []; - } - } - - provideFileDecoration(uri: Uri): FileDecoration | undefined { - return this.decorations.get(uri.toString()); - } - - dispose(): void { - dispose(this.disposables); - } -} +// class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider { + +// private readonly _onDidChangeDecorations = new EventEmitter(); +// readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + +// private decorations = new Map(); +// private readonly disposables: Disposable[] = []; + +// constructor(private readonly repository: Repository) { +// this.disposables.push(window.registerFileDecorationProvider(this)); +// repository.historyProvider.onDidChangeCurrentHistoryItemGroup(this.onDidChangeCurrentHistoryItemGroup, this, this.disposables); +// } + +// private async onDidChangeCurrentHistoryItemGroup(): Promise { +// const newDecorations = new Map(); +// await this.collectIncomingChangesFileDecorations(newDecorations); +// const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); + +// this.decorations = newDecorations; +// this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); +// } + +// private async collectIncomingChangesFileDecorations(bucket: Map): Promise { +// for (const change of await this.getIncomingChanges()) { +// switch (change.status) { +// case Status.INDEX_ADDED: +// bucket.set(change.uri.toString(), { +// badge: '↓A', +// color: new ThemeColor('gitDecoration.incomingAddedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (added)'), +// }); +// break; +// case Status.DELETED: +// bucket.set(change.uri.toString(), { +// badge: '↓D', +// color: new ThemeColor('gitDecoration.incomingDeletedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (deleted)'), +// }); +// break; +// case Status.INDEX_RENAMED: +// bucket.set(change.originalUri.toString(), { +// badge: '↓R', +// color: new ThemeColor('gitDecoration.incomingRenamedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (renamed)'), +// }); +// break; +// case Status.MODIFIED: +// bucket.set(change.uri.toString(), { +// badge: '↓M', +// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), +// tooltip: l10n.t('Incoming Changes (modified)'), +// }); +// break; +// default: { +// bucket.set(change.uri.toString(), { +// badge: '↓~', +// color: new ThemeColor('gitDecoration.incomingModifiedForegroundColor'), +// tooltip: l10n.t('Incoming Changes'), +// }); +// break; +// } +// } +// } +// } + +// private async getIncomingChanges(): Promise { +// try { +// const historyProvider = this.repository.historyProvider; +// const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup; + +// if (!currentHistoryItemGroup?.base) { +// return []; +// } + +// const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, currentHistoryItemGroup.base.id); +// if (!ancestor) { +// return []; +// } + +// const changes = await this.repository.diffBetween(ancestor.id, currentHistoryItemGroup.base.id); +// return changes; +// } catch (err) { +// return []; +// } +// } + +// provideFileDecoration(uri: Uri): FileDecoration | undefined { +// return this.decorations.get(uri.toString()); +// } + +// dispose(): void { +// dispose(this.disposables); +// } +// } export class GitDecorations { @@ -287,7 +287,7 @@ export class GitDecorations { private onDidOpenRepository(repository: Repository): void { const providers = combinedDisposable([ new GitDecorationProvider(repository), - new GitIncomingChangesFileDecorationProvider(repository) + // new GitIncomingChangesFileDecorationProvider(repository) ]); this.providers.set(repository, providers); From 047cf8cc51e325ecbd082bb8de42b5f769dbc433 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:54:02 +0100 Subject: [PATCH 0477/1863] Remove hover from elements using context view (#205586) fix #205526 --- .../base/browser/ui/actionbar/actionViewItems.ts | 15 ++++++++++----- .../browser/languageStatus.contribution.ts | 3 ++- src/vs/workbench/electron-sandbox/window.ts | 5 +++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index a714411e3a20e..fd7424b0f72c9 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -226,12 +226,17 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { const title = this.getTooltip() ?? ''; this.updateAriaLabel(); - if (!this.customHover) { - const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this.customHover = setupCustomHover(hoverDelegate, this.element, title); - this._store.add(this.customHover); + if (this.options.hoverDelegate?.showNativeHover) { + /* While custom hover is not supported with context view */ + this.element.title = title; } else { - this.customHover.update(title); + if (!this.customHover) { + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this.customHover = setupCustomHover(hoverDelegate, this.element, title); + this._store.add(this.customHover); + } else { + this.customHover.update(title); + } } } diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 45e898b8fc10e..a444b09963770 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -33,6 +33,7 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; class LanguageStatusViewModel { @@ -327,7 +328,7 @@ class LanguageStatus { } // -- pin - const actionBar = new ActionBar(right, {}); + const actionBar = new ActionBar(right, { hoverDelegate: nativeHoverDelegate }); store.add(actionBar); let action: Action; if (!isPinned) { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6880e360e0274..f207e15834fa0 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -79,6 +79,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ThemeIcon } from 'vs/base/common/themables'; import { getWorkbenchContribution } from 'vs/workbench/common/contributions'; import { DynamicWorkbenchSecurityConfiguration } from 'vs/workbench/common/configuration'; +import { nativeHoverDelegate } from 'vs/platform/hover/browser/hover'; export class NativeWindow extends BaseWindow { @@ -1163,7 +1164,7 @@ class ZoomStatusEntry extends Disposable { this.zoomLevelLabel = zoomLevelLabel; disposables.add(toDisposable(() => this.zoomLevelLabel = undefined)); - const actionBarLeft = disposables.add(new ActionBar(left)); + const actionBarLeft = disposables.add(new ActionBar(left, { hoverDelegate: nativeHoverDelegate })); actionBarLeft.push(zoomOutAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomOutAction.id)?.getLabel() }); actionBarLeft.push(this.zoomLevelLabel, { icon: false, label: true }); actionBarLeft.push(zoomInAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomInAction.id)?.getLabel() }); @@ -1172,7 +1173,7 @@ class ZoomStatusEntry extends Disposable { right.classList.add('zoom-status-right'); container.appendChild(right); - const actionBarRight = disposables.add(new ActionBar(right)); + const actionBarRight = disposables.add(new ActionBar(right, { hoverDelegate: nativeHoverDelegate })); actionBarRight.push(zoomResetAction, { icon: false, label: true }); actionBarRight.push(zoomSettingsAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(zoomSettingsAction.id)?.getLabel() }); From b44593a612337289c079425a5b2cc7010216eef4 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:34:15 +0100 Subject: [PATCH 0478/1863] Fixes #204923 (#205605) fix #204923 --- .../browser/parts/editor/media/multieditortabscontrol.css | 2 ++ .../workbench/contrib/files/browser/views/media/openeditors.css | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 8a883475400cf..18e66d6a1df90 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -386,11 +386,13 @@ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */ + font-family: 'codicon'; } .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ea71"; /* use `circle-filled` icon unicode for dirty indication */ + font-family: 'codicon'; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css index 12a175f0ebca8..cb154b83db934 100644 --- a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -38,10 +38,12 @@ .open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-pinned::before { content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */ + font-family: 'codicon'; } .open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { content: "\ea71"; /* use `circle-filled` icon unicode for dirty indication */ + font-family: 'codicon'; } .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, From d686f5f765f5c36ea11ac909309b6f1db0662de4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 19 Feb 2024 22:04:48 +0000 Subject: [PATCH 0479/1863] Fix "Used reference" rendering (#205606) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 609da660c84a1..c0f1827014a73 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -1270,14 +1270,14 @@ class ContentReferencesListRenderer implements IListRenderer Date: Mon, 19 Feb 2024 15:32:06 -0800 Subject: [PATCH 0480/1863] chore: run OSS tool (#205608) --- cglicenses.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cglicenses.json b/cglicenses.json index d61164acd3d33..1d6851e38c8bb 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -589,5 +589,15 @@ "prependLicenseText": [ "Copyright (c) heap.js authors" ] + }, + { + // Reason: mono-repo where the individual packages are also dual-licensed under MIT and Apache-2.0 + "name": "system-configuration", + "fullLicenseTextUri": "https://github.com/mullvad/system-configuration-rs/blob/main/system-configuration/LICENSE-MIT" + }, + { + // Reason: mono-repo where the individual packages are also dual-licensed under MIT and Apache-2.0 + "name": "system-configuration-sys", + "fullLicenseTextUri": "https://github.com/mullvad/system-configuration-rs/blob/main/system-configuration-sys/LICENSE-MIT" } ] From 01ce8d869e4220577e194b2dd57bfa12437dfcf5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 20 Feb 2024 01:02:24 +0000 Subject: [PATCH 0481/1863] More chat participant API comments (#205617) More docs --- .../workbench/api/common/extHost.api.impl.ts | 1 - .../api/common/extHostChatAgents2.ts | 4 +- .../api/common/extHostTypeConverters.ts | 29 ++++----- src/vs/workbench/api/common/extHostTypes.ts | 10 +-- .../vscode.proposed.chatParticipant.d.ts | 63 +++++++++++-------- 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c83b498978294..71ec5250fb9a0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1671,7 +1671,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I RelatedInformationType: extHostTypes.RelatedInformationType, SpeechToTextStatus: extHostTypes.SpeechToTextStatus, KeywordRecognitionStatus: extHostTypes.KeywordRecognitionStatus, - ChatResponseTextPart: extHostTypes.ChatResponseTextPart, ChatResponseMarkdownPart: extHostTypes.ChatResponseMarkdownPart, ChatResponseFileTreePart: extHostTypes.ChatResponseFileTreePart, ChatResponseAnchorPart: extHostTypes.ChatResponseAnchorPart, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index a4236ff42e6c2..9f4f87ac9999f 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -234,7 +234,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); // RESPONSE turn - const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter))); + const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.fromContent(r, this.commands.converter))); res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); } @@ -402,7 +402,7 @@ class ExtHostChatAgent { return { name: c.name, - description: c.description, + description: c.description ?? '', followupPlaceholder: c.isSticky2?.placeholder, isSticky: c.isSticky2?.isSticky ?? c.isSticky, sampleRequest: c.sampleRequest diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 52ca8dfa1e292..db879caea6bb0 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2330,18 +2330,6 @@ export namespace InteractiveEditorResponseFeedbackKind { } } -export namespace ChatResponseTextPart { - export function to(part: vscode.ChatResponseTextPart): Dto { - return { - kind: 'markdownContent', - content: MarkdownString.from(new types.MarkdownString().appendText(part.value)) - }; - } - export function from(part: Dto): vscode.ChatResponseTextPart { - return new types.ChatResponseTextPart(part.content.value); - } -} - export namespace ChatResponseMarkdownPart { export function to(part: vscode.ChatResponseMarkdownPart): Dto { return { @@ -2475,14 +2463,27 @@ export namespace ChatResponsePart { } export function from(part: extHostProtocol.IChatProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponsePart | undefined { + switch (part.kind) { + case 'reference': return ChatResponseReferencePart.from(part); + case 'markdownContent': + case 'inlineReference': + case 'progressMessage': + case 'treeData': + case 'command': + return fromContent(part, commandsConverter); + } + return undefined; + } + + export function fromContent(part: extHostProtocol.IChatContentProgressDto, commandsConverter: CommandsConverter): vscode.ChatResponseMarkdownPart | vscode.ChatResponseFileTreePart | vscode.ChatResponseAnchorPart | vscode.ChatResponseCommandButtonPart | undefined { switch (part.kind) { case 'markdownContent': return ChatResponseMarkdownPart.from(part); case 'inlineReference': return ChatResponseAnchorPart.from(part); - case 'reference': return ChatResponseReferencePart.from(part); - case 'progressMessage': return ChatResponseProgressPart.from(part); + case 'progressMessage': return undefined; case 'treeData': return ChatResponseFilesPart.from(part); case 'command': return ChatResponseCommandButtonPart.from(part, commandsConverter); } + return undefined; } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index db0384507653c..4827cdfbfeb03 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4205,14 +4205,6 @@ export enum ChatResultFeedbackKind { Helpful = 1, } - -export class ChatResponseTextPart { - value: string; - constructor(value: string) { - this.value = value; - } -} - export class ChatResponseMarkdownPart { value: vscode.MarkdownString; constructor(value: string | vscode.MarkdownString) { @@ -4272,7 +4264,7 @@ export class ChatRequestTurn implements vscode.ChatRequestTurn { export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( - readonly response: ReadonlyArray, + readonly response: ReadonlyArray, readonly result: vscode.ChatResult, readonly participant: { extensionId: string; participant: string }, readonly command?: string diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 61caeb78893cc..468f12ae69076 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,12 +5,15 @@ declare module 'vscode' { + /** + * Represents a user request in chat history. + */ export class ChatRequestTurn { /** * The prompt as entered by the user. * - * Information about variables used in this request are is stored in {@link ChatRequest.variables}. + * Information about variables used in this request is stored in {@link ChatRequestTurn.variables}. * * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. @@ -35,12 +38,15 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } + /** + * Represents a chat participant's response in chat history. + */ export class ChatResponseTurn { /** - * The content that was received from the chat participant. Only the progress parts that represent actual content (not metadata) are represented. + * The content that was received from the chat participant. Only the stream parts that represent actual content (not metadata) are represented. */ - readonly response: ReadonlyArray; + readonly response: ReadonlyArray; /** * The result that was received from the chat participant. @@ -48,13 +54,16 @@ declare module 'vscode' { readonly result: ChatResult; /** - * The name of the chat participant and contributing extension to which this request was directed. + * The name of the chat participant and contributing extension that this response came from. */ readonly participant: { readonly extensionId: string; readonly participant: string }; + /** + * The name of the command that this response came from. + */ readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); } export interface ChatContext { @@ -98,7 +107,7 @@ declare module 'vscode' { errorDetails?: ChatErrorDetails; /** - * Arbitrary metadata for this result. Can be anything but must be JSON-stringifyable. + * Arbitrary metadata for this result. Can be anything, but must be JSON-stringifyable. */ readonly metadata?: { readonly [key: string]: any }; } @@ -123,7 +132,8 @@ declare module 'vscode' { */ export interface ChatResultFeedback { /** - * This instance of ChatResult has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. + * The ChatResult that the user is providing feedback for. + * This instance has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. */ readonly result: ChatResult; @@ -158,8 +168,11 @@ declare module 'vscode' { readonly isSticky?: boolean; } + /** + * A ChatCommandProvider returns {@link ChatCommands commands} that can be invoked on a chat participant using `/`. For example, `@participant /command`. + * These can be used as shortcuts to let the user explicitly invoke different functionalities provided by the participant. + */ export interface ChatCommandProvider { - /** * Returns a list of commands that its participant is capable of handling. A command * can be selected by the user and will then be passed to the {@link ChatRequestHandler handler} @@ -175,7 +188,7 @@ declare module 'vscode' { } /** - * A followup question suggested by the model. + * A followup question suggested by the participant. */ export interface ChatFollowup { /** @@ -184,7 +197,7 @@ declare module 'vscode' { prompt: string; /** - * A title to show the user, when it is different than the message. + * A title to show the user. The prompt will be shown by default, when this is unspecified. */ label?: string; @@ -205,8 +218,8 @@ declare module 'vscode' { */ export interface ChatFollowupProvider { /** - * - * @param result The same instance of the result object that was returned by the chat participant, and it can be extended with arbitrary properties if needed. + * Provide followups for the given result. + * @param result This instance has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. * @param token A cancellation token. */ provideFollowups(result: ChatResult, token: CancellationToken): ProviderResult; @@ -217,9 +230,11 @@ declare module 'vscode' { */ export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - + /** + * A chat participant can be invoked by the user in a chat session, using the `@` prefix. When it is invoked, it handles the chat request and is solely + * responsible for providing a response to the user. A ChatParticipant is created using {@link chat.createChatParticipant}. + */ export interface ChatParticipant { - /** * The short name by which this participant is referred to in the UI, e.g `workspace`. */ @@ -227,6 +242,7 @@ declare module 'vscode' { /** * The full name of this participant. + * TODO@API This is only used for the default participant, but it seems useful, so should we keep it so we can use it in the future? */ fullName: string; @@ -293,7 +309,6 @@ declare module 'vscode' { * A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used. */ export interface ChatResolvedVariable { - /** * The name of the variable. * @@ -315,7 +330,6 @@ declare module 'vscode' { } export interface ChatRequest { - /** * The prompt as entered by the user. * @@ -336,7 +350,7 @@ declare module 'vscode' { * * *Note* that the prompt contains varibale references as authored and that it is up to the participant * to further modify the prompt, for instance by inlining variable values or creating links to - * headings which contain the resolved values. vvariables are sorted in reverse by their range + * headings which contain the resolved values. Variables are sorted in reverse by their range * in the prompt. That means the last variable in the prompt is the first in this list. This simplifies * string-manipulation of the prompt. */ @@ -344,8 +358,12 @@ declare module 'vscode' { readonly variables: readonly ChatResolvedVariable[]; } + /** + * The ChatResponseStream is how a participant is able to return content to the chat view. It provides several methods for streaming different types of content + * which will be rendered in an appropriate way in the chat view. A participant can use the helper method for the type of content it wants to return, or it + * can instantiate a {@link ChatResponsePart} and use the generic {@link ChatResponseStream.push} method to return it. + */ export interface ChatResponseStream { - /** * Push a markdown part to this stream. Short-hand for * `push(new ChatResponseMarkdownPart(value))`. @@ -359,6 +377,7 @@ declare module 'vscode' { /** * Push an anchor part to this stream. Short-hand for * `push(new ChatResponseAnchorPart(value, title))`. + * An anchor is an inline reference to some type of resource. * * @param value A uri or location * @param title An optional title that is rendered with value @@ -413,11 +432,6 @@ declare module 'vscode' { push(part: ChatResponsePart): ChatResponseStream; } - export class ChatResponseTextPart { - value: string; - constructor(value: string); - } - export class ChatResponseMarkdownPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -458,12 +472,11 @@ declare module 'vscode' { /** * Represents the different chat response types. */ - export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart + export type ChatResponsePart = ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; export namespace chat { - /** * Create a new {@link ChatParticipant chat participant} instance. * From 864f4f41cb18802a78da048fd81a9cffa392dd6c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:08:42 +0100 Subject: [PATCH 0482/1863] fix #205648 (#205653) --- .../files/browser/views/openEditorsView.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 9f21e5014ba6f..c6fd9a02c0d48 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -67,6 +67,7 @@ export class OpenEditorsView extends ViewPane { private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler | undefined; private structuralRefreshDelay: number; + private dnd: OpenEditorsDragAndDrop | undefined; private list: WorkbenchList | undefined; private listLabels: ResourceLabels | undefined; private needsRefresh = false; @@ -198,13 +199,16 @@ export class OpenEditorsView extends ViewPane { if (this.listLabels) { this.listLabels.clear(); } + + this.dnd = new OpenEditorsDragAndDrop(this.sortOrder, this.instantiationService, this.editorGroupService); + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this.list = this.instantiationService.createInstance(WorkbenchList, 'OpenEditors', container, delegate, [ new EditorGroupRenderer(this.keybindingService, this.instantiationService), new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService) ], { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, - dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), + dnd: this.dnd, overrideStyles: { listBackground: this.getBackgroundColor() }, @@ -448,6 +452,9 @@ export class OpenEditorsView extends ViewPane { // Trigger a 'repaint' when decoration settings change or the sort order changed if (event.affectsConfiguration('explorer.decorations') || event.affectsConfiguration('explorer.openEditors.sortOrder')) { this.sortOrder = this.configurationService.getValue('explorer.openEditors.sortOrder'); + if (this.dnd) { + this.dnd.sortOrder = this.sortOrder; + } this.listRefreshScheduler?.schedule(); } } @@ -672,10 +679,18 @@ class OpenEditorRenderer implements IListRenderer { + private _sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath'; + public set sortOrder(value: 'editorOrder' | 'alphabetical' | 'fullPath') { + this._sortOrder = value; + } + constructor( + sortOrder: 'editorOrder' | 'alphabetical' | 'fullPath', private instantiationService: IInstantiationService, private editorGroupService: IEditorGroupsService - ) { } + ) { + this._sortOrder = sortOrder; + } @memoize private get dropHandler(): ResourcesDropHandler { return this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false }); @@ -724,6 +739,16 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop Date: Tue, 20 Feb 2024 13:53:54 +0100 Subject: [PATCH 0483/1863] Hover Delay Fix (#205665) fix #205658 --- src/vs/base/browser/ui/iconLabel/iconLabelHover.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 508a643abb4e7..20ad4662b0d9c 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -180,6 +180,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM } if (hadHover) { hoverDelegate.onDidHideHover?.(); + hoverWidget = undefined; } }; From 035e55cf4bf4b550bbe1f77558d41d74e09b0934 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 20 Feb 2024 16:09:56 +0100 Subject: [PATCH 0484/1863] Adjust width for rename suggestions (#205689) rename suggestions: adjust width so longer candidates don't overflow --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index dc5131bba439f..cce562b9fb909 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -501,9 +501,11 @@ class CandidatesView { this._listWidget.splice(0, 0, candidates); - this._listWidget.layout(height, this._pickListWidth(candidates)); + const width = Math.max(200, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + this._pickListWidth(candidates)); // TODO@ulugbekna: approximate calc - clean this up + this._listWidget.layout(height, width); this._listContainer.style.height = `${height}px`; + this._listContainer.style.width = `${width}px`; } public clearCandidates(): void { @@ -577,7 +579,7 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - return Math.max(...candidates.map(c => c.newSymbolName.length)) * 7 /* approximate # of pixes taken by a single character */; + return Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * 7.2) /* approximate # of pixes taken by a single character */; } } From f34d48a3cdf7187eabb63df86a6d2706e2bc0008 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 20 Feb 2024 16:10:30 +0100 Subject: [PATCH 0485/1863] Add telemetry for settings in release notes (#205673) --- .../markdown/browser/markdownSettingRenderer.ts | 15 ++++++++++++++- .../test/browser/markdownSettingRenderer.test.ts | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 72f4dfd0e2ebb..5193d73e16406 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -13,6 +13,7 @@ import { DefaultSettings } from 'vs/workbench/services/preferences/common/prefer import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const codeSettingRegex = /^/; const codeFeatureRegex = /^/; @@ -26,7 +27,8 @@ export class SimpleSettingRenderer { constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IPreferencesService private readonly _preferencesService: IPreferencesService + @IPreferencesService private readonly _preferencesService: IPreferencesService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } @@ -291,6 +293,17 @@ export class SimpleSettingRenderer { async updateSetting(uri: URI, x: number, y: number) { if (uri.scheme === Schemas.codeSetting) { + type ReleaseNotesSettingUsedClassification = { + owner: 'alexr00'; + comment: 'Used to understand if the the action to update settings from the release notes is used.'; + settingId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the setting that was clicked on in the release notes' }; + }; + type ReleaseNotesSettingUsed = { + settingId: string; + }; + this._telemetryService.publicLog2('releaseNotesSettingAction', { + 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/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 271cf0d2da5da..fb609bd79b75d 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -61,7 +61,7 @@ suite('Markdown Setting Renderer Test', () => { preferencesService = {}; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any); }); suiteTeardown(() => { From 1f3bd326290cdee8204942da8dc3e843e5753373 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 16:55:40 +0100 Subject: [PATCH 0486/1863] adding option to serialize --- .../browser/parts/editor/editorsObserver.ts | 5 +++++ src/vs/workbench/common/editor.ts | 5 +++++ src/vs/workbench/common/editor/editorGroupModel.ts | 3 ++- .../bulkEdit/browser/preview/bulkEditPane.ts | 1 + .../multiDiffEditor/browser/multiDiffEditorInput.ts | 13 +++++++++++-- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index ac30da415d25e..bf55634f0c94b 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -430,19 +430,24 @@ export class EditorsObserver extends Disposable { entries: coalesce(entries.map(({ editor, groupId }) => { // Find group for entry + console.log('editor: ', editor); + console.log('groupId : ', groupId); const group = this.editorGroupsContainer.getGroup(groupId); + console.log('group : ', group); if (!group) { return undefined; } // Find serializable editors of group let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); + console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); if (!serializableEditorsOfGroup) { serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { const editorSerializer = registry.getEditorSerializer(editor); return editorSerializer?.canSerialize(editor); }); + console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index bf6785d967516..c6986bd0f57cf 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -505,6 +505,11 @@ export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { * If not set, the resources are dynamically derived from the {@link multiDiffSource}. */ readonly resources?: IResourceDiffEditorInput[]; + + /** + * Whether the editor should be serialized and stored for subsequent sessions. + */ + readonly isTransient?: boolean; } export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 497ef8daf46e9..017ac15184f47 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1032,9 +1032,10 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { let canSerializeEditor = false; const editorSerializer = registry.getEditorSerializer(editor); - if (editorSerializer) { + if (editorSerializer && editorSerializer.canSerialize(editor)) { const value = editorSerializer.serialize(editor); + console.log('value : ', value); // Editor can be serialized if (typeof value === 'string') { canSerializeEditor = true; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8739a79bbbb85..a543ab53b2cbb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -351,6 +351,7 @@ export class BulkEditPane extends ViewPane { resources, label, options, + isTransient: true, description: label }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 1cb31624f0652..041b8ac44a371 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -48,10 +48,12 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor resource.modified.resource, ); }), + input.isTransient ?? false ); } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { + console.log('inside of fromSerialized'); return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), @@ -59,7 +61,8 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor data.resources?.map(resource => new MultiDiffEditorItem( resource.originalUri ? URI.parse(resource.originalUri) : undefined, resource.modifiedUri ? URI.parse(resource.modifiedUri) : undefined, - )) + )), + false ); } @@ -80,12 +83,14 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor public readonly multiDiffSource: URI, public readonly label: string | undefined, public readonly initialResources: readonly MultiDiffEditorItem[] | undefined, + public readonly isTransient: boolean = false, @ITextModelService private readonly _textModelService: ITextModelService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, @ITextFileService private readonly _textFileService: ITextFileService, ) { + console.log('isTransient from the constructor : ', isTransient); super(); this._register(autorun((reader) => { @@ -361,14 +366,18 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { canSerialize(editor: EditorInput): boolean { - return editor instanceof MultiDiffEditorInput; + console.log('inside of can serialize'); + console.log('editor instanceof MultiDiffEditorInput && !editor.isTransient : ', editor instanceof MultiDiffEditorInput && !editor.isTransient); + return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { + console.log('inside of serialize'); return JSON.stringify(editor.serialize()); } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { + console.log('inside of deserialize'); try { const data = parse(serializedEditor) as ISerializedMultiDiffEditorInput; return MultiDiffEditorInput.fromSerialized(data, instantiationService); From 5c2f9eb177601d09ac2a2bfd0e922c558fc8a6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9A=B4=E8=BA=81=E6=9A=B4=E8=BA=81=E6=9C=80=E6=9A=B4?= =?UTF-8?q?=E8=BA=81/Bigforce?= Date: Tue, 20 Feb 2024 23:55:44 +0800 Subject: [PATCH 0487/1863] Fix browser host open additional files in merge mode (#205663) Co-authored-by: FANG.Ge --- src/vs/workbench/services/host/browser/browserHostService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 79c91e10dba3e..3ce96bba22a82 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -307,7 +307,7 @@ export class BrowserHostService extends Disposable implements IHostService { } // Support diffMode - if (options?.diffMode && fileOpenables.length === 2) { + else if (options?.diffMode && fileOpenables.length === 2) { const editors = coalesce(await pathsToEditors(fileOpenables, this.fileService, this.logService)); if (editors.length !== 2 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1])) { return; // invalid resources From 194a8dd18e097fa71277eb998a77cf8e2df6bee6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:23:51 +0100 Subject: [PATCH 0488/1863] using the uri of the multi file editor instead of using the schema for individual files inside the multi diff editor --- .../contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts | 3 +-- .../workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts | 3 ++- .../contrib/bulkEdit/browser/preview/bulkEditPreview.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 1d7f842bebcba..c3131b30d4495 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -14,7 +14,6 @@ import { localize, localize2 } from 'vs/nls'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; @@ -67,7 +66,7 @@ class UXState { for (const input of group.editors) { const resource = EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY }); - if (resource?.scheme === BulkEditPreviewProvider.Schema) { + if (resource?.scheme === BulkEditPane.Schema) { previewEditors.push(input); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 8739a79bbbb85..220a0bae8bdf8 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -48,6 +48,7 @@ const enum State { export class BulkEditPane extends ViewPane { static readonly ID = 'refactorPreview'; + static readonly Schema = 'vscode-bulkeditpreview-multieditor'; static readonly ctxHasCategories = new RawContextKey('refactorPreview.hasCategories', false); static readonly ctxGroupByFile = new RawContextKey('refactorPreview.groupByFile', true); @@ -344,7 +345,7 @@ export class BulkEditPane extends ViewPane { } } }; - const multiDiffSource = URI.from({ scheme: 'refactor-preview' }); + const multiDiffSource = URI.from({ scheme: BulkEditPane.Schema }); const label = 'Refactor Preview'; this._editorService.openEditor({ multiDiffSource, diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts index d9e8a1112a0d2..40fbb606f8b96 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts @@ -351,7 +351,7 @@ export class BulkFileOperations { export class BulkEditPreviewProvider implements ITextModelContentProvider { - static readonly Schema = 'vscode-bulkeditpreview'; + private static readonly Schema = 'vscode-bulkeditpreview-editor'; static emptyPreview = URI.from({ scheme: BulkEditPreviewProvider.Schema, fragment: 'empty' }); From 73ad7fb895b9d5a2a24600f3071513cd28c72338 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 17:26:51 +0100 Subject: [PATCH 0489/1863] voice - align hold to speak with inline chat (#205655) --- .../actions/voiceChatActions.ts | 95 ++++++++++++++----- .../electron-sandbox/chat.contribution.ts | 3 +- 2 files changed, 75 insertions(+), 23 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 e56a11a9ca647..e1e9306cb1c64 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -9,11 +9,10 @@ import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; @@ -36,7 +35,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme'; import { Color } from 'vs/base/common/color'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isNumber } from 'vs/base/common/types'; +import { assertIsDefined, isNumber } from 'vs/base/common/types'; import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -64,6 +63,7 @@ const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voice 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); +const FocusInChatInput = assertIsDefined(ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT)); type VoiceChatSessionContext = 'inline' | 'quick' | 'view' | 'editor'; @@ -92,7 +92,6 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise; static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); - const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); const chatContributionService = accessor.get(IChatContributionService); const quickChatService = accessor.get(IQuickChatService); @@ -136,12 +135,9 @@ class VoiceChatSessionControllerFactory { // View Chat if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { - const provider = firstOrDefault(chatService.getProviderInfos()); - if (provider) { - const chatView = await chatWidgetService.revealViewForProvider(provider.id); - if (chatView) { - return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService); - } + const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor); + if (chatView) { + return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService); } } @@ -169,6 +165,18 @@ class VoiceChatSessionControllerFactory { return undefined; } + static async revealChatView(accessor: ServicesAccessor): Promise { + const chatWidgetService = accessor.get(IChatWidgetService); + const chatService = accessor.get(IChatService); + + const provider = firstOrDefault(chatService.getProviderInfos()); + if (provider) { + return chatWidgetService.revealViewForProvider(provider.id); + } + + return undefined; + } + private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService); } @@ -414,7 +422,7 @@ async function startVoiceChatWithHoldMode(id: string, accessor: ServicesAccessor let acceptVoice = false; const handle = disposableTimeout(() => { acceptVoice = true; - session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for 250ms + session.setTimeoutDisabled(true); // disable accept on timeout when hold mode runs for VOICE_KEY_HOLD_THRESHOLD }, VOICE_KEY_HOLD_THRESHOLD); const controller = await VoiceChatSessionControllerFactory.create(accessor, target); @@ -459,6 +467,57 @@ export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { } } +export class HoldToVoiceChatInChatViewAction extends Action2 { + + static readonly ID = 'workbench.action.chat.holdToVoiceChatInChatView'; + + constructor() { + super({ + id: HoldToVoiceChatInChatViewAction.ID, + title: localize2('workbench.action.chat.holdToVoiceChatInChatView.label', "Hold to Voice Chat in View"), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + CanVoiceChat, + FocusInChatInput.negate(), // when already in chat input, disable this action and prefer to start voice chat directly + EditorContextKeys.focus.negate() // do not steal the inline-chat keybinding + ), + primary: KeyMod.CtrlCmd | KeyCode.KeyI + } + }); + } + + override async run(accessor: ServicesAccessor, context?: IChatExecuteActionContext): Promise { + + // The intent of this action is to provide 2 modes to align with what `Ctrlcmd+I` in inline chat: + // - if the user press and holds, we start voice chat in the chat view + // - if the user press and releases quickly enough, we just open the chat view without voice chat + + const instantiationService = accessor.get(IInstantiationService); + const keybindingService = accessor.get(IKeybindingService); + + const holdMode = keybindingService.enableKeybindingHoldMode(HoldToVoiceChatInChatViewAction.ID); + + let session: IVoiceChatSession | undefined; + const handle = disposableTimeout(async () => { + const controller = await VoiceChatSessionControllerFactory.create(accessor, 'view'); + if (controller) { + session = VoiceChatSessions.getInstance(instantiationService).start(controller, context); + session.setTimeoutDisabled(true); + } + }, VOICE_KEY_HOLD_THRESHOLD); + + (await VoiceChatSessionControllerFactory.revealChatView(accessor))?.focusInput(); + + await holdMode; + handle.dispose(); + + if (session) { + session.accept(); + } + } +} + export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { static readonly ID = 'workbench.action.chat.inlineVoiceChat'; @@ -502,11 +561,8 @@ export class StartVoiceChatAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and( - CanVoiceChat, - EditorContextKeys.focus.toNegated(), // do not steal the inline-chat keybinding - CONTEXT_VOICE_CHAT_GETTING_READY.negate(), - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), - CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST.negate(), + FocusInChatInput, // scope this action to chat input fields only + EditorContextKeys.focus.negate(), // do not steal the inline-chat keybinding CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS.negate(), CONTEXT_QUICK_VOICE_CHAT_IN_PROGRESS.negate(), CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS.negate(), @@ -629,7 +685,6 @@ class BaseStopListeningAction extends Action2 { category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib + 100, - when: ContextKeyExpr.and(CanVoiceChat, context), primary: KeyCode.Escape }, precondition: ContextKeyExpr.and(CanVoiceChat, context), @@ -704,11 +759,7 @@ export class StopListeningAndSubmitAction extends Action2 { f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and( - CanVoiceChat, - ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT), - CONTEXT_VOICE_CHAT_IN_PROGRESS - ), + when: FocusInChatInput, primary: KeyMod.CtrlCmd | KeyCode.KeyI }, precondition: ContextKeyExpr.and(CanVoiceChat, CONTEXT_VOICE_CHAT_IN_PROGRESS) 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 2f69e1b13f87e..08b145350e175 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, HoldToVoiceChatInChatViewAction } 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'; @@ -11,6 +11,7 @@ registerAction2(StartVoiceChatAction); registerAction2(InstallVoiceChatAction); registerAction2(VoiceChatInChatViewAction); +registerAction2(HoldToVoiceChatInChatViewAction); registerAction2(QuickVoiceChatAction); registerAction2(InlineVoiceChatAction); From 4aac6ba4d9baf0c18201a8f2181d2d63f9431940 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 17:27:47 +0100 Subject: [PATCH 0490/1863] window - fix window state validation to actually apply properly and add more logging (#205677) --- .../auxiliaryWindowsMainService.ts | 10 ++++--- .../platform/window/electron-main/window.ts | 26 ++++++++++++++++++- .../windows/electron-main/windowImpl.ts | 1 - .../platform/windows/electron-main/windows.ts | 25 +++++++++--------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts index 786f2ef963945..1174386d1c2f2 100644 --- a/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts +++ b/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -12,7 +12,7 @@ import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/e import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowState } from 'vs/platform/window/electron-main/window'; +import { IWindowState, defaultAuxWindowState } from 'vs/platform/window/electron-main/window'; import { WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService { @@ -79,7 +79,7 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar }); } - private validateWindowState(details: HandlerDetails): IWindowState | undefined { + private validateWindowState(details: HandlerDetails): IWindowState { const windowState: IWindowState = {}; const features = details.features.split(','); // for example: popup=yes,left=270,top=14.5,width=800,height=600 @@ -101,7 +101,11 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar } } - return WindowStateValidator.validateWindowState(this.logService, windowState); + const state = WindowStateValidator.validateWindowState(this.logService, windowState) ?? defaultAuxWindowState(); + + this.logService.trace('[aux window] using window state', state); + + return state; } registerWindow(webContents: WebContents): void { diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 6f744cf645458..04795036c4453 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BrowserWindow, Rectangle } from 'electron'; +import { BrowserWindow, Rectangle, screen } from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -145,6 +145,30 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt }; }; +export const defaultAuxWindowState = function (): IWindowState { + + // Auxiliary windows are being created from a `window.open` call + // that sets `windowFeatures` that encode the desired size and + // position of the new window (`top`, `left`). + // In order to truly override this to a good default window state + // we need to set not only width and height but also x and y to + // a good location on the primary display. + + const width = 800; + const height = 600; + const workArea = screen.getPrimaryDisplay().workArea; + const x = Math.max(workArea.x + (workArea.width / 2) - (width / 2), 0); + const y = Math.max(workArea.y + (workArea.height / 2) - (height / 2), 0); + + return { + x, + y, + width, + height, + mode: WindowMode.Normal + }; +}; + export const enum WindowMode { Maximized, Normal, diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 3041b4e0d7756..807bb60113660 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -570,7 +570,6 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } }); - // Create the browser window mark('code/willCreateCodeBrowserWindow'); this._win = new BrowserWindow(options); diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index c5fd87557d72a..0cccca7c81091 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -115,7 +115,7 @@ export interface IOpenConfiguration extends IBaseOpenConfiguration { export interface IOpenEmptyConfiguration extends IBaseOpenConfiguration { } -export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState?: IWindowState, overrides?: BrowserWindowConstructorOptions): BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { +export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowState: IWindowState, overrides?: BrowserWindowConstructorOptions): BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } { const themeMainService = accessor.get(IThemeMainService); const productService = accessor.get(IProductService); const configurationService = accessor.get(IConfigurationService); @@ -129,10 +129,14 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt minHeight: WindowMinimumSize.HEIGHT, title: productService.nameLong, ...overrides, + x: windowState.x, + y: windowState.y, + width: windowState.width, + height: windowState.height, webPreferences: { enableWebSQL: false, spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(windowState?.zoomLevel ?? windowSettings?.zoomLevel), + zoomFactor: zoomLevelToZoomFactor(windowState.zoomLevel ?? windowSettings?.zoomLevel), autoplayPolicy: 'user-gesture-required', // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 @@ -143,13 +147,6 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt experimentalDarkMode: true }; - if (windowState) { - options.x = windowState.x; - options.y = windowState.y; - options.width = windowState.width; - options.height = windowState.height; - } - if (isLinux) { options.icon = join(environmentMainService.appRoot, 'resources/linux/code.png'); // always on Linux } else if (isWindows && !environmentMainService.isBuilt) { @@ -246,8 +243,9 @@ export namespace WindowStateValidator { // some pixels (128) visible on the screen for the user to drag it back. if (displays.length === 1) { const displayWorkingArea = getWorkingArea(displays[0]); + logService.trace('window#validateWindowState: single monitor working area', displayWorkingArea); + if (displayWorkingArea) { - logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); function ensureStateInDisplayWorkingArea(): void { if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { @@ -320,10 +318,13 @@ export namespace WindowStateValidator { try { display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); displayWorkingArea = getWorkingArea(display); + + logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); } catch (error) { // Electron has weird conditions under which it throws errors // e.g. https://github.com/microsoft/vscode/issues/100334 when // large numbers are passed in + logService.error('window#validateWindowState: error finding display for window state', error); } if ( @@ -334,11 +335,11 @@ export namespace WindowStateValidator { state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom ) { - logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); - return state; } + logService.trace('window#validateWindowState: state is outside of the multi-monitor working area'); + return undefined; } From 81e1ca04f8552b588221d88d96ae00b94540bfef Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:31:44 +0100 Subject: [PATCH 0491/1863] cleaning the code --- .../workbench/browser/parts/editor/editorsObserver.ts | 5 ----- src/vs/workbench/common/editor/editorGroupModel.ts | 3 +-- .../multiDiffEditor/browser/multiDiffEditorInput.ts | 11 ++++------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index bf55634f0c94b..ac30da415d25e 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -430,24 +430,19 @@ export class EditorsObserver extends Disposable { entries: coalesce(entries.map(({ editor, groupId }) => { // Find group for entry - console.log('editor: ', editor); - console.log('groupId : ', groupId); const group = this.editorGroupsContainer.getGroup(groupId); - console.log('group : ', group); if (!group) { return undefined; } // Find serializable editors of group let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); - console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); if (!serializableEditorsOfGroup) { serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { const editorSerializer = registry.getEditorSerializer(editor); return editorSerializer?.canSerialize(editor); }); - console.log('serializableEditorsOfGroup : ', serializableEditorsOfGroup); mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); } diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 017ac15184f47..497ef8daf46e9 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1032,10 +1032,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { let canSerializeEditor = false; const editorSerializer = registry.getEditorSerializer(editor); - if (editorSerializer && editorSerializer.canSerialize(editor)) { + if (editorSerializer) { const value = editorSerializer.serialize(editor); - console.log('value : ', value); // Editor can be serialized if (typeof value === 'string') { canSerializeEditor = true; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 041b8ac44a371..3d26c944df208 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -53,7 +53,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor } public static fromSerialized(data: ISerializedMultiDiffEditorInput, instantiationService: IInstantiationService): MultiDiffEditorInput { - console.log('inside of fromSerialized'); return instantiationService.createInstance( MultiDiffEditorInput, URI.parse(data.multiDiffSourceUri), @@ -90,7 +89,6 @@ export class MultiDiffEditorInput extends EditorInput implements ILanguageSuppor @IMultiDiffSourceResolverService private readonly _multiDiffSourceResolverService: IMultiDiffSourceResolverService, @ITextFileService private readonly _textFileService: ITextFileService, ) { - console.log('isTransient from the constructor : ', isTransient); super(); this._register(autorun((reader) => { @@ -365,19 +363,18 @@ interface ISerializedMultiDiffEditorInput { } export class MultiDiffEditorSerializer implements IEditorSerializer { + + // TODO@bpasero, @aiday-mar: following should be removed canSerialize(editor: EditorInput): boolean { - console.log('inside of can serialize'); - console.log('editor instanceof MultiDiffEditorInput && !editor.isTransient : ', editor instanceof MultiDiffEditorInput && !editor.isTransient); return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { - console.log('inside of serialize'); - return JSON.stringify(editor.serialize()); + const shouldSerialize = editor instanceof MultiDiffEditorInput && !editor.isTransient; + return shouldSerialize ? JSON.stringify(editor.serialize()) : undefined; } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { - console.log('inside of deserialize'); try { const data = parse(serializedEditor) as ISerializedMultiDiffEditorInput; return MultiDiffEditorInput.fromSerialized(data, instantiationService); From 02359773c2255a1625ad72205729fa53cc2e0e8f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 20 Feb 2024 17:32:48 +0100 Subject: [PATCH 0492/1863] adding more explicit message --- .../contrib/multiDiffEditor/browser/multiDiffEditorInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 3d26c944df208..3841a5d56aa62 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -364,7 +364,7 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { - // TODO@bpasero, @aiday-mar: following should be removed + // TODO@bpasero, @aiday-mar: following canSerialize should be removed (debt item) canSerialize(editor: EditorInput): boolean { return editor instanceof MultiDiffEditorInput && !editor.isTransient; } From 5bc6776982eb94191421fd211edf316a6bf6cb4a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:41:42 +0100 Subject: [PATCH 0493/1863] Gix - recompute diagnostics when settings change (#205711) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 9df39df517765..16c29c00ac664 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -24,7 +24,7 @@ export class GitCommitInputBoxDiagnosticsManager { this.migrateInputValidationSettings() .then(() => { mapEvent(filterEvent(workspace.onDidChangeTextDocument, e => e.document.uri.scheme === 'vscode-scm'), e => e.document)(this.onDidChangeTextDocument, this, this.disposables); - filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation'))(this.onDidChangeConfiguration, this, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.inputValidation') || e.affectsConfiguration('git.inputValidationLength') || e.affectsConfiguration('git.inputValidationSubjectLength'))(this.onDidChangeConfiguration, this, this.disposables); }); } From 94ad9a4199cde13241a9ddc25096979e6d88a297 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:45:25 +0100 Subject: [PATCH 0494/1863] Git - rename code action (#205712) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index 16c29c00ac664..c19fb563b1967 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -123,7 +123,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const workspaceEdit = new WorkspaceEdit(); workspaceEdit.delete(document.uri, diagnostic.range); - const codeAction = new CodeAction(l10n.t('Remove empty characters'), CodeActionKind.QuickFix); + const codeAction = new CodeAction(l10n.t('Clear whitespace characters'), CodeActionKind.QuickFix); codeAction.diagnostics = [diagnostic]; codeAction.edit = workspaceEdit; codeActions.push(codeAction); From 7560c3db8c90b1f7888e2348f792816b303697e1 Mon Sep 17 00:00:00 2001 From: RedCMD <33529441+RedCMD@users.noreply.github.com> Date: Wed, 21 Feb 2024 06:06:40 +1300 Subject: [PATCH 0495/1863] Improve extension `README` preview markdown codeblock language detection (#205329) --- .../contrib/markdown/browser/markdownDocumentRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index fce923cad92a6..8ac086cada8c3 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -215,7 +215,7 @@ export async function renderMarkdownDocument( return; } - const languageId = languageService.getLanguageIdByLanguageName(lang); + const languageId = languageService.getLanguageIdByLanguageName(lang) ?? languageService.getLanguageIdByLanguageName(lang.split(/\s+|:|,|(?!^)\{|\?]/, 1)[0]); const html = await tokenizeToString(languageService, code, languageId); callback(null, `${html}`); }); From 3dc8040fe4999f0db1596740b85bd2d52d85702a Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 20 Feb 2024 18:17:27 +0100 Subject: [PATCH 0496/1863] Fix notebook actionItem hovers (#205719) fix notebook actionItem hovers --- .../browser/view/cellParts/cellActionView.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 a50b67ca2b678..c2c7f3e740a78 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -13,6 +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'; export class CodiconActionViewItem extends MenuEntryActionViewItem { @@ -38,12 +40,12 @@ export class ActionViewWithLabel extends MenuEntryActionViewItem { if (this._actionLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; } } } export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { private _actionLabel?: HTMLAnchorElement; + private _hover?: ICustomHover; constructor( action: SubmenuItemAction, @@ -63,6 +65,10 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { container.classList.add('notebook-action-view-item'); this._actionLabel = document.createElement('a'); container.appendChild(this._actionLabel); + + const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); + this._hover = this._register(setupCustomHover(hoverDelegate, this._actionLabel, '')); + this.updateLabel(); } @@ -88,13 +94,13 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { if (this.renderLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label; + this._hover?.update(primaryAction.tooltip.length ? primaryAction.tooltip : primaryAction.label); } } else { if (this.renderLabel) { this._actionLabel.classList.add('notebook-label'); this._actionLabel.innerText = this._action.label; - this._actionLabel.title = this._action.tooltip.length ? this._action.tooltip : this._action.label; + this._hover?.update(this._action.tooltip.length ? this._action.tooltip : this._action.label); } } } From 673a1aeca1ed358222c25de98570f23060997b49 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 20 Feb 2024 09:35:54 -0800 Subject: [PATCH 0497/1863] fix #205445 (#205720) --- .../extensionManagement/common/extensionManagementCLI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts index 05ee97927fec7..2dd973658b956 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementCLI.ts @@ -137,7 +137,7 @@ export class ExtensionManagementCLI { } } - this.logger.trace(localize('updateExtensionsQuery', "Fetching latest versions for {0} extensions", installedExtensionsQuery.length)); + this.logger.trace(localize({ key: 'updateExtensionsQuery', comment: ['Placeholder is for the count of extensions'] }, "Fetching latest versions for {0} extensions", installedExtensionsQuery.length)); const availableVersions = await this.extensionGalleryService.getExtensions(installedExtensionsQuery, { compatible: true }, CancellationToken.None); const extensionsToUpdate: InstallExtensionInfo[] = []; From 025b4c9435a928cd38b317a3b72e52e5a69b4b1f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 20 Feb 2024 20:31:13 +0100 Subject: [PATCH 0498/1863] voice - allow dictation in SCM input and comments (#205738) --- .../workbench/contrib/comments/browser/simpleCommentEditor.ts | 2 ++ src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index cda25cbdfac99..2a4f2c3a2bbf6 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -13,6 +13,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; // Allowed Editor Contributions: import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; +import { EditorDictation } from 'vs/workbench/contrib/codeEditor/browser/dictation/editorDictation'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; @@ -62,6 +63,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { { id: SuggestController.ID, ctor: SuggestController, instantiation: EditorContributionInstantiation.Eager }, { id: SnippetController2.ID, ctor: SnippetController2, instantiation: EditorContributionInstantiation.Lazy }, { id: TabCompletionController.ID, ctor: TabCompletionController, instantiation: EditorContributionInstantiation.Eager }, // eager because it needs to define a context key + { id: EditorDictation.ID, ctor: EditorDictation, instantiation: EditorContributionInstantiation.Lazy } ] }; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e075d69cca4cc..f91410f9b8d2b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -51,6 +51,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { EditorDictation } from 'vs/workbench/contrib/codeEditor/browser/dictation/editorDictation'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import * as platform from 'vs/base/common/platform'; import { compare, format } from 'vs/base/common/strings'; @@ -2444,7 +2445,8 @@ class SCMInputWidget { SuggestController.ID, InlineCompletionsController.ID, CodeActionController.ID, - FormatOnType.ID + FormatOnType.ID, + EditorDictation.ID ]) }; From 5e9e54773a6924b88d64dd61c64142bf355a74fb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 20 Feb 2024 20:13:42 +0000 Subject: [PATCH 0499/1863] Don't rely on options from setInput (#205745) Fix microsoft/vscode-copilot-release#914 --- src/vs/workbench/contrib/chat/browser/chatEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 8401da7b17226..4651778b16558 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -81,7 +81,7 @@ export class ChatEditor extends EditorPane { super.clearInput(); } - override async setInput(input: ChatEditorInput, options: IChatEditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise { + override async setInput(input: ChatEditorInput, options: IChatEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { super.setInput(input, options, context, token); const editorModel = await input.resolve(); @@ -93,7 +93,7 @@ export class ChatEditor extends EditorPane { throw new Error('ChatEditor lifecycle issue: no editor widget'); } - this.updateModel(editorModel.model, options.viewState); + this.updateModel(editorModel.model, options?.viewState ?? input.options.viewState); } private updateModel(model: IChatModel, viewState?: IChatViewState): void { From 926bf1a1f2b3d098f47383597b28fb1d5a4d7f2c Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:44:06 -0800 Subject: [PATCH 0500/1863] code action ranges api doc improvements (#205771) doc improvements --- src/vscode-dts/vscode.proposed.codeActionRanges.d.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts index 704208454d7cd..350be2d553668 100644 --- a/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts +++ b/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts @@ -7,10 +7,8 @@ declare module 'vscode' { export interface CodeAction { /** - * - * The range to which this Code Action applies to, which will be highlighted. - * - * Ex: A refactoring action will highlight the range of text that will be affected. + * The ranges to which this Code Action applies to, which will be highlighted. + * For example: A refactoring action will highlight the range of text that will be affected. */ ranges?: Range[]; } From a1e1ff68449087c4a4dd59b593663505d9143543 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 01:36:20 +0100 Subject: [PATCH 0501/1863] editors - drop generic `options` parameter in editor input `resolve` method (#205751) --- .../workbench/browser/parts/editor/binaryEditor.ts | 2 +- .../workbench/browser/parts/editor/textDiffEditor.ts | 2 +- .../browser/parts/editor/textResourceEditor.ts | 2 +- src/vs/workbench/common/editor/diffEditorInput.ts | 12 ++++++------ src/vs/workbench/common/editor/editorInput.ts | 3 +-- .../contrib/preferences/browser/settingsEditor2.ts | 2 +- .../contrib/webviewPanel/browser/webviewEditor.ts | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 0fb42552e45bd..cba829c7be515 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -46,7 +46,7 @@ export abstract class BaseBinaryResourceEditor extends EditorPlaceholder { } protected async getContents(input: EditorInput, options: IEditorOptions): Promise { - const model = await input.resolve(options); + const model = await input.resolve(); // Assert Model instance if (!(model instanceof BinaryEditorModel)) { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 455922bbcbfdd..21429e3733400 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -106,7 +106,7 @@ export class TextDiffEditor extends AbstractTextEditor imp await super.setInput(input, options, context, token); try { - const resolvedModel = await input.resolve(options); + const resolvedModel = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 34762256f40ed..7a16fa667b83c 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -53,7 +53,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< // Set input and resolve await super.setInput(input, options, context, token); - const resolvedModel = await input.resolve(options); + const resolvedModel = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 1d6e611875149..f9b9aeb58b287 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -14,7 +14,7 @@ import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorMo import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { shorten } from 'vs/base/common/labels'; -import { IEditorOptions, isResolvedEditorModel } from 'vs/platform/editor/common/editor'; +import { isResolvedEditorModel } from 'vs/platform/editor/common/editor'; interface IDiffEditorInputLabels { name: string; @@ -171,13 +171,13 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito } } - override async resolve(options?: IEditorOptions): Promise { + override async resolve(): Promise { // Create Model - we never reuse our cached model if refresh is true because we cannot // decide for the inputs within if the cached model can be reused or not. There may be // inputs that need to be loaded again and thus we always recreate the model and dispose // the previous one - if any. - const resolvedModel = await this.createModel(options); + const resolvedModel = await this.createModel(); this.cachedModel?.dispose(); this.cachedModel = resolvedModel; @@ -193,12 +193,12 @@ export class DiffEditorInput extends SideBySideEditorInput implements IDiffEdito return editorPanes.find(editorPane => editorPane.typeId === TEXT_DIFF_EDITOR_ID); } - private async createModel(options?: IEditorOptions): Promise { + private async createModel(): Promise { // Join resolve call over two inputs and build diff editor model const [originalEditorModel, modifiedEditorModel] = await Promise.all([ - this.original.resolve(options), - this.modified.resolve(options) + this.original.resolve(), + this.modified.resolve() ]); // If both are text models, return textdiffeditor model diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index 9bce607e38701..8b80fe942a322 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -5,7 +5,6 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { firstOrDefault } from 'vs/base/common/arrays'; import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, AbstractEditorInput, isEditorInput, IEditorIdentifier } from 'vs/workbench/common/editor'; import { isEqual } from 'vs/base/common/resources'; @@ -235,7 +234,7 @@ export abstract class EditorInput extends AbstractEditorInput { * The `options` parameter are passed down from the editor when the * input is resolved as part of it. */ - async resolve(options?: IEditorOptions): Promise { + async resolve(): Promise { return null; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9df8e9b70d650..a06c804835153 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -355,7 +355,7 @@ export class SettingsEditor2 extends EditorPane { return; } - const model = await this.input.resolve(options); + const model = await this.input.resolve(); if (token.isCancellationRequested || !(model instanceof Settings2EditorModel)) { return; } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 8a51934944637..f9b3f0c34f3c6 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -154,7 +154,7 @@ export class WebviewEditor extends EditorPane { } await super.setInput(input, options, context, token); - await input.resolve(options); + await input.resolve(); if (token.isCancellationRequested || this._isDisposed) { return; From a7002f6b66713b57de06510d9b14b25249d3d96e Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 20 Feb 2024 17:18:34 -0800 Subject: [PATCH 0502/1863] Use NotebookOptions for statusbar instead of viewmodel options (#205791) fix nb indentation actions + get nb options instead of textmodel options for statusbar --- .../contrib/editorStatusBar/editorStatusBar.ts | 8 +++++--- .../controller/notebookIndentationActions.ts | 16 +++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 9900e41b0c3c3..bbfe3a2609d26 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -303,9 +303,11 @@ export class NotebookIndentationStatus extends Disposable implements IWorkbenchC this._accessor.clear(); return; } - const indentSize = cellOptions?.indentSize; - const tabSize = cellOptions?.tabSize; - const insertSpaces = cellOptions?.insertSpaces; + + const cellEditorOverridesRaw = editor.notebookOptions.getDisplayOptions().editorOptionsCustomizations; + const indentSize = cellEditorOverridesRaw['editor.indentSize'] ?? cellOptions?.indentSize; + const insertSpaces = cellEditorOverridesRaw['editor.insertSpaces'] ?? cellOptions?.tabSize; + const tabSize = cellEditorOverridesRaw['editor.tabSize'] ?? cellOptions?.insertSpaces; const width = typeof indentSize === 'number' ? indentSize : tabSize; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts index 58d4bf63b6d61..c647ab969d7b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts @@ -24,7 +24,7 @@ export class NotebookIndentUsingTabs extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentUsingTabs.ID, title: nls.localize('indentUsingTabs', "Indent Using Tabs"), precondition: undefined, }); @@ -56,7 +56,7 @@ export class NotebookChangeTabDisplaySize extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookChangeTabDisplaySize.ID, title: nls.localize('changeTabDisplaySize', "Change Tab Display Size"), precondition: undefined, }); @@ -72,7 +72,7 @@ export class NotebookIndentationToSpacesAction extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentationToSpacesAction.ID, title: nls.localize('convertIndentationToSpaces', "Convert Indentation to Spaces"), precondition: undefined, }); @@ -80,7 +80,6 @@ export class NotebookIndentationToSpacesAction extends Action2 { override run(accessor: ServicesAccessor, ...args: any[]): void { convertNotebookIndentation(accessor, true); - } } @@ -89,7 +88,7 @@ export class NotebookIndentationToTabsAction extends Action2 { constructor() { super({ - id: NotebookIndentUsingSpaces.ID, + id: NotebookIndentationToTabsAction.ID, title: nls.localize('convertIndentationToTabs', "Convert Indentation to Tabs"), precondition: undefined, }); @@ -194,8 +193,7 @@ function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: bo bulkEditService.apply(edits, { label: nls.localize('convertIndentation', "Convert Indentation"), code: 'undoredo.convertIndentation', }); - })).then((notebookEdits) => { - + })).then(() => { // store the initial values of the configuration const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any; const initialIndentSize = initialConfig['editor.indentSize']; @@ -255,3 +253,7 @@ function getIndentationEditOperations(model: ITextModel, tabSize: number, tabsTo } registerAction2(NotebookIndentUsingSpaces); +registerAction2(NotebookIndentUsingTabs); +registerAction2(NotebookChangeTabDisplaySize); +registerAction2(NotebookIndentationToSpacesAction); +registerAction2(NotebookIndentationToTabsAction); From ee69e2887fbe532588f74ae86560e7fdc1e59550 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 20 Feb 2024 18:35:32 -0800 Subject: [PATCH 0503/1863] Notebook chat editing enhancements. (#205799) * Notebook chat editing enhancements. * hide cell chat actions from f1. --- .../notebook/browser/controller/chat/cellChatActions.ts | 2 ++ .../browser/controller/chat/notebookChatController.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index 8c97251257ec4..430062261c9fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -382,6 +382,7 @@ registerAction2(class extends NotebookAction { } ] }, + f1: false, menu: [ { id: MenuId.NotebookCellBetween, @@ -454,6 +455,7 @@ registerAction2(class extends NotebookAction { original: '$(sparkle) Generate', }, tooltip: localize('notebookActions.menu.insertCodeCellWithChat.tooltip', "Generate Code Cell with Chat"), + f1: false, menu: [ { id: MenuId.NotebookCellListTop, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 7686f52b8b307..cec4fe9242f63 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -369,7 +369,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito const cellTop = this._notebookEditor.getAbsoluteTopOfElement(previousCell); const cellHeight = this._notebookEditor.getHeightOfElement(previousCell); - this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight); + this._notebookEditor.revealOffsetInCenterIfOutsideViewport(cellTop + cellHeight + 48 /** center of the dialog */); } } } @@ -412,6 +412,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito return; } + if (this._widget.editingCell && this._widget.editingCell.textBuffer.getLength() > 0) { + // it already contains some text, clear it + const ref = await this._widget.editingCell.resolveTextModel(); + ref.setValue(''); + } + const editingCellIndex = this._widget.editingCell ? this._notebookEditor.getCellIndex(this._widget.editingCell) : undefined; if (editingCellIndex !== undefined) { this._notebookEditor.setSelections([{ From 9e92682062c60f6d175ceacd22e5a5d6eb82ebd8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 09:58:53 +0100 Subject: [PATCH 0504/1863] some API todos for variable resolver (#205828) --- src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts index 32f081ecc5d81..eb6f0882d63e9 100644 --- a/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts +++ b/src/vscode-dts/vscode.proposed.chatVariableResolver.d.ts @@ -33,11 +33,15 @@ declare module 'vscode' { description?: string; } + // TODO@API align with ChatRequest export interface ChatVariableContext { /** * The message entered by the user, which includes this variable. */ + // TODO@API AS-IS, variables as types, agent/commands stripped prompt: string; + + // readonly variables: readonly ChatResolvedVariable[]; } export interface ChatVariableResolver { From 8895f460c69c8c9be55d280baedc39d82361079f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:26:15 +0100 Subject: [PATCH 0505/1863] Git - better handle edge case when hard wrapping a line (#205831) --- extensions/git/src/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/diagnostics.ts b/extensions/git/src/diagnostics.ts index c19fb563b1967..a8c1a3deea3c5 100644 --- a/extensions/git/src/diagnostics.ts +++ b/extensions/git/src/diagnostics.ts @@ -190,7 +190,7 @@ export class GitCommitInputBoxCodeActionsProvider implements CodeActionProvider const lineLengthThreshold = line === 0 ? inputValidationSubjectLength ?? inputValidationLength : inputValidationLength; const lineSegments: string[] = []; - const lineText = document.lineAt(line).text; + const lineText = document.lineAt(line).text.trim(); let position = 0; while (lineText.length - position > lineLengthThreshold) { From f7f9fb09a3200246a55da9d6882d56b82eb4fe9a Mon Sep 17 00:00:00 2001 From: Andy Hippo Date: Wed, 21 Feb 2024 10:36:04 +0100 Subject: [PATCH 0506/1863] Fix memory leaks (#205589) * Fix memory leaks The activities in `treeView.ts` and `reportExplorer.ts` are not disposed when the parent object is disposed. Also fix the `contextKeyListener` disposable while we're here. The activity in `webviewViewPane.ts` is never disposed, because the return value is ignored. --- src/vs/workbench/browser/parts/views/treeView.ts | 13 ++++--------- .../contrib/remote/browser/remoteExplorer.ts | 16 ++++++---------- .../webviewView/browser/webviewViewPane.ts | 9 ++------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index d142226edb744..a46d7bc51d612 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -22,7 +22,7 @@ import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; import { basename, dirname } from 'vs/base/common/resources'; @@ -426,7 +426,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } private _badge: IViewBadge | undefined; - private _badgeActivity: IDisposable | undefined; + private readonly _activity = this._register(new MutableDisposable()); + get badge(): IViewBadge | undefined { return this._badge; } @@ -438,19 +439,13 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { return; } - if (this._badgeActivity) { - this._badgeActivity.dispose(); - this._badgeActivity = undefined; - } - this._badge = badge; - if (badge) { const activity = { badge: new NumberBadge(badge.value, () => badge.tooltip), priority: 50 }; - this._badgeActivity = this.activityService.showViewActivity(this.id, activity); + this._activity.value = this.activityService.showViewActivity(this.id, activity); } } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 880ccebd8e040..37948b2bf608c 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions, IViewContainersRegistry, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IRemoteExplorerService, PORT_AUTO_FALLBACK_SETTING, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_HYBRID, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService'; @@ -41,8 +41,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag export const VIEWLET_ID = 'workbench.view.remote'; export class ForwardedPortsView extends Disposable implements IWorkbenchContribution { - private contextKeyListener?: IDisposable; - private _activityBadge?: IDisposable; + private readonly contextKeyListener = this._register(new MutableDisposable()); + private readonly activityBadge = this._register(new MutableDisposable()); private entryAccessor: IStatusbarEntryAccessor | undefined; constructor( @@ -75,10 +75,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu } private async enableForwardedPortsView() { - if (this.contextKeyListener) { - this.contextKeyListener.dispose(); - this.contextKeyListener = undefined; - } + this.contextKeyListener.clear(); const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); @@ -91,7 +88,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu viewsRegistry.registerViews([tunnelPanelDescriptor], viewContainer); } } else { - this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => { + this.contextKeyListener.value = this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { this.enableForwardedPortsView(); } @@ -119,9 +116,8 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu } private async updateActivityBadge() { - this._activityBadge?.dispose(); if (this.remoteExplorerService.tunnelModel.forwarded.size > 0) { - this._activityBadge = this.activityService.showViewActivity(TUNNEL_VIEW_ID, { + this.activityBadge.value = this.activityService.showViewActivity(TUNNEL_VIEW_ID, { badge: new NumberBadge(this.remoteExplorerService.tunnelModel.forwarded.size, n => n === 1 ? nls.localize('1forwardedPort', "1 forwarded port") : nls.localize('nForwardedPorts', "{0} forwarded ports", n)) }); } diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index e44af5edc0ceb..41cc58d865297 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -57,7 +57,7 @@ export class WebviewViewPane extends ViewPane { private setTitle: string | undefined; private badge: IViewBadge | undefined; - private activity: IDisposable | undefined; + private readonly activity = this._register(new MutableDisposable()); private readonly memento: Memento; private readonly viewState: MementoObject; @@ -256,18 +256,13 @@ export class WebviewViewPane extends ViewPane { return; } - if (this.activity) { - this.activity.dispose(); - this.activity = undefined; - } - this.badge = badge; if (badge) { const activity = { badge: new NumberBadge(badge.value, () => badge.tooltip), priority: 150 }; - this.activityService.showViewActivity(this.id, activity); + this.activity.value = this.activityService.showViewActivity(this.id, activity); } } From 2411a9c9136657cd7b5558951021a4d9abd9a6b8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 11:06:38 +0100 Subject: [PATCH 0507/1863] add `inlineChat.accessibleDiffView` setting (#205837) fixes https://github.com/microsoft/vscode/issues/205714 --- .../inlineChat/browser/inlineChatStrategies.ts | 8 ++++++-- .../contrib/inlineChat/common/inlineChat.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 9659c4cdb28d6..63088a27ed5e4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -31,12 +31,13 @@ import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; -import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { HunkState } from './inlineChatSession'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IEditObserver { start(): void; @@ -445,6 +446,7 @@ export class LiveStrategy extends EditModeStrategy { @IContextKeyService contextKeyService: IContextKeyService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IConfigurationService private readonly _configService: IConfigurationService, @IInstantiationService protected readonly _instaService: IInstantiationService, ) { super(session, editor, zone); @@ -705,7 +707,9 @@ export class LiveStrategy extends EditModeStrategy { const remainingHunks = this._session.hunkData.pending; this._updateSummaryMessage(remainingHunks); - if (this._accessibilityService.isScreenReaderOptimized()) { + + const mode = this._configService.getValue<'on' | 'off' | 'auto'>(InlineChatConfigKeys.AccessibleDiffView); + if (mode === 'on' || mode === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index d339e9f5b077f..dbf95c3bb0bbb 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -227,6 +227,7 @@ export const enum InlineChatConfigKeys { FinishOnType = 'inlineChat.finishOnType', AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave', HoldToSpeech = 'inlineChat.holdToSpeech', + AccessibleDiffView = 'inlineChat.accessibleDiffView' } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -257,6 +258,17 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('holdToSpeech', "Whether holding the inline chat keybinding will automatically enable speech recognition."), default: true, type: 'boolean' + }, + [InlineChatConfigKeys.AccessibleDiffView]: { + description: localize('accessibleDiffView', "Whether the inline chat also renders an accessible diff viewer for its changes."), + default: 'auto', + type: 'string', + enum: ['auto', 'on', 'off'], + markdownEnumDescriptions: [ + localize('accessibleDiffView.auto', "The accessible diff viewer is based screen reader mode being enabled."), + localize('accessibleDiffView.on', "The accessible diff viewer is always enabled."), + localize('accessibleDiffView.off', "The accessible diff viewer is never enabled."), + ], } } }); From 7582df69014b762ee67c26dfa0a9935b9fe52849 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 11:12:45 +0100 Subject: [PATCH 0508/1863] Dictated text goes off screen when using voice commands. (fix #205783) (#205838) --- .../contrib/codeEditor/browser/dictation/editorDictation.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index b4ed9df731d13..26d4b486350d4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -199,10 +199,11 @@ export class EditorDictation extends Disposable implements IEditorContribution { previewStart = assertIsDefined(this.editor.getPosition()); } + const endPosition = new Position(previewStart.lineNumber, previewStart.column + text.length); this.editor.executeEdits(EditorDictation.ID, [ EditOperation.replace(Range.fromPositions(previewStart, previewStart.with(undefined, previewStart.column + lastReplaceTextLength)), text) ], [ - Selection.fromPositions(new Position(previewStart.lineNumber, previewStart.column + text.length)) + Selection.fromPositions(endPosition) ]); if (isPreview) { @@ -225,6 +226,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { lastReplaceTextLength = 0; } + this.editor.revealPositionInCenterIfOutsideViewport(endPosition); this.widget.layout(); }; From f1e39630da255e29af3d71131f0801b4bf3b6e43 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 21 Feb 2024 11:13:04 +0100 Subject: [PATCH 0509/1863] fixing bug --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0b8bc138bf31e..0be9f26bde91a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -472,15 +472,17 @@ export class InlineCompletionsModel extends Disposable { export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); + const primaryEditStartPosition = primaryEdit.range.getStartPosition(); const primaryEditEndPosition = primaryEdit.range.getEndPosition(); const replacedTextAfterPrimaryCursor = textModel.getValueInRange( Range.fromPositions(primaryPosition, primaryEditEndPosition) ); - const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEdit.range.getStartPosition()); + const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { + const posEnd = addPositions(subtractPositions(pos, primaryEditStartPosition), primaryEditEndPosition); const textAfterSecondaryCursor = textModel.getValueInRange( - Range.fromPositions(pos, primaryEditEndPosition) + Range.fromPositions(pos, posEnd) ); const l = commonPrefixLength(replacedTextAfterPrimaryCursor, textAfterSecondaryCursor); const range = Range.fromPositions(pos, pos.delta(0, l)); From ef64ed413f4e16c4d1bdff4d9a2b03dae4acf1f2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 11:45:40 +0100 Subject: [PATCH 0510/1863] Aggressive caching with Open Current File as Release Notes (#205846) Fixes #205675 --- .../update/browser/releaseNotesEditor.ts | 18 +++++++++++++----- .../workbench/contrib/update/browser/update.ts | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index a7303e1f8c95a..b5e2de0ed521b 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -45,7 +45,6 @@ export class ReleaseNotesManager { private readonly disposables = new DisposableStore(); public constructor( - private readonly _useCurrentFile: boolean, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageService private readonly _languageService: ILanguageService, @@ -81,8 +80,8 @@ export class ReleaseNotesManager { } } - public async show(version: string): Promise { - const releaseNoteText = await this.loadReleaseNotes(version); + public async show(version: string, useCurrentFile: boolean): Promise { + const releaseNoteText = await this.loadReleaseNotes(version, useCurrentFile); this._lastText = releaseNoteText; const html = await this.renderBody(releaseNoteText); const title = nls.localize('releaseNotesInputName', "Release Notes: {0}", version); @@ -137,7 +136,7 @@ export class ReleaseNotesManager { return true; } - private async loadReleaseNotes(version: string): Promise { + private async loadReleaseNotes(version: string, useCurrentFile: boolean): Promise { const match = /^(\d+\.\d+)\./.exec(version); if (!match) { throw new Error('not found'); @@ -199,7 +198,12 @@ export class ReleaseNotesManager { const fetchReleaseNotes = async () => { let text; try { - text = this._useCurrentFile ? this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue() : await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + if (useCurrentFile) { + const file = this._codeEditorService.getActiveCodeEditor()?.getModel()?.getValue(); + text = file ? file.substring(file.indexOf('#')) : undefined; + } else { + text = await asTextOrError(await this._requestService.request({ url }, CancellationToken.None)); + } } catch { throw new Error('Failed to fetch release notes'); } @@ -211,6 +215,10 @@ export class ReleaseNotesManager { return patchKeybindings(text); }; + // Don't cache the current file + if (useCurrentFile) { + return fetchReleaseNotes(); + } if (!this._releaseNotesCache.has(version)) { this._releaseNotesCache.set(version, (async () => { try { diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 5d811ef439970..969c563d5dec0 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -40,10 +40,10 @@ let releaseNotesManager: ReleaseNotesManager | undefined = undefined; export function showReleaseNotesInEditor(instantiationService: IInstantiationService, version: string, useCurrentFile: boolean) { if (!releaseNotesManager) { - releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager, useCurrentFile); + releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager); } - return releaseNotesManager.show(version); + return releaseNotesManager.show(version, useCurrentFile); } async function openLatestReleaseNotesInBrowser(accessor: ServicesAccessor) { From e8aed244b9f142aa8015c4a82fec01a4fb55453e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 11:29:08 +0100 Subject: [PATCH 0511/1863] Improves softAssert function --- src/vs/base/common/assert.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index 5c1bff2799031..4ded48fb1de86 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -35,9 +35,12 @@ export function assert(condition: boolean): void { } } +/** + * Like assert, but doesn't throw. + */ export function softAssert(condition: boolean): void { if (!condition) { - onUnexpectedError(new BugIndicatingError('Assertion Failed')); + onUnexpectedError(new BugIndicatingError('Soft Assertion Failed')); } } From 076e9df452fa0ee71d4fb1074896f74a0266ecd7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 11:56:08 +0100 Subject: [PATCH 0512/1863] Something should happen when the flashing microphone is clicked (fix #205670) (#205842) * Something should happen when the flashing microphone is clicked (fix #205670) * switch to action bar * . --- .../browser/dictation/editorDictation.css | 7 --- .../browser/dictation/editorDictation.ts | 43 ++++++++++++------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css index 321f2b0a27130..0a8d982123f12 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.css @@ -16,13 +16,6 @@ max-width: var(--vscode-editor-dictation-widget-width); } -.monaco-editor .editor-dictation-widget .codicon.codicon-mic-filled { - display: flex; - align-items: center; - width: 16px; - height: 16px; -} - .monaco-editor .editor-dictation-widget.recording .codicon.codicon-mic-filled { color: var(--vscode-activityBarBadge-background); animation: editor-dictation-animation 1s infinite; diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 26d4b486350d4..67863783054b7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -4,15 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./editorDictation'; -import { localize2 } from 'vs/nls'; -import { IDimension, h, reset } from 'vs/base/browser/dom'; +import { localize, localize2 } from 'vs/nls'; +import { IDimension } from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Codicon } from 'vs/base/common/codicons'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -27,6 +26,9 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { assertIsDefined } from 'vs/base/common/types'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { toAction } from 'vs/base/common/actions'; +import { ThemeIcon } from 'vs/base/common/themables'; const EDITOR_DICTATION_IN_PROGRESS = new RawContextKey('editorDictation.inProgress', false); const VOICE_CATEGORY = localize2('voiceCategory', "Voice"); @@ -69,9 +71,11 @@ export class EditorDictationStartAction extends EditorAction2 { export class EditorDictationStopAction extends EditorAction2 { + static readonly ID = 'workbench.action.editorDictation.stop'; + constructor() { super({ - id: 'workbench.action.editorDictation.stop', + id: EditorDictationStopAction.ID, title: localize2('stopDictation', "Stop Dictation in Editor"), category: VOICE_CATEGORY, precondition: EDITOR_DICTATION_IN_PROGRESS, @@ -94,15 +98,21 @@ export class DictationWidget extends Disposable implements IContentWidget { readonly allowEditorOverflow = true; private readonly domNode = document.createElement('div'); - private readonly elements = h('.editor-dictation-widget@main', [h('span@mic')]); - constructor(private readonly editor: ICodeEditor) { + constructor(private readonly editor: ICodeEditor, keybindingService: IKeybindingService) { super(); - this.domNode.appendChild(this.elements.root); - this.domNode.style.zIndex = '1000'; - - reset(this.elements.mic, renderIcon(Codicon.micFilled)); + const actionBar = this._register(new ActionBar(this.domNode)); + const stopActionKeybinding = keybindingService.lookupKeybinding(EditorDictationStopAction.ID)?.getLabel(); + actionBar.push(toAction({ + id: EditorDictationStopAction.ID, + label: stopActionKeybinding ? localize('stopDictationShort1', "Stop Dictation ({0})", stopActionKeybinding) : localize('stopDictationShort2', "Stop Dictation"), + class: ThemeIcon.asClassName(Codicon.micFilled), + run: () => EditorDictation.get(editor)?.stop() + }), { icon: true, label: false, keybinding: stopActionKeybinding }); + + this.domNode.classList.add('editor-dictation-widget'); + this.domNode.appendChild(actionBar.domNode); } getId(): string { @@ -133,8 +143,8 @@ export class DictationWidget extends Disposable implements IContentWidget { const lineHeight = this.editor.getOption(EditorOption.lineHeight); const width = this.editor.getLayoutInfo().contentWidth * 0.7; - this.elements.main.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); - this.elements.main.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); + this.domNode.style.setProperty('--vscode-editor-dictation-widget-height', `${lineHeight}px`); + this.domNode.style.setProperty('--vscode-editor-dictation-widget-width', `${width}px`); return null; } @@ -148,11 +158,11 @@ export class DictationWidget extends Disposable implements IContentWidget { } active(): void { - this.elements.main.classList.add('recording'); + this.domNode.classList.add('recording'); } hide() { - this.elements.main.classList.remove('recording'); + this.domNode.classList.remove('recording'); this.editor.removeContentWidget(this); } } @@ -165,7 +175,7 @@ export class EditorDictation extends Disposable implements IEditorContribution { return editor.getContribution(EditorDictation.ID); } - private readonly widget = this._register(new DictationWidget(this.editor)); + private readonly widget = this._register(new DictationWidget(this.editor, this.keybindingService)); private readonly editorDictationInProgress = EDITOR_DICTATION_IN_PROGRESS.bindTo(this.contextKeyService); private sessionDisposables = this._register(new MutableDisposable()); @@ -173,7 +183,8 @@ export class EditorDictation extends Disposable implements IEditorContribution { constructor( private readonly editor: ICodeEditor, @ISpeechService private readonly speechService: ISpeechService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(); } From a7ac7083e51d15475112c61f270e8c60f78e0096 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 12:07:11 +0100 Subject: [PATCH 0513/1863] make sure LM provider error bubble all the way, reject response streams and result promise (#205849) fixes https://github.com/microsoft/vscode/issues/205722 --- .../api/browser/mainThreadChatProvider.ts | 1 + .../api/common/extHostChatProvider.ts | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index a3637ad0000f6..dd76d12f105c6 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -104,6 +104,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { task.catch(err => { this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); + throw err; }).finally(() => { this._logService.debug('[CHAT] extension request DONE', extension.value, requestId); }); diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostChatProvider.ts index 3f35da68ace23..8ad8318f9e182 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostChatProvider.ts @@ -17,6 +17,7 @@ import { Emitter, Event } 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'; type LanguageModelData = { readonly extension: ExtensionIdentifier; @@ -54,18 +55,32 @@ class LanguageModelRequest { // responses: AsyncIterable[] // FUTURE responses per N }; - promise.finally(() => { - this._isDone = true; - if (this._responseStreams.size > 0) { - for (const [, value] of this._responseStreams) { - value.stream.resolve(); - } - } else { - this._defaultStream.resolve(); + 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() { + if (this._responseStreams.size > 0) { + for (const [, value] of this._responseStreams) { + yield value.stream; + } + } else { + yield this._defaultStream; + } + } + handleFragment(fragment: IChatResponseFragment): void { if (this._isDone) { return; From 414f2cd189e6bf7b418d5e1d9c117826520470ed Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:20:59 +0100 Subject: [PATCH 0514/1863] Git - fix reopen closed repositories action visibility issue (#205851) --- extensions/git/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f1b45e74665c8..f000da7f3f228 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1462,7 +1462,7 @@ { "command": "git.reopenClosedRepositories", "group": "navigation@1", - "when": "scmProvider == git && git.closedRepositoryCount > 0" + "when": "git.closedRepositoryCount > 0" } ], "scm/sourceControl": [ From 6d57f57ec8ac105fb51e271de61a905de3d7ee6a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 12:22:53 +0100 Subject: [PATCH 0515/1863] Fix issue with settings not being respected in release notes (#205852) Settings with values separated are not respected in release notes Fixes #205678 Also fixes #205742 --- .../browser/markdownSettingRenderer.ts | 19 ++++++++++++++++--- .../browser/markdownSettingRenderer.test.ts | 9 +++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 5193d73e16406..9fa69f9f0a4b7 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -14,9 +14,10 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -const codeSettingRegex = /^/; -const codeFeatureRegex = /^/; +const codeSettingRegex = /^/; +const codeFeatureRegex = /^/; export class SimpleSettingRenderer { private _defaultSettings: DefaultSettings; @@ -28,7 +29,8 @@ export class SimpleSettingRenderer { @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IPreferencesService private readonly _preferencesService: IPreferencesService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IClipboardService private readonly _clipboardService: IClipboardService ) { this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); } @@ -258,6 +260,17 @@ export class SimpleSettingRenderer { } }); + actions.push({ + class: undefined, + enabled: true, + id: 'copySettingId', + tooltip: nls.localize('copySettingId', "Copy Setting ID"), + label: nls.localize('copySettingId', "Copy Setting ID"), + run: () => { + this._clipboardService.writeText(settingId); + } + }); + return actions; } diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index fb609bd79b75d..834ff1b0a4094 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -61,7 +61,7 @@ suite('Markdown Setting Renderer Test', () => { preferencesService = {}; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); - settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any); + settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any, { writeText: async () => { } } as any); }); suiteTeardown(() => { @@ -82,7 +82,7 @@ suite('Markdown Setting Renderer Test', () => { test('actions with no value', () => { const uri = URI.parse(settingRenderer.settingToUriString('example.booleanSetting')); const actions = settingRenderer.getActions(uri); - assert.strictEqual(actions?.length, 1); + assert.strictEqual(actions?.length, 2); assert.strictEqual(actions[0].label, 'View "Example: Boolean Setting" in Settings'); }); @@ -91,7 +91,7 @@ suite('Markdown Setting Renderer Test', () => { const uri = URI.parse(settingRenderer.settingToUriString('example.stringSetting', 'three')); const verifyOriginalState = (actions: IAction[] | undefined): actions is IAction[] => { - assert.strictEqual(actions?.length, 2); + assert.strictEqual(actions?.length, 3); assert.strictEqual(actions[0].label, 'Set "Example: String Setting" to "three"'); assert.strictEqual(actions[1].label, 'View in Settings'); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'two'); @@ -104,9 +104,10 @@ suite('Markdown Setting Renderer Test', () => { await actions[0].run(); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); const actionsUpdated = settingRenderer.getActions(uri); - assert.strictEqual(actionsUpdated?.length, 2); + assert.strictEqual(actionsUpdated?.length, 3); assert.strictEqual(actionsUpdated[0].label, 'Restore value of "Example: String Setting"'); assert.strictEqual(actions[1].label, 'View in Settings'); + assert.strictEqual(actions[2].label, 'Copy Setting ID'); assert.strictEqual(configurationService.getValue('example.stringSetting'), 'three'); // Restore the value From 7b0593941809093055d3a69299ed127c557d4f92 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 21 Feb 2024 12:25:06 +0100 Subject: [PATCH 0516/1863] "Try Feature" tooltip sounds awkward (#205841) * "Try Feature" tooltip sounds awkward Fixes #205741 * Fix test --- .../contrib/markdown/browser/markdownSettingRenderer.ts | 2 +- .../markdown/test/browser/markdownSettingRenderer.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 9fa69f9f0a4b7..c4cf0aa94fed3 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -169,7 +169,7 @@ export class SimpleSettingRenderer { private renderSetting(setting: ISetting, newValue: string | undefined): string | undefined { const href = this.settingToUriString(setting.key, newValue); - const title = nls.localize('changeSettingTitle', "Try feature"); + const title = nls.localize('changeSettingTitle', "View or change setting"); return ` ${setting.key} diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index 834ff1b0a4094..55e38f3d018d4 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -73,7 +73,7 @@ suite('Markdown Setting Renderer Test', () => { const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ` + ` example.booleanSetting `); From 67f91582f588d1d37a94ce5b18e01ba535184967 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 12:36:00 +0100 Subject: [PATCH 0517/1863] actionbar - `animated` is an obsolete property (#205854) --- src/vs/base/browser/ui/actionbar/actionbar.ts | 5 ----- src/vs/base/browser/ui/menu/menu.ts | 4 ---- src/vs/workbench/browser/parts/compositeBar.ts | 1 - src/vs/workbench/browser/parts/globalCompositeBar.ts | 1 - .../extensions/browser/abstractRuntimeExtensionsEditor.ts | 3 +-- .../workbench/contrib/extensions/browser/extensionEditor.ts | 3 +-- .../workbench/contrib/extensions/browser/extensionsList.ts | 1 - src/vs/workbench/contrib/markers/browser/markersTable.ts | 3 +-- .../contrib/preferences/browser/keybindingsEditor.ts | 2 +- .../contrib/preferences/browser/preferencesWidgets.ts | 1 - .../workbench/contrib/preferences/browser/settingsEditor2.ts | 1 - .../contrib/workspace/browser/workspaceTrustEditor.ts | 2 +- 12 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 9ba16500c8ff0..5f5ad35284224 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -47,7 +47,6 @@ export interface IActionBarOptions { readonly actionRunner?: IActionRunner; readonly ariaLabel?: string; readonly ariaRole?: string; - readonly animated?: boolean; readonly triggerKeys?: ActionTrigger; readonly allowContextMenu?: boolean; readonly preventLoopNavigation?: boolean; @@ -137,10 +136,6 @@ export class ActionBar extends Disposable implements IActionRunner { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-action-bar'; - if (options.animated !== false) { - this.domNode.classList.add('animated'); - } - let previousKeys: KeyCode[]; let nextKeys: KeyCode[]; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 35343e4f878de..4429e37687d7e 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -1056,10 +1056,6 @@ ${formatRule(Codicon.menuSubmenu)} cursor: default; } -.monaco-menu .monaco-action-bar.animated .action-item.active { - transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ -} - .monaco-menu .monaco-action-bar .action-item .icon, .monaco-menu .monaco-action-bar .action-item .codicon { display: inline-block; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index a2007e1829909..76d6c0c1ee4ce 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -221,7 +221,6 @@ export class CompositeBar extends Widget implements ICompositeBar { orientation: this.options.orientation, ariaLabel: localize('activityBarAriaLabel', "Active View Switcher"), ariaRole: 'tablist', - animated: false, preventLoopNavigation: this.options.preventLoopNavigation, triggerKeys: { keyDown: true } })); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 550866d685b00..db3471fcadc44 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -97,7 +97,6 @@ export class GlobalCompositeBar extends Disposable { }, orientation: ActionsOrientation.VERTICAL, ariaLabel: localize('manage', "Manage"), - animated: false, preventLoopNavigation: true })); diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 413da693a6a74..991a3df035cb2 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -241,10 +241,9 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { const msgContainer = append(desc, $('div.msg')); - const actionbar = new ActionBar(desc, { animated: false }); + const actionbar = new ActionBar(desc); actionbar.onDidRun(({ error }) => error && this._notificationService.error(error)); - const timeContainer = append(element, $('.time')); const activationTime = append(timeContainer, $('div.activation-time')); const profileTime = append(timeContainer, $('div.profile-time')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 658f429cff140..21986a4b70938 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -99,7 +99,7 @@ class NavBar extends Disposable { super(); const element = append(container, $('.navbar')); this.actions = []; - this.actionbar = this._register(new ActionBar(element, { animated: false })); + this.actionbar = this._register(new ActionBar(element)); } push(id: string, label: string, tooltip: string): void { @@ -336,7 +336,6 @@ export class ExtensionEditor extends EditorPane { const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { - animated: false, actionViewItemProvider: (action: IAction) => { if (action instanceof ExtensionDropDownAction) { return action.createActionViewItem(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 0f3b1b04d08e1..1bf769f4c668a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -98,7 +98,6 @@ export class Renderer implements IPagedRenderer { const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $(`.verified-publisher`)), true); const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis')); const actionbar = new ActionBar(footer, { - animated: false, actionViewItemProvider: (action: IAction) => { if (action instanceof ActionWithDropDownAction) { return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index d466b58a6b2d6..dc454d054ed89 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -74,8 +74,7 @@ class MarkerSeverityColumnRenderer implements ITableRenderer action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined, - animated: false + actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined }); return { actionBar, icon }; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 0b0c257a0e523..b0026da674cf8 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -851,7 +851,7 @@ class ActionsColumnRenderer implements ITableRenderer action.id === 'folderSettings' ? this.folderSettings : undefined })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index a06c804835153..c0632e494bcf2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -655,7 +655,6 @@ export class SettingsEditor2 extends EditorPane { this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { - animated: false, actionViewItemProvider: (action) => { if (action.id === filterAction.id) { return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, this.actionRunner, this.searchWidget); diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 578fe2777d757..fe7f625a78494 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -406,7 +406,7 @@ class TrustedUriActionsColumnRenderer implements ITableRenderer Date: Wed, 21 Feb 2024 12:44:54 +0100 Subject: [PATCH 0518/1863] Removed Copilot related code from TypeScript Refactor Provider --- .../src/languageFeatures/refactor.ts | 46 ++----------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index bbc5aef39b7cf..a61758cc66dbc 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -21,7 +21,7 @@ import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService import { coalesce } from '../utils/arrays'; import { nulToken } from '../utils/cancellation'; import FormattingOptionsManager from './fileConfigurationManager'; -import { CompositeCommand, EditorChatFollowUp, EditorChatFollowUp_Args } from './util/copilot'; +import { CompositeCommand, EditorChatFollowUp } from './util/copilot'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto.FileCodeEdits[]): vscode.WorkspaceEdit { @@ -347,14 +347,10 @@ class InlinedCodeAction extends vscode.CodeAction { public readonly refactor: Proto.ApplicableRefactorInfo, public readonly action: Proto.RefactorActionInfo, public readonly range: vscode.Range, - public readonly copilotRename?: (info: Proto.RefactorEditInfo) => vscode.Command, ) { - const title = copilotRename ? action.description + ' and suggest a name with Copilot.' : action.description; + const title = action.description; super(title, InlinedCodeAction.getKind(action)); - if (copilotRename) { - this.isAI = true; - } if (action.notApplicableReason) { this.disabled = { reason: action.notApplicableReason }; } @@ -392,15 +388,12 @@ class InlinedCodeAction extends vscode.CodeAction { if (response.body.renameLocation) { // Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137 if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) { - if (this.copilotRename && this.command) { - this.command.title = 'Copilot: ' + this.command.title; - } this.command = { command: CompositeCommand.ID, title: '', arguments: coalesce([ this.command, - this.copilotRename ? this.copilotRename(response.body) : { + { command: 'editor.action.rename', arguments: [[ this.document.uri, @@ -635,38 +628,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider vscode.Command) = info => ({ - title: '', - command: EditorChatFollowUp.ID, - arguments: [{ - message: `Rename ${newName} to a better name based on usage.`, - expand: Extract_Constant.matches(action) ? { - kind: 'navtree-function', - pos: typeConverters.Position.fromLocation(info.renameLocation!), - } : { - kind: 'refactor-info', - refactor: info, - }, - action: { type: 'refactor', refactor: action }, - document, - } satisfies EditorChatFollowUp_Args] - }); - codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename)); - } - } + codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection)); } for (const codeAction of codeActions) { codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions); From be34739ee85723404c11063dcca59377b6c358e8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 12:53:06 +0100 Subject: [PATCH 0519/1863] editors - use `canSerialize` properly (#205856) --- .../workbench/common/editor/editorGroupModel.ts | 2 +- .../contrib/chat/browser/chatEditorInput.ts | 10 +++------- .../browser/interactive.contribution.ts | 17 +++++++++++------ .../browser/multiDiffEditorInput.ts | 10 ++++++---- .../browser/searchEditor.contribution.ts | 4 ++++ .../browser/terminalEditorSerializer.ts | 9 ++++----- .../browser/webviewEditorInputSerializer.ts | 2 +- .../common/untitledTextEditorHandler.ts | 2 +- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 497ef8daf46e9..8a0182342560f 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -1033,7 +1033,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const editorSerializer = registry.getEditorSerializer(editor); if (editorSerializer) { - const value = editorSerializer.serialize(editor); + const value = editorSerializer.canSerialize(editor) ? editorSerializer.serialize(editor) : undefined; // Editor can be serialized if (typeof value === 'string') { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index f59f81e2ffb16..e6cc6c81ff000 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -183,16 +183,12 @@ interface ISerializedChatEditorInput { } export class ChatEditorInputSerializer implements IEditorSerializer { - canSerialize(input: EditorInput): boolean { - return input instanceof ChatEditorInput; + canSerialize(input: EditorInput): input is ChatEditorInput & { readonly sessionId: string } { + return input instanceof ChatEditorInput && typeof input.sessionId === 'string'; } serialize(input: EditorInput): string | undefined { - if (!(input instanceof ChatEditorInput)) { - return undefined; - } - - if (typeof input.sessionId !== 'string') { + if (!this.canSerialize(input)) { return undefined; } diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 3ba9491111ec4..05d7d253673f3 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -10,7 +10,6 @@ import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { extname, isEqual } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { assertType } from 'vs/base/common/types'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -263,13 +262,19 @@ type interactiveEditorInputData = { resource: URI; inputResource: URI; name: str export class InteractiveEditorSerializer implements IEditorSerializer { public static readonly ID = InteractiveEditorInput.ID; - canSerialize(editor: EditorInput): boolean { - const interactiveEditorInput = editor as InteractiveEditorInput; - return URI.isUri(interactiveEditorInput?.primary?.resource) && URI.isUri(interactiveEditorInput?.inputResource); + canSerialize(editor: EditorInput): editor is InteractiveEditorInput { + if (!(editor instanceof InteractiveEditorInput)) { + return false; + } + + return URI.isUri(editor.primary.resource) && URI.isUri(editor.inputResource); } - serialize(input: EditorInput): string { - assertType(input instanceof InteractiveEditorInput); + serialize(input: EditorInput): string | undefined { + if (!this.canSerialize(input)) { + return undefined; + } + return JSON.stringify({ resource: input.primary.resource, inputResource: input.inputResource, diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 3841a5d56aa62..058373fd46540 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -364,14 +364,16 @@ interface ISerializedMultiDiffEditorInput { export class MultiDiffEditorSerializer implements IEditorSerializer { - // TODO@bpasero, @aiday-mar: following canSerialize should be removed (debt item) - canSerialize(editor: EditorInput): boolean { + canSerialize(editor: EditorInput): editor is MultiDiffEditorInput { return editor instanceof MultiDiffEditorInput && !editor.isTransient; } serialize(editor: MultiDiffEditorInput): string | undefined { - const shouldSerialize = editor instanceof MultiDiffEditorInput && !editor.isTransient; - return shouldSerialize ? JSON.stringify(editor.serialize()) : undefined; + if (!this.canSerialize(editor)) { + return undefined; + } + + return JSON.stringify(editor.serialize()); } deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 020ae109fd0e9..be7cad0444498 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -113,6 +113,10 @@ class SearchEditorInputSerializer implements IEditorSerializer { } serialize(input: SearchEditorInput) { + if (!this.canSerialize(input)) { + return undefined; + } + if (input.isDisposed()) { return JSON.stringify({ modelUri: undefined, dirty: false, config: input.tryReadConfigSync(), name: input.getName(), matchRanges: [], backingUri: input.backingUri?.toString() } as SerializedSearchEditor); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts index 5ad20c381c0f3..72200823f2ed3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts @@ -14,16 +14,15 @@ export class TerminalInputSerializer implements IEditorSerializer { @ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService ) { } - public canSerialize(editorInput: TerminalEditorInput): boolean { - return !!editorInput.terminalInstance?.persistentProcessId; + public canSerialize(editorInput: TerminalEditorInput): editorInput is TerminalEditorInput & { readonly terminalInstance: ITerminalInstance } { + return typeof editorInput.terminalInstance?.persistentProcessId === 'number' && editorInput.terminalInstance.shouldPersist; } public serialize(editorInput: TerminalEditorInput): string | undefined { - if (!editorInput.terminalInstance?.persistentProcessId || !editorInput.terminalInstance.shouldPersist) { + if (!this.canSerialize(editorInput)) { return; } - const term = JSON.stringify(this._toJson(editorInput.terminalInstance)); - return term; + return JSON.stringify(this._toJson(editorInput.terminalInstance)); } public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index ade4299afe4d0..8a590bff082fc 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -58,7 +58,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { } public serialize(input: WebviewInput): string | undefined { - if (!this._webviewWorkbenchService.shouldPersist(input)) { + if (!this.canSerialize(input)) { return undefined; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index f786bd25f0424..b9357adb2c105 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -40,7 +40,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { } serialize(editorInput: EditorInput): string | undefined { - if (!this.filesConfigurationService.isHotExitEnabled || editorInput.isDisposed()) { + if (!this.canSerialize(editorInput)) { return undefined; } From 19781f2c52ef5e6b721ce64f436f45126b1e11d1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 12:41:19 +0100 Subject: [PATCH 0520/1863] Sets focus border for focused editors --- .../multiDiffEditorWidget/diffEditorItemTemplate.ts | 4 +++- .../browser/widget/multiDiffEditorWidget/style.css | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 8c44f6e41819e..0f3353c66bc14 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -230,7 +230,8 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.root.style.position = 'absolute'; // For sticky scroll - const delta = Math.max(0, Math.min(verticalRange.length - this._headerHeight, viewPort.start - verticalRange.start)); + const maxDelta = verticalRange.length - this._headerHeight; + const delta = Math.max(0, Math.min(viewPort.start - verticalRange.start, maxDelta)); this._elements.header.style.transform = `translateY(${delta}px)`; globalTransaction(tx => { @@ -242,6 +243,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this.editor.getOriginalEditor().setScrollTop(editorScroll); this._elements.header.classList.toggle('shadow', delta > 0 || editorScroll > 0); + this._elements.header.classList.toggle('collapsed', delta === maxDelta); } public hide(): void { diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css index ca41df09a0406..44f0703d580df 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -7,6 +7,10 @@ background: var(--vscode-multiDiffEditor-background); overflow-y: hidden; + .focused { + --vscode-multiDiffEditor-border: var(--vscode-focusBorder); + } + .multiDiffEntry { display: flex; flex-direction: column; @@ -27,6 +31,10 @@ z-index: 1000; background: var(--vscode-editor-background); + &:not(.collapsed) .header-content { + border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); + } + .header-content { margin: 8px 8px 0px 8px; padding: 8px 5px; @@ -43,8 +51,6 @@ color: var(--vscode-foreground); background: var(--vscode-multiDiffEditor-headerBackground); - border-bottom: 1px solid var(--vscode-sideBarSectionHeader-border); - &.shadow { box-shadow: var(--vscode-scrollbar-shadow) 0px 6px 6px -6px; } From 2e99a31949532c4537b979c5ff6b794284f8ecae Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 21 Feb 2024 13:42:01 +0100 Subject: [PATCH 0521/1863] comparing bulk file operations --- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 5 +++-- .../contrib/bulkEdit/browser/preview/bulkEditTree.ts | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9728bfc1e2dfe..21cbba6bc42b2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -5,7 +5,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter, compareBulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -361,8 +361,9 @@ export class BulkEditPane extends ViewPane { if (this._fileOperations === fileOperations && this._resources) { return this._resources; } + const sortedFileOperations = fileOperations.sort(compareBulkFileOperations); const resources: IResourceDiffEditorInput[] = []; - for (const operation of fileOperations) { + for (const operation of sortedFileOperations) { const operationUri = operation.uri; const previewUri = this._currentProvider!.asPreviewUri(operationUri); // delete -> show single editor diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index 5dda15aee3e8c..c5fd28d6a8e75 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -286,7 +286,7 @@ export class BulkEditSorter implements ITreeSorter { compare(a: BulkEditElement, b: BulkEditElement): number { if (a instanceof FileElement && b instanceof FileElement) { - return compare(a.edit.uri.toString(), b.edit.uri.toString()); + return compareBulkFileOperations(a.edit, b.edit); } if (a instanceof TextEditElement && b instanceof TextEditElement) { @@ -297,6 +297,10 @@ export class BulkEditSorter implements ITreeSorter { } } +export function compareBulkFileOperations(a: BulkFileOperation, b: BulkFileOperation): number { + return compare(a.uri.toString(), b.uri.toString()); +} + // --- ACCESSI export class BulkEditAccessibilityProvider implements IListAccessibilityProvider { From a3b932f0d68dc2bd905233f31c62ab95b0cdf11d Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 21 Feb 2024 15:02:02 +0100 Subject: [PATCH 0522/1863] Fixes #205850 (#205862) * Sets focus border for focused editors * Fixes #205850 * Fixes CI and don't use focus --- .../multiDiffEditorWidget.ts | 9 +++- .../multiDiffEditorWidgetImpl.ts | 45 ++++++++++++------- .../bulkEdit/browser/preview/bulkEditPane.ts | 8 +++- .../browser/multiDiffEditor.ts | 12 +++-- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts index 5a869f83bd2f7..6867bb84ac7cc 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -46,8 +46,8 @@ export class MultiDiffEditorWidget extends Disposable { this._register(recomputeInitiallyAndOnChange(this._widgetImpl)); } - public reveal(resource: IMultiDiffResource, range: Range): void { - this._widgetImpl.get().reveal(resource, range); + public reveal(resource: IMultiDiffResource, options?: RevealOptions): void { + this._widgetImpl.get().reveal(resource, options); } public createViewModel(model: IMultiDiffEditorModel): MultiDiffEditorViewModel { @@ -82,3 +82,8 @@ export class MultiDiffEditorWidget extends Disposable { return this._widgetImpl.get().tryGetCodeEditor(resource); } } + +export interface RevealOptions { + range?: Range; + highlight: boolean; +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index ca4e240c1dace..a913cdad35054 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -10,24 +10,24 @@ import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorun, autorunWithStore, derived, derivedObservableWithCache, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { ITransaction, disposableObservableValue, globalTransaction, transaction } from 'vs/base/common/observableInternal/base'; import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { URI } from 'vs/base/common/uri'; import 'vs/css!./style'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; +import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IRange } from 'vs/editor/common/core/range'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; +import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; import { DocumentDiffItemViewModel, MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { ObjectPool } from './objectPool'; -import { ContextKeyValue, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { URI } from 'vs/base/common/uri'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _elements = h('div.monaco-component.multiDiffEditor', [ @@ -107,7 +107,6 @@ export class MultiDiffEditorWidgetImpl extends Disposable { private readonly _workbenchUIElementFactory: IWorkbenchUIElementFactory, @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, - @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -190,8 +189,7 @@ export class MultiDiffEditorWidgetImpl extends Disposable { this._scrollableElement.setScrollPosition({ scrollLeft: scrollState.left, scrollTop: scrollState.top }); } - // todo@aiday-mar need to reveal the range instead of just the start line number - public reveal(resource: IMultiDiffResource, range: Range): void { + public reveal(resource: IMultiDiffResource, options?: RevealOptions): void { const viewItems = this._viewItems.get(); let searchCallback: (item: VirtualizedViewItem) => boolean; if ('original' in resource) { @@ -200,11 +198,18 @@ export class MultiDiffEditorWidgetImpl extends Disposable { searchCallback = (item) => item.viewModel.modifiedUri?.toString() === resource.modified.toString(); } const index = viewItems.findIndex(searchCallback); - let scrollTop = (range.startLineNumber - 1) * this._configurationService.getValue('editor.lineHeight'); + let scrollTop = 0; for (let i = 0; i < index; i++) { scrollTop += viewItems[i].contentHeight.get() + this._spaceBetweenPx; } this._scrollableElement.setScrollPosition({ scrollTop }); + + const diffEditor = viewItems[index].template.get()?.editor; + const editor = 'original' in resource ? diffEditor?.getOriginalEditor() : diffEditor?.getModifiedEditor(); + if (editor && options?.range) { + editor.revealRangeInCenter(options.range); + highlightRange(editor, options.range); + } } public getViewState(): IMultiDiffEditorViewState { @@ -290,6 +295,16 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } } +function highlightRange(targetEditor: ICodeEditor, range: IRange) { + const modelNow = targetEditor.getModel(); + const decorations = targetEditor.createDecorationsCollection([{ range, options: { description: 'symbol-navigate-action-highlight', className: 'symbolHighlight' } }]); + setTimeout(() => { + if (targetEditor.getModel() === modelNow) { + decorations.clear(); + } + }, 350); +} + export interface IMultiDiffEditorViewState { scrollState: { top: number; left: number }; docStates?: Record; @@ -307,7 +322,7 @@ export interface IMultiDiffEditorOptions extends ITextEditorOptions { export interface IMultiDiffEditorOptionsViewState { revealData?: { resource: IMultiDiffResource; - range: Range; + range?: IRange; }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 9728bfc1e2dfe..07dc0d160fca3 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -37,8 +37,8 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; -import { Range } from 'vs/editor/common/core/range'; import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IRange } from 'vs/editor/common/core/range'; const enum State { Data = 'data', @@ -325,11 +325,15 @@ export class BulkEditPane extends ViewPane { if (!fileOperations) { return; } + + let selection: IRange | undefined = undefined; let fileElement: FileElement; if (e.element instanceof TextEditElement) { fileElement = e.element.parent; + selection = e.element.edit.textEdit.textEdit.range; } else if (e.element instanceof FileElement) { fileElement = e.element; + selection = e.element.edit.textEdits[0]?.textEdit.textEdit.range; } else { // invalid event return; @@ -341,7 +345,7 @@ export class BulkEditPane extends ViewPane { viewState: { revealData: { resource: { original: fileElement.edit.uri }, - range: new Range(1, 1, 1, 1) + range: selection, } } }; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index bb8bdc7c16e53..c1491431828f7 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -26,6 +26,7 @@ import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEdit import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; export class MultiDiffEditor extends AbstractEditorWithViewState { static readonly ID = 'multiDiffEditor'; @@ -80,19 +81,22 @@ export class MultiDiffEditor extends AbstractEditorWithViewState { From 44ffd1a464fb3b976bc110ec768b8ec5fbcb7752 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 06:21:13 -0800 Subject: [PATCH 0523/1863] fix #205789 (#205868) --- .../workbench/contrib/extensions/browser/media/extension.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extension.css b/src/vs/workbench/contrib/extensions/browser/media/extension.css index c9622b3b43cf4..136ce1af0f75d 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -174,14 +174,16 @@ display: flex; justify-content: flex-end; padding-right: 7px; - height: 18px; + height: 24px; overflow: hidden; + align-items: center; } .extension-list-item > .details > .footer > .author { flex: 1; display: flex; align-items: center; + line-height: 24px; } .extension-list-item > .details > .footer > .author > .publisher-name { From e7a3ef849bcc870fe2546952a83e0428b42ba127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Wed, 21 Feb 2024 23:34:11 +0800 Subject: [PATCH 0524/1863] Add tips for debug views (#205861) * Add tips for debug views * Add tips for debug views --- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/workbench/contrib/debug/browser/welcomeView.ts | 5 +++-- .../workbench/contrib/files/browser/explorerViewlet.ts | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index a5d96361f08d1..0d3a9795ffbb8 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -214,7 +214,7 @@ export interface ErrorReporter { onEmptyValue(id: string): void; onDeprecatedOption(deprecatedId: string, message: string): void; - getSubcommandReporter?(commmand: string): ErrorReporter; + getSubcommandReporter?(command: string): ErrorReporter; } const ignoringReporter = { diff --git a/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/src/vs/workbench/contrib/debug/browser/welcomeView.ts index b0c09a24c5200..703f645f19e17 100644 --- a/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -165,8 +165,9 @@ viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, { { key: 'customizeRunAndDebugOpenFolder', comment: [ - 'Please do not translate the word "commmand", it is part of our internal syntax which must not change', - '{Locked="](command:{0})"}' + 'Please do not translate the word "command", it is part of our internal syntax which must not change', + 'Please do not translate "launch.json", it is the specific configuration file name', + '{Locked="](command:{0})"}', ] }, "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", (isMacintosh && !isWeb) ? OpenFileFolderAction.ID : OpenFolderAction.ID), diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 010e42cb8373c..9785e04a5b126 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -283,7 +283,7 @@ const openRecentButton = `[${openRecent}](command:${OpenRecentAction.ID})`; const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet added a folder to the workspace.\n{0}", addRootFolderButton), when: ContextKeyExpr.and( // inside a .code-workspace @@ -296,7 +296,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderHelpWeb', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}\n{1}", openFolderViaWorkspaceButton, openRecentButton), when: ContextKeyExpr.and( // inside a .code-workspace @@ -309,7 +309,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "Connected to remote.\n{0}", openFolderButton), when: ContextKeyExpr.and( // not inside a .code-workspace @@ -323,7 +323,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderButEditorsHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}\nOpening a folder will close all currently open editors. To keep them open, {1} instead.", openFolderButton, addAFolderButton), when: ContextKeyExpr.and( // editors are opened @@ -340,7 +340,7 @@ viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] }, + content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change'] }, "You have not yet opened a folder.\n{0}", openFolderButton), when: ContextKeyExpr.and( // no editor is open From 451333c31fe3cb1a39a08e63d94c2e8b06c9d26e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 15:56:15 +0000 Subject: [PATCH 0525/1863] Small chat API updates (#205880) * Fix #205812 * Fix #205810 * Fix #205809 --- src/vscode-dts/vscode.proposed.chatParticipant.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 468f12ae69076..43bacd595dd29 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -83,7 +83,7 @@ declare module 'vscode' { message: string; /** - * If partial markdown content was sent over the `progress` callback before the response terminated, then this flag + * If partial markdown content was sent over the {@link ChatRequestHandler handler}'s response stream before the response terminated, then this flag * can be set to true and it will be rendered with incomplete markdown features patched up. * * For example, if the response terminated after sending part of a triple-backtick code block, then the editor will @@ -281,7 +281,7 @@ declare module 'vscode' { followupProvider?: ChatFollowupProvider; /** - * When the user clicks this participant in `/help`, this text will be submitted to this command + * When the user clicks this participant in `/help`, this text will be submitted to this participant. */ sampleRequest?: string; @@ -408,7 +408,7 @@ declare module 'vscode' { * Push a progress part to this stream. Short-hand for * `push(new ChatResponseProgressPart(value))`. * - * @param value + * @param value A progress message * @returns This stream. */ progress(value: string): ChatResponseStream; From 63a3873d110168ac19028f63e1b07f6ac12149df Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:12:34 +0100 Subject: [PATCH 0526/1863] SCM - fix an issue with window title variables (#205883) --- .../workbench/contrib/scm/browser/activity.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index efd752e0f533a..671a46f65a0aa 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -224,8 +224,9 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri private activeRepositoryNameContextKey: IContextKey; private activeRepositoryBranchNameContextKey: IContextKey; + private focusedRepository: ISCMRepository | undefined = undefined; + private focusDisposable: IDisposable = Disposable.None; private readonly disposables = new DisposableStore(); - private readonly focusedRepositoryDisposables = new DisposableStore(); constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -254,28 +255,25 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri return; } - const activeResourceRepository = Iterable.find( - this.scmViewService.visibleRepositories, + const repository = Iterable.find( + this.scmViewService.repositories, r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri)) ); - this.onDidFocusRepository(activeResourceRepository); + this.onDidFocusRepository(repository); } private onDidFocusRepository(repository: ISCMRepository | undefined): void { - this.focusedRepositoryDisposables.clear(); - - if (!repository) { + if (!repository || this.focusedRepository === repository) { return; } - this.focusedRepositoryDisposables.add(repository.provider.onDidChangeHistoryProvider(() => { - if (repository.provider.historyProvider) { - this.focusedRepositoryDisposables.add(repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.updateContextKeys(repository))); - } + this.focusDisposable.dispose(); + this.focusedRepository = repository; - this.updateContextKeys(repository); - })); + if (repository && repository.provider.onDidChangeStatusBarCommands) { + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.updateContextKeys(repository)); + } this.updateContextKeys(repository); } @@ -284,6 +282,11 @@ export class SCMActiveRepositoryContextKeyController implements IWorkbenchContri this.activeRepositoryNameContextKey.set(repository?.provider.name ?? ''); this.activeRepositoryBranchNameContextKey.set(repository?.provider.historyProvider?.currentHistoryItemGroup?.label ?? ''); } + + dispose(): void { + this.focusDisposable.dispose(); + this.disposables.dispose(); + } } export class SCMActiveResourceContextKeyController implements IWorkbenchContribution { From 2ad2a35cadcce552081b81f4ac9373b23939a8e2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 21 Feb 2024 08:53:05 -0800 Subject: [PATCH 0527/1863] Make sure we don't report errors in chat code blocks (#204779) --- .../src/configuration/fileSchemes.ts | 3 +-- .../src/tsServer/bufferSyncSupport.ts | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/configuration/fileSchemes.ts b/extensions/typescript-language-features/src/configuration/fileSchemes.ts index 07dd38a53798c..cfc3f66db1716 100644 --- a/extensions/typescript-language-features/src/configuration/fileSchemes.ts +++ b/extensions/typescript-language-features/src/configuration/fileSchemes.ts @@ -16,8 +16,6 @@ export const azurerepos = 'azurerepos'; export const vsls = 'vsls'; export const walkThroughSnippet = 'walkThroughSnippet'; export const vscodeNotebookCell = 'vscode-notebook-cell'; -export const memFs = 'memfs'; -export const vscodeVfs = 'vscode-vfs'; export const officeScript = 'office-script'; export const chatCodeBlock = 'vscode-chat-code-block'; @@ -31,6 +29,7 @@ export function getSemanticSupportedSchemes() { untitled, walkThroughSnippet, vscodeNotebookCell, + chatCodeBlock, ]; } diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index c95b2e473e336..dc5948314d952 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -745,6 +745,10 @@ export default class BufferSyncSupport extends Disposable { } private shouldValidate(buffer: SyncedBuffer): boolean { + if (buffer.resource.scheme === fileSchemes.chatCodeBlock) { + return false; + } + if (!this.client.configuration.enableProjectDiagnostics && !this._tabResources.has(buffer.resource)) { // Only validate resources that are showing to the user return false; } From 52d39c1fa7eddc73e58595b69702e942fb075253 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 21 Feb 2024 09:00:14 -0800 Subject: [PATCH 0528/1863] Fix high contrast light md images (#205888) Fixes #203686 --- extensions/github/markdown.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/github/markdown.css b/extensions/github/markdown.css index 84441f42b16df..7cfc8ba75a597 100644 --- a/extensions/github/markdown.css +++ b/extensions/github/markdown.css @@ -5,7 +5,7 @@ .vscode-dark img[src$=\#gh-light-mode-only], .vscode-light img[src$=\#gh-dark-mode-only], -.vscode-high-contrast img[src$=\#gh-light-mode-only], +.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only], .vscode-high-contrast-light img[src$=\#gh-dark-mode-only] { display: none; } From b111d8076b5aa45cf82114dfc0fb21ac459dfa12 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 21 Feb 2024 18:05:50 +0100 Subject: [PATCH 0529/1863] fixes https://github.com/microsoft/vscode/issues/203466 (#205889) --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 39b4563509b5b..33094375625ab 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -931,6 +931,7 @@ export class InlineChatController implements IEditorContribution { this._zone.value.setWidgetMargins(widgetPosition); this._zone.value.show(widgetPosition); } else { + this._zone.value.setWidgetMargins(widgetPosition); this._zone.value.updatePositionAndHeight(widgetPosition); } } From 02d0259c51f63dd3c3e3ffd58be46bc14ca58b74 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Wed, 21 Feb 2024 17:55:47 +0100 Subject: [PATCH 0530/1863] Update active recording color and animation --- .../actions/voiceChatActions.ts | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 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 e1e9306cb1c64..987e8d663c80f 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -776,7 +776,7 @@ registerThemingParticipant((theme, collector) => { let activeRecordingDimmedColor: Color | undefined; if (theme.type === ColorScheme.LIGHT || theme.type === ColorScheme.DARK) { activeRecordingColor = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND) ?? theme.getColor(focusBorder); - activeRecordingDimmedColor = activeRecordingColor?.transparent(0.2); + activeRecordingDimmedColor = activeRecordingColor?.transparent(0.38); } else { activeRecordingColor = theme.getColor(contrastBorder); activeRecordingDimmedColor = theme.getColor(contrastBorder); @@ -786,45 +786,23 @@ registerThemingParticipant((theme, collector) => { 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) { - color: ${activeRecordingColor}; - outline: 1px solid ${activeRecordingColor}; - outline-offset: -1px; - animation: pulseAnimation 1s infinite; + outline: 2px solid ${activeRecordingColor}; border-radius: 50%; - } - - .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; - } - - .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 { - content: ''; - position: absolute; - outline: 1px solid ${activeRecordingDimmedColor}; - outline-offset: 3px; - animation: pulseAnimation 1s infinite; - border-radius: 50%; - width: 16px; - height: 16px; + animation: pulseAnimation 1500ms ease-in-out infinite !important; + padding-left: 4px; + height: 17px; } @keyframes pulseAnimation { 0% { - outline-width: 1px; + outline-width: 2px; } 50% { - outline-width: 3px; + outline-width: 5px; outline-color: ${activeRecordingDimmedColor}; } 100% { - outline-width: 1px; + outline-width: 2px; } } `); From c7672504241058f8355b1a48937c0eb51bd9ea15 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 09:46:39 -0800 Subject: [PATCH 0531/1863] fix #205895 (#205896) --- .../contrib/extensions/browser/extensionFeaturesTab.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts index 35491eaa6208e..8bd7b3cd17735 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionFeaturesTab.ts @@ -53,10 +53,10 @@ class RuntimeStatusMarkdownRenderer extends Disposable implements IExtensionFeat shouldRender(manifest: IExtensionManifest): boolean { const extensionId = new ExtensionIdentifier(getExtensionId(manifest.publisher, manifest.name)); - if (this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { - return !!manifest.main || !!manifest.browser; + if (!this.extensionService.extensions.some(e => ExtensionIdentifier.equals(e.identifier, extensionId))) { + return false; } - return !!manifest.activationEvents; + return !!manifest.main || !!manifest.browser; } render(manifest: IExtensionManifest): IRenderedData { From 16ad3d4df85c5c5aed5bda147d6205f8a48d5af0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 10:10:08 -0800 Subject: [PATCH 0532/1863] Fix #205170 (#205897) --- .../output/browser/output.contribution.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 794a8e60d5710..28eba6fc1090f 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -20,7 +20,7 @@ import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensio import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItem, IQuickInputService, IQuickPickSeparator, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { AUX_WINDOW_GROUP, AUX_WINDOW_GROUP_TYPE, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assertIsDefined } from 'vs/base/common/types'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -408,16 +408,30 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { const editorService = accessor.get(IEditorService); const fileConfigurationService = accessor.get(IFilesConfigurationService); - const entries: IOutputChannelQuickPickItem[] = outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(channel => ({ id: channel.id, label: channel.label, channel })); - - const argName = args && typeof args === 'string' ? args : undefined; let entry: IOutputChannelQuickPickItem | undefined; - if (argName) { - entry = entries.find(e => e.id === argName); + const argName = args && typeof args === 'string' ? args : undefined; + const extensionChannels: IOutputChannelQuickPickItem[] = []; + const coreChannels: IOutputChannelQuickPickItem[] = []; + for (const c of outputService.getChannelDescriptors()) { + if (c.file && c.log) { + const e = { id: c.id, label: c.label, channel: c }; + if (c.extensionId) { + extensionChannels.push(e); + } else { + coreChannels.push(e); + } + if (e.id === argName) { + entry = e; + } + } } if (!entry) { - entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log File") }); + const entries: QuickPickInput[] = [...extensionChannels.sort((a, b) => a.label.localeCompare(b.label))]; + if (entries.length && coreChannels.length) { + entries.push({ type: 'separator' }); + entries.push(...coreChannels.sort((a, b) => a.label.localeCompare(b.label))); + } + entry = await quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log File") }); } if (entry) { const resource = assertIsDefined(entry.channel.file); From ad082bd32886f74ddb27168accbc9054f6115360 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 21 Feb 2024 10:17:14 -0800 Subject: [PATCH 0533/1863] Restore focus when the widget is dismissed. (#205900) * Restore focus when the widget is dismissed. * Only restore focus when the widget has focus. --- .../controller/chat/notebookChatController.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index cec4fe9242f63..b22b51850d6b9 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -784,6 +784,27 @@ export class NotebookChatController extends Disposable implements INotebookEdito dismiss() { + // move focus back to the cell above + if (this._widget) { + const widgetIndex = this._widget.afterModelPosition; + const currentFocus = this._notebookEditor.getFocus(); + + if (currentFocus.start === widgetIndex && currentFocus.end === widgetIndex) { + // focus is on the widget + if (widgetIndex === 0) { + // on top of all cells + if (this._notebookEditor.getLength() > 0) { + this._notebookEditor.focusNotebookCell(this._notebookEditor.cellAt(0)!, 'container'); + } + } else { + const cell = this._notebookEditor.cellAt(widgetIndex - 1); + if (cell) { + this._notebookEditor.focusNotebookCell(cell, 'container'); + } + } + } + } + this._ctxCellWidgetFocused.set(false); this._ctxUserDidEdit.set(false); this._sessionCtor?.cancel(); From 7a40a10b24ea6b7dc8169f4716bfd460c448b7bd Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:21:39 -0800 Subject: [PATCH 0534/1863] chore: bump proxy-agent and ip (#205903) --- build/npm/gyp/yarn.lock | 6 +++--- remote/yarn.lock | 12 ++++++------ yarn.lock | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build/npm/gyp/yarn.lock b/build/npm/gyp/yarn.lock index 3716071611c37..c444d89094fcf 100644 --- a/build/npm/gyp/yarn.lock +++ b/build/npm/gyp/yarn.lock @@ -367,9 +367,9 @@ inherits@2, inherits@^2.0.3: integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index e660692a80659..5fdbd0988ea21 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -59,9 +59,9 @@ integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== "@vscode/proxy-agent@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" - integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== + version "0.19.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.1.tgz#d9640d85df1c48885580b68bb4b2b54e17f5332c" + integrity sha512-cs1VOx6d5n69HhgzK0cWeyfudJt+9LdJi/vtgRRxxwisWKg4h83B3+EUJ4udF5SEkJgMBp3oU0jheZVt43ImnQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -314,9 +314,9 @@ ini@~1.3.0: integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-extglob@^2.1.1: version "2.1.1" diff --git a/yarn.lock b/yarn.lock index fa86aaa289306..7dcbe6fe03049 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1410,9 +1410,9 @@ node-addon-api "^6.0.0" "@vscode/proxy-agent@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.0.tgz#ea6571854abb8691ca24a95ba81a6850c54da5df" - integrity sha512-VKCEELuv/BFt1h9wAQE9zuKZM2UAJlJzucXFlvyUYrrPRG66Mgm2JQmClxkE5VRa0gCDRzUClZBT8Fptx8Ce7A== + version "0.19.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.19.1.tgz#d9640d85df1c48885580b68bb4b2b54e17f5332c" + integrity sha512-cs1VOx6d5n69HhgzK0cWeyfudJt+9LdJi/vtgRRxxwisWKg4h83B3+EUJ4udF5SEkJgMBp3oU0jheZVt43ImnQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -5394,9 +5394,9 @@ invert-kv@^2.0.0: integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== is-absolute@^1.0.0: version "1.0.0" From 0cae183880d27078528fd50fffd4e29b5de4a82c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 19:28:21 +0000 Subject: [PATCH 0535/1863] Include conversation history context to `provideCommands` (#205867) * Include conversation history context to `provideCommands` so that chat participant commands can be dynamic to the conversation #199908 * Fix tests * Fix test --- .../api/browser/mainThreadChatAgents2.ts | 17 ++-- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostChatAgents2.ts | 17 ++-- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../browser/contrib/chatInputEditorContrib.ts | 8 +- .../contrib/chat/common/chatAgents.ts | 7 +- .../contrib/chat/common/chatModel.ts | 38 ++++++++- .../contrib/chat/common/chatRequestParser.ts | 12 ++- .../contrib/chat/common/chatServiceImpl.ts | 34 +------- .../contrib/chat/common/chatViewModel.ts | 5 ++ .../contrib/chat/common/voiceChat.ts | 32 +++---- .../actions/voiceChatActions.ts | 2 +- .../chat/test/browser/chatVariables.test.ts | 3 + ..._agent_and_subcommand_after_newline.0.snap | 7 +- ..._subcommand_with_leading_whitespace.0.snap | 7 +- ...uestParser_agent_with_question_mark.0.snap | 7 +- ...er_agent_with_subcommand_after_text.0.snap | 7 +- ...hatRequestParser_agents__subCommand.0.snap | 7 +- ..._agents_and_variables_and_multiline.0.snap | 7 +- ..._and_variables_and_multiline__part2.0.snap | 7 +- .../test/common/chatRequestParser.test.ts | 23 ++--- .../chat/test/common/chatService.test.ts | 12 ++- .../chat/test/common/mockChatService.ts | 85 +++++++++++++++++++ .../chat/test/common/voiceChat.test.ts | 14 +-- .../browser/inlineChatController.ts | 2 +- .../vscode.proposed.chatParticipant.d.ts | 3 +- 26 files changed, 223 insertions(+), 144 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/mockChatService.ts diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index fa796bde94873..c143be650786e 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -20,6 +20,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -76,7 +77,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } $registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void { - let lastSlashCommands: IChatAgentCommand[] | undefined; + const lastSlashCommands: WeakMap = new WeakMap(); const d = this._chatAgentService.registerAgent({ id: name, extensionId: extension, @@ -96,15 +97,19 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA return this._proxy.$provideFollowups(request, handle, result, token); }, - get lastSlashCommands() { - return lastSlashCommands; + getLastSlashCommands: (model: IChatModel) => { + return lastSlashCommands.get(model); }, - provideSlashCommands: async (token) => { + provideSlashCommands: async (model, history, token) => { if (!this._agents.get(handle)?.hasSlashCommands) { return []; // save an IPC call } - lastSlashCommands = await this._proxy.$provideSlashCommands(handle, token); - return lastSlashCommands; + const commands = await this._proxy.$provideSlashCommands(handle, { history }, token); + if (model) { + lastSlashCommands.set(model, commands); + } + + return commands; }, provideWelcomeMessage: (token: CancellationToken) => { return this._proxy.$provideWelcomeMessage(handle, token); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3481cd5a5102c..8dbc24affe718 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1227,7 +1227,7 @@ export type IChatAgentHistoryEntryDto = { export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; - $provideSlashCommands(handle: number, token: CancellationToken): Promise; + $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise; $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise; $acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void; $acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void; diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9f4f87ac9999f..ad00314cac6d0 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -190,7 +190,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { const stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._logService, this.commands.converter, sessionDisposables); try { - const convertedHistory = await this.prepareHistoryTurns(request, context); + const convertedHistory = await this.prepareHistoryTurns(request.agentId, context); const task = agent.invoke( typeConvert.ChatAgentRequest.to(request), { history: convertedHistory }, @@ -220,13 +220,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } } - private async prepareHistoryTurns(request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { + private async prepareHistoryTurns(agentId: string, context: { history: IChatAgentHistoryEntryDto[] }): Promise<(vscode.ChatRequestTurn | vscode.ChatResponseTurn)[]> { const res: (vscode.ChatRequestTurn | vscode.ChatResponseTurn)[] = []; for (const h of context.history) { const ehResult = typeConvert.ChatAgentResult.to(h.result); - const result: vscode.ChatResult = request.agentId === h.request.agentId ? + const result: vscode.ChatResult = agentId === h.request.agentId ? ehResult : { ...ehResult, metadata: undefined }; @@ -245,13 +245,16 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._sessionDisposables.deleteAndDispose(sessionId); } - async $provideSlashCommands(handle: number, token: CancellationToken): Promise { + async $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise { const agent = this._agents.get(handle); if (!agent) { // this is OK, the agent might have disposed while the request was in flight return []; } - return agent.provideSlashCommands(token); + + const convertedHistory = await this.prepareHistoryTurns(agent.id, context); + + return agent.provideSlashCommands({ history: convertedHistory }, token); } async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { @@ -386,11 +389,11 @@ class ExtHostChatAgent { return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? []; } - async provideSlashCommands(token: CancellationToken): Promise { + async provideSlashCommands(context: vscode.ChatContext, token: CancellationToken): Promise { if (!this._commandProvider) { return []; } - const result = await this._commandProvider.provideCommands(token); + const result = await this._commandProvider.provideCommands(context, token); if (!result) { return []; } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 528810a99a0a2..d1cdc123a6b25 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -266,7 +266,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`; - const commands = await a.provideSlashCommands(CancellationToken.None); + const commands = await a.provideSlashCommands(undefined, [], CancellationToken.None); const commandText = commands.map(c => { const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 12c466bfd6f90..791f9e41fd3a0 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -27,6 +27,7 @@ import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { SelectAndInsertFileAction, dynamicVariableDecorationType } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; +import { getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; @@ -398,7 +399,7 @@ class AgentCompletions extends Disposable { } const usedAgent = parsedRequest[usedAgentIdx] as ChatRequestAgentPart; - const commands = await usedAgent.agent.provideSlashCommands(token); // Refresh the cache here + const commands = await usedAgent.agent.provideSlashCommands(widget.viewModel.model, getHistoryEntriesFromModel(widget.viewModel.model), token); // Refresh the cache here return { suggestions: commands.map((c, i) => { @@ -421,7 +422,8 @@ class AgentCompletions extends Disposable { triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget) { + const viewModel = widget?.viewModel; + if (!widget || !viewModel) { return; } @@ -431,7 +433,7 @@ class AgentCompletions extends Disposable { } const agents = this.chatAgentService.getAgents(); - const all = agents.map(agent => agent.provideSlashCommands(token)); + const all = agents.map(agent => agent.provideSlashCommands(viewModel.model, getHistoryEntriesFromModel(viewModel.model), token)); const commands = await raceCancellation(Promise.all(all), token); if (!commands) { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 3f9eb992697e9..4ee4c07f0cf6c 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; //#region agent service, commands etc @@ -33,8 +33,8 @@ export interface IChatAgentData { export interface IChatAgent extends IChatAgentData { invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise; - lastSlashCommands?: IChatAgentCommand[]; - provideSlashCommands(token: CancellationToken): Promise; + getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined; + provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; } @@ -155,7 +155,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No agent with id ${id} registered`); } data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; - data.agent.provideSlashCommands(CancellationToken.None); // Update the cached slash commands this._onDidChangeAgents.fire(); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 64b50701e8432..c8e47cb8b1626 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -14,8 +14,8 @@ import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatRequestTextPart, IParsedChatRequest, getPromptText, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -843,3 +843,37 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { return this.session.responderAvatarIconUri; } } + +export function getHistoryEntriesFromModel(model: IChatModel): IChatAgentHistoryEntry[] { + const history: IChatAgentHistoryEntry[] = []; + for (const request of model.getRequests()) { + if (!request.response) { + continue; + } + + const promptTextResult = getPromptText(request.message); + const historyRequest: IChatAgentRequest = { + sessionId: model.sessionId, + requestId: request.id, + agentId: request.response.agent?.id ?? '', + message: promptTextResult.message, + command: request.response.slashCommand?.name, + variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack + }; + history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); + } + + return history; +} + +export function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { + return { + variables: variableData.variables.map(v => ({ + ...v, + range: { + start: v.range.start - diff, + endExclusive: v.range.endExclusive - diff + } + })) + }; +} diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 007b8c8a1917e..aa05f52b6d884 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -7,7 +7,9 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -19,12 +21,14 @@ export class ChatRequestParser { constructor( @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, - @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService + @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService, + @IChatService private readonly chatService: IChatService ) { } parseChatRequest(sessionId: string, message: string): IParsedChatRequest { const parts: IParsedChatRequestPart[] = []; const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls + const model = this.chatService.getSession(sessionId)!; let lineNumber = 1; let column = 1; @@ -38,7 +42,7 @@ export class ChatRequestParser { } else if (char === chatAgentLeader) { newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts); } else if (char === chatSubcommandLeader) { - newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts); + newPart = this.tryToParseSlashCommand(model, message.slice(i), message, i, new Position(lineNumber, column), parts); } if (!newPart) { @@ -138,7 +142,7 @@ export class ChatRequestParser { return; } - private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { + private tryToParseSlashCommand(model: IChatModel, remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined { const nextSlashMatch = remainingMessage.match(slashReg); if (!nextSlashMatch) { return; @@ -167,7 +171,7 @@ export class ChatRequestParser { return; } - const subCommands = usedAgent.agent.lastSlashCommands; + const subCommands = usedAgent.agent.getLastSlashCommands(model); const subCommand = subCommands?.find(c => c.name === command); if (subCommand) { // Valid agent subcommand diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 630e7e853e8bf..89081fe437d25 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -20,9 +20,9 @@ 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 { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { 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 } from 'vs/workbench/contrib/chat/common/chatModel'; +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'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; @@ -531,23 +531,7 @@ export class ChatService extends Disposable implements IChatService { const defaultAgent = this.chatAgentService.getDefaultAgent(); if (agentPart || (defaultAgent && !commandPart)) { const agent = (agentPart?.agent ?? defaultAgent)!; - const history: IChatAgentHistoryEntry[] = []; - for (const request of model.getRequests()) { - if (!request.response) { - continue; - } - - const promptTextResult = getPromptText(request.message); - const historyRequest: IChatAgentRequest = { - sessionId, - requestId: request.id, - agentId: request.response.agent?.id ?? '', - message: promptTextResult.message, - command: request.response.slashCommand?.name, - variables: updateRanges(request.variableData, promptTextResult.diff) // TODO bit of a hack - }; - history.push({ request: historyRequest, response: request.response.response.value, result: request.response.result ?? {} }); - } + const history = getHistoryEntriesFromModel(model); const initVariableData: IChatRequestVariableData = { variables: [] }; request = model.addRequest(parsedRequest, initVariableData, agent, agentSlashCommandPart?.command); @@ -764,15 +748,3 @@ export class ChatService extends Disposable implements IChatService { this.trace('transferChatSession', `Transferred session ${model.sessionId} to workspace ${toWorkspace.toString()}`); } } - -function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData { - return { - variables: variableData.variables.map(v => ({ - ...v, - range: { - start: v.range.start - diff, - endExclusive: v.range.endExclusive - diff - } - })) - }; -} diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 1a95949446422..a91512a6840de 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -41,6 +41,7 @@ export interface IChatSessionInitEvent { } export interface IChatViewModel { + readonly model: IChatModel; readonly initState: ChatModelInitState; readonly providerId: string; readonly sessionId: string; @@ -146,6 +147,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { return this._inputPlaceholder ?? this._model.inputPlaceholder; } + get model(): IChatModel { + return this._model; + } + setInputPlaceholder(text: string): void { this._inputPlaceholder = text; this._onDidChange.fire({ kind: 'changePlaceholder' }); diff --git a/src/vs/workbench/contrib/chat/common/voiceChat.ts b/src/vs/workbench/contrib/chat/common/voiceChat.ts index c0cf6daa574a2..9c92a44354a48 100644 --- a/src/vs/workbench/contrib/chat/common/voiceChat.ts +++ b/src/vs/workbench/contrib/chat/common/voiceChat.ts @@ -9,6 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { rtrim } from 'vs/base/common/strings'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ISpeechService, ISpeechToTextEvent, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; @@ -16,6 +17,7 @@ export const IVoiceChatService = createDecorator('voiceChatSe export interface IVoiceChatSessionOptions { readonly usesAgents?: boolean; + readonly model?: IChatModel; } export interface IVoiceChatService { @@ -75,37 +77,23 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { private static readonly CHAT_AGENT_ALIAS = new Map([['vscode', 'code']]); - private _phrases: Map | undefined = undefined; - private get phrases(): Map { - if (!this._phrases) { - this._phrases = this.createPhrases(); - } - - return this._phrases; - } - constructor( @ISpeechService private readonly speechService: ISpeechService, @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.chatAgentService.onDidChangeAgents(() => this._phrases = undefined)); } - private createPhrases(): Map { + private createPhrases(model?: IChatModel): Map { const phrases = new Map(); for (const agent of this.chatAgentService.getAgents()) { const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase(); phrases.set(agentPhrase, { agent: agent.id }); - if (agent.lastSlashCommands) { - for (const slashCommand of agent.lastSlashCommands) { + const commands = model && agent.getLastSlashCommands(model); + if (commands) { + for (const slashCommand of commands) { const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase(); phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name }); @@ -138,6 +126,8 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { const emitter = disposables.add(new Emitter()); const session = this.speechService.createSpeechToTextSession(token, 'chat'); + + const phrases = this.createPhrases(options.model); disposables.add(session.onDidChange(e => { switch (e.status) { case SpeechToTextStatus.Recognizing: @@ -153,7 +143,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent + slash command if (options.usesAgents && startsWithAgent && !detectedAgent && !detectedSlashCommand && originalWords.length >= 4) { - const phrase = this.phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 4).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, PhraseTextType.AGENT_AND_COMMAND), ...originalWords.slice(4)]; @@ -168,7 +158,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for agent (if not done already) if (options.usesAgents && startsWithAgent && !detectedAgent && !transformedWords && originalWords.length >= 2) { - const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, PhraseTextType.AGENT), ...originalWords.slice(2)]; @@ -182,7 +172,7 @@ export class VoiceChatService extends Disposable implements IVoiceChatService { // Check for slash command (if not done already) if (startsWithSlashCommand && !detectedSlashCommand && !transformedWords && originalWords.length >= 2) { - const phrase = this.phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); + const phrase = phrases.get(originalWords.slice(0, 2).map(word => this.normalizeWord(word)).join(' ')); if (phrase) { transformedWords = [this.toText(phrase, options.usesAgents && !detectedAgent ? PhraseTextType.AGENT_AND_COMMAND : // rewrite `/fix` to `@workspace /foo` in this case 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 987e8d663c80f..831f3d4872472 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -300,7 +300,7 @@ class VoiceChatSessions { this.voiceChatGettingReadyKey.set(true); - const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline' }); + const voiceChatSession = this.voiceChatService.createVoiceChatSession(cts.token, { usesAgents: controller.context !== 'inline', model: context?.widget?.viewModel?.model }); let inputValue = controller.getInput(); diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index f835af908b4e1..35173312a8c0e 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -12,8 +12,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatWidgetService } from 'vs/workbench/contrib/chat/test/browser/mockChatWidget'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -30,6 +32,7 @@ suite('ChatVariables', function () { instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatVariablesService, service); + instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); }); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap index cc7ecaf508d4a..4a241114279b9 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_after_newline.0.snap @@ -29,12 +29,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap index d46c197f63314..becd9bf6f3169 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_and_subcommand_with_leading_whitespace.0.snap @@ -29,12 +29,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 4891a73e6415b..50c67ea58d075 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap index d78899819150c..345e8c874dea8 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_subcommand_after_text.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap index df42f889d0548..406e20cfe55a7 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index d3f091a95e8ef..31fd0b94e96e3 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap index c4b86b46fffb4..85bc82a3136ce 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -15,12 +15,7 @@ id: "agent", metadata: { description: "" }, provideSlashCommands: [Function provideSlashCommands], - lastSlashCommands: [ - { - name: "subCommand", - description: "" - } - ] + getLastSlashCommands: [Function getLastSlashCommands] }, kind: "agent" }, diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 5585c30afa878..ba397535bffa1 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -11,8 +11,10 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgent, IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -28,6 +30,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); varService = mockObject()({}); @@ -108,13 +111,13 @@ suite('ChatRequestParser', () => { await assertSnapshot(result); }); - const getAgentWithSlashcommands = (slashCommands: IChatAgentCommand[]) => { - return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], lastSlashCommands: slashCommands }; + const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { + return >{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], getLastSlashCommands: () => slashCommands }; }; test('agent with subcommand after text', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -124,7 +127,7 @@ suite('ChatRequestParser', () => { test('agents, subCommand', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -134,7 +137,7 @@ suite('ChatRequestParser', () => { test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -144,7 +147,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand with leading whitespace', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -154,7 +157,7 @@ suite('ChatRequestParser', () => { test('agent and subcommand after newline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -164,7 +167,7 @@ suite('ChatRequestParser', () => { test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -174,7 +177,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); @@ -186,7 +189,7 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline, part2', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns(getAgentWithSlashcommands([{ name: 'subCommand', description: '' }])); + agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])); instantiationService.stub(IChatAgentService, agentsService as any); varService.hasVariable.returns(true); diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 9f7e33e850ae7..3a2b6abc9985f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -24,13 +24,14 @@ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService'; class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; @@ -59,7 +60,10 @@ const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, metadata: {}, - async provideSlashCommands(token) { + getLastSlashCommands() { + return undefined; + }, + async provideSlashCommands() { return []; }, async invoke(request, progress, history, token) { @@ -104,6 +108,7 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); + instantiationService.stub(IChatService, new MockChatService()); chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, chatAgentService); @@ -115,6 +120,9 @@ suite('Chat', () => { async invoke(request, progress, history, token) { return {}; }, + getLastSlashCommands() { + return undefined; + }, async provideSlashCommands(token) { return []; }, diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts new file mode 100644 index 0000000000000..35c177533926d --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; + +export class MockChatService implements IChatService { + _serviceBrand: undefined; + transferredSessionData: IChatTransferredSessionData | undefined; + + hasSessions(providerId: string): boolean { + throw new Error('Method not implemented.'); + } + getProviderInfos(): IChatProviderInfo[] { + throw new Error('Method not implemented.'); + } + startSession(providerId: string, token: CancellationToken): ChatModel | undefined { + throw new Error('Method not implemented.'); + } + getSession(sessionId: string): IChatModel | undefined { + return {} as IChatModel; + } + getSessionId(sessionProviderId: number): string | undefined { + throw new Error('Method not implemented.'); + } + getOrRestoreSession(sessionId: string): IChatModel | undefined { + throw new Error('Method not implemented.'); + } + loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { + throw new Error('Method not implemented.'); + } + onDidRegisterProvider: Event<{ providerId: string }> = undefined!; + onDidUnregisterProvider: Event<{ providerId: string }> = undefined!; + registerProvider(provider: IChatProvider): IDisposable { + throw new Error('Method not implemented.'); + } + + /** + * Returns whether the request was accepted. + */ + sendRequest(sessionId: string, message: string): Promise { + throw new Error('Method not implemented.'); + } + removeRequest(sessionid: string, requestId: string): Promise { + throw new Error('Method not implemented.'); + } + cancelCurrentRequestForSession(sessionId: string): void { + throw new Error('Method not implemented.'); + } + clearSession(sessionId: string): void { + throw new Error('Method not implemented.'); + } + addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): void { + throw new Error('Method not implemented.'); + } + sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void { + throw new Error('Method not implemented.'); + } + getHistory(): IChatDetail[] { + throw new Error('Method not implemented.'); + } + clearAllHistoryEntries(): void { + throw new Error('Method not implemented.'); + } + removeHistoryEntry(sessionId: string): void { + throw new Error('Method not implemented.'); + } + + onDidPerformUserAction: Event = undefined!; + notifyUserAction(event: IChatUserActionEvent): void { + throw new Error('Method not implemented.'); + } + onDidDisposeSession: Event<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!; + + transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index eaaea9096dd2c..c85e0056d12f7 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -12,6 +12,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { ProviderResult } from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { ISpeechProvider, ISpeechService, ISpeechToTextEvent, ISpeechToTextSession, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; @@ -27,9 +28,10 @@ suite('VoiceChat', () => { extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; - constructor(readonly id: string, readonly lastSlashCommands: IChatAgentCommand[]) { } + constructor(readonly id: string, private readonly lastSlashCommands: IChatAgentCommand[]) { } + getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined { return this.lastSlashCommands; } invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } - provideSlashCommands(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error('Method not implemented.'); } provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); } metadata = {}; } @@ -108,11 +110,11 @@ suite('VoiceChat', () => { }); test('Agent and slash command detection (useAgents: false)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: false }); + testAgentsAndSlashCommandsDetection({ usesAgents: false, model: {} as IChatModel }); }); test('Agent and slash command detection (useAgents: true)', async () => { - testAgentsAndSlashCommandsDetection({ usesAgents: true }); + testAgentsAndSlashCommandsDetection({ usesAgents: true, model: {} as IChatModel }); }); function testAgentsAndSlashCommandsDetection(options: IVoiceChatSessionOptions) { @@ -295,7 +297,7 @@ suite('VoiceChat', () => { test('waiting for input', async () => { // Agent - createSession({ usesAgents: true }); + createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); @@ -308,7 +310,7 @@ suite('VoiceChat', () => { assert.strictEqual(event.waitingForInput, true); // Slash Command - createSession({ usesAgents: true }); + createSession({ usesAgents: true, model: {} as IChatModel }); emitter.fire({ status: SpeechToTextStatus.Recognizing, text: 'At workspace slash explain' }); assert.strictEqual(event?.status, SpeechToTextStatus.Recognizing); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 33094375625ab..38ea05687a15d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -524,7 +524,7 @@ export class InlineChatController implements IEditorContribution { const cts = new CancellationTokenSource(); this._sessionStore.add(cts); for (const agent of this._chatAgentService.getAgents()) { - const commands = await agent.provideSlashCommands(cts.token); + const commands = await agent.provideSlashCommands(undefined, [], cts.token); if (commands.find((command) => withoutSubCommandLeader.startsWith(command.name))) { massagedInput = `${chatAgentLeader}${agent.id} ${input}`; break; diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index 43bacd595dd29..ed62261761d72 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -183,8 +183,7 @@ declare module 'vscode' { * @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or * an empty array. */ - // TODO@API Q: should we provide the current history or last results for extra context? - provideCommands(token: CancellationToken): ProviderResult; + provideCommands(context: ChatContext, token: CancellationToken): ProviderResult; } /** From 52cb4e45c4c509e63441d81d096322dbe5e1cdf7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 21 Feb 2024 21:08:06 +0100 Subject: [PATCH 0536/1863] voice - add config for language (#205908) * voice - add config for language * . * . * . --- .../workbench/api/browser/mainThreadSpeech.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostSpeech.ts | 4 +- .../browser/accessibilityConfiguration.ts | 96 ++++++++++++++++++- .../contrib/speech/browser/speechService.ts | 7 +- .../contrib/speech/common/speechService.ts | 6 +- src/vscode-dts/vscode.proposed.speech.d.ts | 6 +- 7 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSpeech.ts b/src/vs/workbench/api/browser/mainThreadSpeech.ts index 389fa1b87097b..56ce1bca62330 100644 --- a/src/vs/workbench/api/browser/mainThreadSpeech.ts +++ b/src/vs/workbench/api/browser/mainThreadSpeech.ts @@ -41,7 +41,7 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const registration = this.speechService.registerSpeechProvider(identifier, { metadata, - createSpeechToTextSession: token => { + createSpeechToTextSession: (token, options) => { if (token.isCancellationRequested) { return { onDidChange: Event.None @@ -51,7 +51,7 @@ export class MainThreadSpeech implements MainThreadSpeechShape { const disposables = new DisposableStore(); const session = Math.random(); - this.proxy.$createSpeechToTextSession(handle, session); + this.proxy.$createSpeechToTextSession(handle, session, options?.language); const onDidChange = disposables.add(new Emitter()); this.speechToTextSessions.set(session, { onDidChange }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8dbc24affe718..5c18ab8657ab5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1171,7 +1171,7 @@ export interface MainThreadSpeechShape extends IDisposable { } export interface ExtHostSpeechShape { - $createSpeechToTextSession(handle: number, session: number): Promise; + $createSpeechToTextSession(handle: number, session: number, language?: string): Promise; $cancelSpeechToTextSession(session: number): Promise; $createKeywordRecognitionSession(handle: number, session: number): Promise; diff --git a/src/vs/workbench/api/common/extHostSpeech.ts b/src/vs/workbench/api/common/extHostSpeech.ts index 8d230fd19f2f9..9093f63e3abc5 100644 --- a/src/vs/workbench/api/common/extHostSpeech.ts +++ b/src/vs/workbench/api/common/extHostSpeech.ts @@ -24,7 +24,7 @@ export class ExtHostSpeech implements ExtHostSpeechShape { this.proxy = mainContext.getProxy(MainContext.MainThreadSpeech); } - async $createSpeechToTextSession(handle: number, session: number): Promise { + async $createSpeechToTextSession(handle: number, session: number, language?: string): Promise { const provider = this.providers.get(handle); if (!provider) { return; @@ -35,7 +35,7 @@ export class ExtHostSpeech implements ExtHostSpeechShape { const cts = new CancellationTokenSource(); this.sessions.set(session, cts); - const speechToTextSession = disposables.add(provider.provideSpeechToTextSession(cts.token)); + const speechToTextSession = disposables.add(provider.provideSpeechToTextSession(cts.token, language ? { language } : undefined)); disposables.add(speechToTextSession.onDidChange(e => { if (cts.token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9a2..43507114aa04c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -660,9 +660,11 @@ export function registerAccessibilityConfiguration() { } export const enum AccessibilityVoiceSettingId { - SpeechTimeout = 'accessibility.voice.speechTimeout' + SpeechTimeout = 'accessibility.voice.speechTimeout', + SpeechLanguage = 'accessibility.voice.speechLanguage' } export const SpeechTimeoutDefault = 1200; +const SpeechLanguageDefault = 'en-US'; export class DynamicSpeechAccessibilityConfiguration extends Disposable implements IWorkbenchContribution { @@ -681,6 +683,11 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen return; // these settings require a speech provider } + const languages = this.getLanguages(); + const languagesSorted = Object.keys(languages).sort((langA, langB) => { + return languages[langA].name.localeCompare(languages[langB].name); + }); + const registry = Registry.as(Extensions.Configuration); registry.registerConfiguration({ ...accessibilityConfigurationNodeBase, @@ -691,11 +698,98 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen 'default': SpeechTimeoutDefault, 'minimum': 0, 'tags': ['accessibility'] + }, + [AccessibilityVoiceSettingId.SpeechLanguage]: { + 'markdownDescription': localize('voice.speechLanguage', "The language that voice speech recognition should recognize."), + 'type': 'string', + 'enum': languagesSorted, + 'default': SpeechLanguageDefault, + 'tags': ['accessibility'], + 'enumDescriptions': languagesSorted.map(key => languages[key].name), + 'enumItemLabels': languagesSorted.map(key => languages[key].name) } } }); } + + private getLanguages(): { [locale: string]: { name: string } } { + return { + ['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-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)") + } + }; + } } + Registry.as(WorkbenchExtensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'audioCues.volume', diff --git a/src/vs/workbench/contrib/speech/browser/speechService.ts b/src/vs/workbench/contrib/speech/browser/speechService.ts index e190ac66d3607..00943f3262b83 100644 --- a/src/vs/workbench/contrib/speech/browser/speechService.ts +++ b/src/vs/workbench/contrib/speech/browser/speechService.ts @@ -13,6 +13,7 @@ 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class SpeechService extends Disposable implements ISpeechService { @@ -34,7 +35,8 @@ export class SpeechService extends Disposable implements ISpeechService { @ILogService private readonly logService: ILogService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); } @@ -78,7 +80,8 @@ export class SpeechService extends Disposable implements ISpeechService { this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); } - const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token); + const language = this.configurationService.getValue('accessibility.voice.speechLanguage'); + const session = this._activeSpeechToTextSession = provider.createSpeechToTextSession(token, typeof language === 'string' ? { language } : undefined); const sessionStart = Date.now(); let sessionRecognized = false; diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index 4e4ff9345e66a..a594b4f3acfb4 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -52,10 +52,14 @@ export interface IKeywordRecognitionSession { readonly onDidChange: Event; } +export interface ISpeechToTextSessionOptions { + readonly language?: string; +} + export interface ISpeechProvider { readonly metadata: ISpeechProviderMetadata; - createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; + createSpeechToTextSession(token: CancellationToken, options?: ISpeechToTextSessionOptions): ISpeechToTextSession; createKeywordRecognitionSession(token: CancellationToken): IKeywordRecognitionSession; } diff --git a/src/vscode-dts/vscode.proposed.speech.d.ts b/src/vscode-dts/vscode.proposed.speech.d.ts index 42fbbb033205a..091c3995af300 100644 --- a/src/vscode-dts/vscode.proposed.speech.d.ts +++ b/src/vscode-dts/vscode.proposed.speech.d.ts @@ -7,6 +7,10 @@ declare module 'vscode' { // todo@bpasero work in progress speech API + export interface SpeechToTextOptions { + readonly language?: string; + } + export enum SpeechToTextStatus { Started = 1, Recognizing = 2, @@ -38,7 +42,7 @@ declare module 'vscode' { } export interface SpeechProvider { - provideSpeechToTextSession(token: CancellationToken): SpeechToTextSession; + provideSpeechToTextSession(token: CancellationToken, options?: SpeechToTextOptions): SpeechToTextSession; provideKeywordRecognitionSession(token: CancellationToken): KeywordRecognitionSession; } From 4e2aa982224429f161423cbdded9587e39766782 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:08:34 -0600 Subject: [PATCH 0537/1863] findfiles2 doc clarity improvements (#205902) --- src/vscode-dts/vscode.proposed.findFiles2.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.findFiles2.d.ts b/src/vscode-dts/vscode.proposed.findFiles2.d.ts index f861c7930cea6..145776110d4f1 100644 --- a/src/vscode-dts/vscode.proposed.findFiles2.d.ts +++ b/src/vscode-dts/vscode.proposed.findFiles2.d.ts @@ -54,13 +54,14 @@ declare module 'vscode' { /** * Whether symlinks should be followed while searching. - * See the vscode setting `"search.followSymlinks"`. + * Defaults to the value for `search.followSymlinks` in settings. + * For more info, see the setting listed above. */ followSymlinks?: boolean; /** * If set to true, the `filePattern` arg will be fuzzy-searched instead of glob-searched. - * If `filePattern` is a `GlobPattern`, then the fuzzy search will act on the `pattern` of the `RelativePattern` + * If `filePattern` is a {@link RelativePattern relative pattern}, then the fuzzy search will act on the `pattern` of the {@link RelativePattern RelativePattern} */ fuzzy?: boolean; } @@ -73,7 +74,7 @@ declare module 'vscode' { * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace. * * @example - * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true}, 10) + * findFiles('**​/*.js', {exclude: '**​/out/**', useIgnoreFiles: true, maxResults: 10}) * * @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern * will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern} From ccb571b6eff269584a68b0d7d43e9a0550c9f7bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 21 Feb 2024 12:18:50 -0800 Subject: [PATCH 0538/1863] fix bugs --- .../browser/accessibilityConfiguration.ts | 13 ++++++++----- .../accessibilitySignals/browser/commands.ts | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index afd920ac1f9a2..7710bbe3b73a6 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -115,7 +115,7 @@ export const announcementFeatureBase: IConfigurationPropertySchema = { const defaultNoAnnouncement: IConfigurationPropertySchema = { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'default': { 'sound': 'auto', } @@ -536,7 +536,6 @@ const configuration: IConfigurationNode = { }, 'accessibility.signals.chatResponseReceived': { ...defaultNoAnnouncement, - additionalProperties: false, 'description': localize('accessibility.signals.chatResponseReceived', "Indicates when the response has been received."), 'properties': { 'sound': { @@ -562,7 +561,7 @@ const configuration: IConfigurationNode = { 'accessibility.signals.save': { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'markdownDescription': localize('accessibility.signals.save', "Plays a signal when a file is saved."), 'properties': { 'sound': { @@ -596,7 +595,7 @@ const configuration: IConfigurationNode = { 'accessibility.signals.format': { 'type': 'object', 'tags': ['accessibility'], - additionalProperties: true, + additionalProperties: false, 'markdownDescription': localize('accessibility.signals.format', "Plays a signal when a file or notebook is formatted."), 'properties': { 'sound': { @@ -621,6 +620,10 @@ const configuration: IConfigurationNode = { localize('accessibility.signals.format.announcement.never', "Never announces.") ], }, + }, + default: { + 'sound': 'never', + 'announcement': 'never' } }, } @@ -737,7 +740,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi }))); Registry.as(WorkbenchExtensions.ConfigurationMigration) - .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.announcementMessage).map(item => ({ + .registerConfigurationMigrations(AccessibilitySignal.allAccessibilitySignals.filter(i => !!i.legacyAnnouncementSettingsKey).map(item => ({ key: item.legacyAnnouncementSettingsKey!, migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 666516df128eb..8561b125cd0a9 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -12,6 +12,7 @@ import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/ac import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class ShowSignalSoundHelp extends Action2 { static readonly ID = 'signals.sounds.help'; @@ -32,6 +33,7 @@ export class ShowSignalSoundHelp extends Action2 { const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); + const preferencesService = accessor.get(IPreferencesService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.map((signal, idx) => ({ label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.sound')})` : signal.name, @@ -68,6 +70,9 @@ export class ShowSignalSoundHelp extends Action2 { } qp.hide(); }); + qp.onDidTriggerItemButton(e => { + preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); + }); qp.onDidChangeActive(() => { accessibilitySignalService.playSound(qp.activeItems[0].signal.sound.getSound(true), true); }); @@ -96,14 +101,15 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { const quickInputService = accessor.get(IQuickInputService); const configurationService = accessor.get(IConfigurationService); const accessibilityService = accessor.get(IAccessibilityService); + const preferencesService = accessor.get(IPreferencesService); const userGestureSignals = [AccessibilitySignal.save, AccessibilitySignal.format]; - const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.announcementMessage).map((signal, idx) => ({ + const items: (IQuickPickItem & { signal: AccessibilitySignal })[] = AccessibilitySignal.allAccessibilitySignals.filter(c => !!c.legacyAnnouncementSettingsKey).map((signal, idx) => ({ label: userGestureSignals.includes(signal) ? `${signal.name} (${configurationService.getValue(signal.settingsKey + '.announcement')})` : signal.name, signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), - alwaysVisible: true + alwaysVisible: true, }] : [] })); const qp = quickInputService.createQuickPick(); @@ -111,7 +117,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { qp.selectedItems = items.filter(i => accessibilitySignalService.isAnnouncementEnabled(i.signal) || userGestureSignals.includes(i.signal) && configurationService.getValue(i.signal.settingsKey + '.announcement') !== 'never'); qp.onDidAccept(() => { const enabledAnnouncements = qp.selectedItems.map(i => i.signal); - const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !enabledAnnouncements.includes(cue)); + const disabledAnnouncements = AccessibilitySignal.allAccessibilitySignals.filter(cue => !!cue.legacyAnnouncementSettingsKey && !enabledAnnouncements.includes(cue)); for (const signal of enabledAnnouncements) { let { sound, announcement } = configurationService.getValue<{ sound: string; announcement?: string }>(signal.settingsKey); announcement = userGestureSignals.includes(signal) ? 'userGesture' : signal.announcementMessage && accessibilityService.isScreenReaderOptimized() ? 'auto' : undefined; @@ -124,6 +130,9 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { } qp.hide(); }); + qp.onDidTriggerItemButton(e => { + preferencesService.openUserSettings({ jsonEditor: true, revealSetting: { key: e.item.signal.settingsKey, edit: true } }); + }); qp.placeholder = localize('announcement.help.placeholder', 'Select an announcement to configure'); qp.canSelectMany = true; await qp.show(); From 92db21f4e0bf366ecfc24ae0144b426f009214a5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 21 Feb 2024 12:25:26 -0800 Subject: [PATCH 0539/1863] fix #204994 (#205909) --- src/vs/workbench/browser/parts/paneCompositeBar.ts | 6 ------ src/vs/workbench/browser/parts/paneCompositePart.ts | 4 ++-- src/vs/workbench/services/views/browser/viewsService.ts | 5 +++++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 4387df1a1380c..8fe901376cc03 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -207,12 +207,6 @@ export class PaneCompositeBar extends Disposable { if (to === this.location) { this.onDidRegisterViewContainers([container]); - - // Open view container if part is visible and there is no other view container opened - const visibleComposites = this.compositeBar.getVisibleComposites(); - if (!this.paneCompositePart.getActivePaneComposite() && this.layoutService.isVisible(this.paneCompositePart.partId) && visibleComposites.length) { - this.paneCompositePart.openPaneComposite(visibleComposites[0].id); - } } } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 82d20fd96663a..6ef4dfe09a3d6 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -180,7 +180,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.updateGlobalToolbarActions())); - this._register(this.registry.onDidDeregister(async (viewletDescriptor: PaneCompositeDescriptor) => { + this._register(this.registry.onDidDeregister((viewletDescriptor: PaneCompositeDescriptor) => { const activeContainers = this.viewDescriptorService.getViewContainersByLocation(this.location) .filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); @@ -189,7 +189,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart c.id === defaultViewletId)[0] || activeContainers[0]; - await this.openPaneComposite(containerToOpen.id); + this.doOpenPaneComposite(containerToOpen.id); } } else { this.layoutService.setPartHidden(true, this.partId); diff --git a/src/vs/workbench/services/views/browser/viewsService.ts b/src/vs/workbench/services/views/browser/viewsService.ts index 49643efd58855..c342e91da150a 100644 --- a/src/vs/workbench/services/views/browser/viewsService.ts +++ b/src/vs/workbench/services/views/browser/viewsService.ts @@ -137,6 +137,11 @@ export class ViewsService extends Disposable implements IViewsService { private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { this.deregisterPaneComposite(viewContainer, from); this.registerPaneComposite(viewContainer, to); + + // Open view container if part is visible and there is only one view container in location + if (this.layoutService.isVisible(getPartByLocation(to)) && this.viewDescriptorService.getViewContainersByLocation(to).length === 1) { + this.openViewContainer(viewContainer.id); + } } private onViewDescriptorsAdded(views: ReadonlyArray, container: ViewContainer): void { From 61f447b79af940ff0675b0a76bc5bd8b9c659c3a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 20:57:18 +0000 Subject: [PATCH 0540/1863] More small chat API fixes (#205901) * Fix #205811 * Fix #205807 * Make description optional * Fix * fix test --- .../src/singlefolder-tests/chat.test.ts | 2 +- .../workbench/api/common/extHostChatAgents2.ts | 6 ++++-- src/vs/workbench/api/common/extHostTypes.ts | 4 ++-- .../vscode.proposed.chatParticipant.d.ts | 16 +++++----------- .../vscode.proposed.defaultChatParticipant.d.ts | 5 +++++ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 44fa439679611..6fb0262e13244 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -67,7 +67,7 @@ suite('chat', () => { interactive.sendInteractiveRequestToProvider('provider', { message: '@participant /hello friend' }); } else { assert.strictEqual(request.context.history.length, 1); - assert.strictEqual(request.context.history[0].participant.participant, 'participant'); + assert.strictEqual(request.context.history[0].participant.name, 'participant'); assert.strictEqual(request.context.history[0].command, 'hello'); } }); diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index ad00314cac6d0..9f9266babba68 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -231,11 +231,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { { ...ehResult, metadata: undefined }; // REQUEST turn - res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', participant: h.request.agentId })); + res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', name: h.request.agentId })); // RESPONSE turn const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.fromContent(r, this.commands.converter))); - res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', participant: h.request.agentId }, h.request.command)); + res.push(new extHostTypes.ChatResponseTurn(parts, result, { extensionId: '', name: h.request.agentId }, h.request.command)); } return res; @@ -508,9 +508,11 @@ class ExtHostChatAgent { updateMetadataSoon(); }, get fullName() { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); return that._fullName ?? that.extension.displayName ?? that.extension.name; }, set fullName(v) { + checkProposedApiEnabled(that.extension, 'defaultChatParticipant'); that._fullName = v; updateMetadataSoon(); }, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4827cdfbfeb03..4fb94e3f1c850 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4257,7 +4257,7 @@ export class ChatRequestTurn implements vscode.ChatRequestTurn { readonly prompt: string, readonly command: string | undefined, readonly variables: vscode.ChatResolvedVariable[], - readonly participant: { extensionId: string; participant: string }, + readonly participant: { extensionId: string; name: string }, ) { } } @@ -4266,7 +4266,7 @@ export class ChatResponseTurn implements vscode.ChatResponseTurn { constructor( readonly response: ReadonlyArray, readonly result: vscode.ChatResult, - readonly participant: { extensionId: string; participant: string }, + readonly participant: { extensionId: string; name: string }, readonly command?: string ) { } } diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index ed62261761d72..5fcc77cb7b571 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -23,7 +23,7 @@ declare module 'vscode' { /** * The name of the chat participant and contributing extension to which this request was directed. */ - readonly participant: { readonly extensionId: string; readonly participant: string }; + readonly participant: { readonly extensionId: string; readonly name: string }; /** * The name of the {@link ChatCommand command} that was selected for this request. @@ -35,7 +35,7 @@ declare module 'vscode' { */ readonly variables: ChatResolvedVariable[]; - private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); + private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; name: string }); } /** @@ -56,14 +56,14 @@ declare module 'vscode' { /** * The name of the chat participant and contributing extension that this response came from. */ - readonly participant: { readonly extensionId: string; readonly participant: string }; + readonly participant: { readonly extensionId: string; readonly name: string }; /** * The name of the command that this response came from. */ readonly command?: string; - private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; participant: string }); + private constructor(response: ReadonlyArray, result: ChatResult, participant: { extensionId: string; name: string }); } export interface ChatContext { @@ -239,16 +239,10 @@ declare module 'vscode' { */ readonly name: string; - /** - * The full name of this participant. - * TODO@API This is only used for the default participant, but it seems useful, so should we keep it so we can use it in the future? - */ - fullName: string; - /** * A human-readable description explaining what this participant does. */ - description: string; + description?: string; /** * Icon for the participant shown in UI. diff --git a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts index e1c026a655740..07a2b6f5c4253 100644 --- a/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.defaultChatParticipant.d.ts @@ -18,6 +18,11 @@ declare module 'vscode' { */ isDefault?: boolean; + /** + * The full name of this participant. + */ + fullName?: string; + /** * When true, this participant is invoked when the user submits their query using ctrl/cmd+enter * TODO@API name From 006e4f9dd3f549590f71ecd1fd53b5369f927442 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 21 Feb 2024 21:14:10 +0000 Subject: [PATCH 0541/1863] Catch an error from agent (#205913) --- src/vs/workbench/api/common/extHostChatAgents2.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 9f9266babba68..8166c1e7d3397 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -253,8 +253,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } const convertedHistory = await this.prepareHistoryTurns(agent.id, context); - - return agent.provideSlashCommands({ history: convertedHistory }, token); + try { + return await agent.provideSlashCommands({ history: convertedHistory }, token); + } catch (err) { + const msg = toErrorMessage(err); + this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] Error while providing slash commands: ${msg}`); + return []; + } } async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise { From 4665ce61312f31f22607b5c72a4492b5ecaf7a98 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:04:09 -0600 Subject: [PATCH 0542/1863] quick search esc leads to closing edited files (#205917) Fixes #205907 --- src/vs/workbench/browser/quickaccess.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index b0a88a281426f..6b9e07abe7abc 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -84,7 +84,7 @@ export class EditorViewState { if (shouldCloseCurrEditor) { const activeEditorPane = this.editorService.activeEditorPane; const currEditor = activeEditorPane?.input; - if (currEditor && currEditor !== this._editorViewState.editor) { + if (currEditor && currEditor !== this._editorViewState.editor && activeEditorPane?.group.isPinned(currEditor) !== true) { await activeEditorPane.group.closeEditor(currEditor); } } From 91f71cdc378f4f41f29ef1b533d9d93529ac3203 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 21 Feb 2024 15:47:38 -0800 Subject: [PATCH 0543/1863] debug: bump js-debug 1.87 (#205919) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index b17eeda961047..60765ed5470cb 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.86.1", - "sha256": "e382de75b63a57d3419bbb110e17551d40d88b4bb0a49452dab2c7278b815e72", + "version": "1.87.0", + "sha256": "155ba715cc1045835951e70a663010c8f91d773d90a3ec504a041fd53b5658b0", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 5ca03d67eea4b9067d8ef4f97565b6270571e86a Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:18:30 -0800 Subject: [PATCH 0544/1863] console error when issue reporter fails (#205920) * throw some console errors here and there * error checking --- src/vs/code/electron-sandbox/issue/issueReporterService.ts | 5 +++++ .../services/issue/electron-sandbox/issueService.ts | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 6bfb60db1f70d..067f09fd0bd65 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -926,6 +926,7 @@ export class IssueReporter extends Disposable { const response = await fetch(url, init); if (!response.ok) { + console.error('Invalid GitHub URL provided.'); return false; } const result = await response.json(); @@ -984,6 +985,7 @@ export class IssueReporter extends Disposable { let issueUrl = hasUri ? this.getExtensionBugsUrl() : this.getIssueUrl(); if (!issueUrl) { + console.error('No issue url found'); return false; } @@ -1004,6 +1006,7 @@ export class IssueReporter extends Disposable { try { url = await this.writeToClipboard(baseUrl, issueBody); } catch (_) { + console.error('Writing to clipboard failed'); return false; } } @@ -1040,6 +1043,8 @@ export class IssueReporter extends Disposable { owner: match[1], repositoryName: match[2] }; + } else { + console.error('No GitHub match'); } return undefined; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index add5d98fc2e56..b1d728b506a23 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -185,6 +185,13 @@ export class NativeIssueService implements IWorkbenchIssueService { githubAccessToken }, dataOverrides); + if (issueReporterData.extensionId) { + const extensionExists = extensionData.some(extension => extension.id === issueReporterData.extensionId); + if (!extensionExists) { + console.error(`Extension with ID ${issueReporterData.extensionId} does not exist.`); + } + } + if (issueReporterData.extensionId && this.extensionIdentifierSet.has(issueReporterData.extensionId)) { ipcRenderer.send(`vscode:triggerReporterMenuResponse:${issueReporterData.extensionId}`, issueReporterData); this.extensionIdentifierSet.delete(new ExtensionIdentifier(issueReporterData.extensionId)); From a0b90ac5e04ea9a0733b68062253a469994e170e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 21 Feb 2024 16:25:42 -0800 Subject: [PATCH 0545/1863] Fix #205818. Fix race condition. (#205921) --- .../contrib/notebook/browser/view/cellParts/codeCell.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index a74983b6b5fae..2333273262864 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -137,7 +137,6 @@ export class CodeCell extends Disposable { this._register(Event.runAndSubscribe(viewCell.onDidChangeLayout, this.updateForLayout.bind(this))); this._cellEditorOptions.setLineNumbers(this.viewCell.lineNumbers); - this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(templateData))); templateData.editor.updateOptions(this._cellEditorOptions.getUpdatedValue(this.viewCell.internalMetadata, this.viewCell.uri)); } @@ -231,6 +230,8 @@ export class CodeCell extends Disposable { focusEditorIfNeeded(); } + + this._register(this._cellEditorOptions.onDidChange(() => this.updateCodeCellOptions(this.templateData))); }); } From eb278bd53bf8c7f67de33e8bd1a3a7725edfc010 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 22 Feb 2024 07:41:54 +0100 Subject: [PATCH 0546/1863] voice - add missing languages (#205935) --- .../accessibility/browser/accessibilityConfiguration.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 164dd8331d530..567bcc87a8dfa 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -717,6 +717,9 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen private getLanguages(): { [locale: string]: { name: string } } { return { + ['da-DK']: { + name: localize('speechLanguage.da-DK', "Danish (Denmark)") + }, ['de-DE']: { name: localize('speechLanguage.de-DE', "German (Germany)") }, @@ -768,6 +771,9 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen ['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)") }, From 801f96365665352513d6971d78557f75aa5d193c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 22 Feb 2024 09:32:21 +0100 Subject: [PATCH 0547/1863] prevent double discard of inline chat edits (#205945) maybe https://github.com/microsoft/vscode/issues/204592 --- .../workbench/contrib/inlineChat/browser/inlineChatSession.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 693fde05babfd..a3e3865631d3c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -723,7 +723,9 @@ export class HunkData { discardAll() { const edits: ISingleEditOperation[][] = []; for (const item of this.getInfo()) { - edits.push(this._discardEdits(item)); + if (item.getState() !== HunkState.Rejected) { + edits.push(this._discardEdits(item)); + } } const undoEdits: IValidEditOperation[][] = []; this._textModelN.pushEditOperations(null, edits.flat(), (_undoEdits) => { From e46c089ef43742a696924b4142d79628b865cbc1 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:45:00 +0100 Subject: [PATCH 0548/1863] Remove double hover in notebookKernelView.ts (#205948) remove double hover --- .../contrib/notebook/browser/viewParts/notebookKernelView.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 64bd903fae6a0..34958f1e7c0e9 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -166,7 +166,6 @@ export class NotebooKernelActionViewItem extends ActionViewItem { if (this._kernelLabel) { this._kernelLabel.classList.add('kernel-label'); this._kernelLabel.innerText = this._action.label; - this._kernelLabel.title = this._action.tooltip; } } From a6ba9af63c85385681c87c431d0ce4dc6c543313 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 22 Feb 2024 01:33:23 -0800 Subject: [PATCH 0549/1863] Add gap to extension `more info` table (#205923) * Add gap to extension `more info` table Fixes #165239 Switches to use a grid layout to simplify the code and also add a small gap between the label and content --- .../extensions/browser/media/extensionEditor.css | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index ce78e8c7f0276..5983168077029 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -494,15 +494,9 @@ .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry { font-size: 90%; - display: flex; -} - -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :first-child { - width: 40%; -} - -.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry :last-child { - width: 60%; + display: grid; + grid-template-columns: 40% 60%; + gap: 4px; } .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { From a4f9d3286675d83a390b1a92de3e5ea4aa0d4ab3 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 10:42:51 +0100 Subject: [PATCH 0550/1863] Add telemetry reporting to rename suggestions (#205869) rename suggestions: add telemetry reporting --- .../editor/contrib/rename/browser/rename.ts | 38 ++++++++++++++++++- .../rename/browser/renameInputField.ts | 24 +++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index fcf0db314c29f..13d42c42e28fc 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -36,7 +36,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField } from './renameInputField'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { CONTEXT_RENAME_INPUT_FOCUSED, CONTEXT_RENAME_INPUT_VISIBLE, RenameInputField, RenameInputFieldResult } from './renameInputField'; class RenameSkeleton { @@ -149,6 +150,7 @@ class RenameController implements IEditorContribution { @ILogService private readonly _logService: ILogService, @ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])); } @@ -240,6 +242,8 @@ class RenameController implements IEditorContribution { const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); trace('received response from rename input field'); + this._reportTelemetry(inputFieldResult); + // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { trace(`returning early - rename input field response - ${inputFieldResult}`); @@ -325,6 +329,38 @@ class RenameController implements IEditorContribution { focusPreviousRenameSuggestion(): void { this._renameInputField.focusPreviousRenameSuggestion(); } + + private _reportTelemetry(inputFieldResult: boolean | RenameInputFieldResult) { + type RenameInvokedEvent = + { + kind: 'accepted' | 'cancelled'; + /** provided only if kind = 'accepted' */ + wantsPreview?: boolean; + /** provided only if kind = 'accepted' */ + source?: RenameInputFieldResult['source']; + /** provided only if kind = 'accepted' */ + hadRenameSuggestions?: boolean; + }; + + type RenameInvokedClassification = { + owner: 'ulugbekna'; + comment: 'A rename operation was invoked.'; + kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the rename operation was cancelled or accepted.' }; + wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; + source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the new name came from the input field or rename suggestions.' }; + hadRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user had rename suggestions.'; isMeasurement: true }; + }; + + this._telemetryService.publicLog2( + 'renameInvokedEvent', + typeof inputFieldResult === 'boolean' ? { kind: 'cancelled' } : { + kind: 'accepted', + wantsPreview: inputFieldResult.wantsPreview, + source: inputFieldResult.source, + hadRenameSuggestions: inputFieldResult.hadRenameSuggestions, + } + ); + } } // ---- action implementation diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index cce562b9fb909..794ef418d9c64 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -49,6 +49,8 @@ export const CONTEXT_RENAME_INPUT_FOCUSED = new RawContextKey('renameIn export interface RenameInputFieldResult { newName: string; wantsPreview?: boolean; + source: 'inputField' | 'renameSuggestion'; + hadRenameSuggestions: boolean; } export class RenameInputField implements IContentWidget { @@ -298,7 +300,19 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const newName = this._candidatesView.focusedCandidate ?? this._input.value; + const hadRenameSuggestions = this._candidatesView.hasCandidates(); + + let newName: string; + let source: 'inputField' | 'renameSuggestion'; + if (this._candidatesView.focusedCandidate !== undefined) { + this._trace('using new name from renameSuggestion'); + newName = this._candidatesView.focusedCandidate; + source = 'renameSuggestion'; + } else { + this._trace('using new name from inputField'); + newName = this._input.value; + source = 'inputField'; + } if (newName === value || newName.trim().length === 0 /* is just whitespace */) { this.cancelInput(true, '_currentAcceptInput (because newName === value || newName.trim().length === 0)'); @@ -311,7 +325,9 @@ export class RenameInputField implements IContentWidget { resolve({ newName, - wantsPreview: supportPreview && wantsPreview + wantsPreview: supportPreview && wantsPreview, + source, + hadRenameSuggestions, }); }; @@ -513,6 +529,10 @@ class CandidatesView { this._listWidget.splice(0, this._listWidget.length, []); } + public hasCandidates() { + return this._listWidget.length > 0; + } + public get focusedCandidate(): string | undefined { if (this._listWidget.length === 0) { return; From d0e118cb72ead940697f3f84ff5cbe1a859f8fc7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 22 Feb 2024 10:52:02 +0100 Subject: [PATCH 0551/1863] fix #165239 (#205954) --- .../contrib/extensions/browser/media/extensionEditor.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 5983168077029..44f8b720581e7 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -496,7 +496,12 @@ font-size: 90%; display: grid; grid-template-columns: 40% 60%; - gap: 4px; + gap: 6px; + padding: 2px 4px; +} + +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry:nth-child(odd) { + background-color: rgba(130, 130, 130, 0.04); } .extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry code { From 30d4de8b06905dd0170ca40452c443261920da1f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 22 Feb 2024 11:10:05 +0100 Subject: [PATCH 0552/1863] adding on did resize call inside of the constructor --- .../contrib/stickyScroll/browser/stickyScrollController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index fac1f9d3d5189..492524e64b750 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -85,6 +85,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._register(this._stickyLineCandidateProvider); this._widgetState = new StickyScrollWidgetState([], [], 0); + this._onDidResize(); this._readConfiguration(); const stickyScrollDomNode = this._stickyScrollWidget.getDomNode(); this._register(this._editor.onDidChangeConfiguration(e => { From 6eec64e575f957402a2e2f042b603470c020cd58 Mon Sep 17 00:00:00 2001 From: Peter V Date: Thu, 22 Feb 2024 05:12:36 -0500 Subject: [PATCH 0553/1863] Fix `IRawGalleryExtension.shortDescription` can be undefined. (#202780) `IRawGalleryExtension.shortDescription` can be undefined, some extensions doesn't have a "shortDescription" like: "temakulakov.hackjb" --- .../extensionManagement/common/extensionGalleryService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 07195962b1c22..8833103e59c02 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -68,7 +68,7 @@ interface IRawGalleryExtension { readonly extensionId: string; readonly extensionName: string; readonly displayName: string; - readonly shortDescription: string; + readonly shortDescription?: string; readonly publisher: IRawGalleryExtensionPublisher; readonly versions: IRawGalleryExtensionVersion[]; readonly statistics: IRawGalleryExtensionStatistics[]; @@ -532,7 +532,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller publisherDisplayName: galleryExtension.publisher.displayName, publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined, publisherSponsorLink: getSponsorLink(latestVersion), - description: galleryExtension.shortDescription || '', + description: galleryExtension.shortDescription ?? '', installCount: getStatistic(galleryExtension.statistics, 'install'), rating: getStatistic(galleryExtension.statistics, 'averagerating'), ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), From 10171ed10f9439f19adee0ae94679979aa65da99 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 22 Feb 2024 11:28:33 +0100 Subject: [PATCH 0554/1863] Synchronizes in-editor scrolling back to virtual list scrolling. (#205959) Fixes revealing issues. --- .../diffEditorItemTemplate.ts | 35 ++++++++++++++++--- .../multiDiffEditorWidgetImpl.ts | 7 ++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts index 0f3353c66bc14..f5433ba99d774 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -24,6 +24,7 @@ import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActio export class TemplateData implements IObjectData { constructor( public readonly viewModel: DocumentDiffItemViewModel, + public readonly deltaScrollVertical: (delta: number) => void, ) { } @@ -116,15 +117,15 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< this._elements.editor.style.display = this._collapsed.read(reader) ? 'none' : 'block'; })); - this.editor.getModifiedEditor().onDidLayoutChange(e => { + this._register(this.editor.getModifiedEditor().onDidLayoutChange(e => { const width = this.editor.getModifiedEditor().getLayoutInfo().contentWidth; this._modifiedWidth.set(width, undefined); - }); + })); - this.editor.getOriginalEditor().onDidLayoutChange(e => { + this._register(this.editor.getOriginalEditor().onDidLayoutChange(e => { const width = this.editor.getOriginalEditor().getLayoutInfo().contentWidth; this._originalWidth.set(width, undefined); - }); + })); this._register(this.editor.onDidContentSizeChange(e => { globalTransaction(tx => { @@ -134,6 +135,18 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< }); })); + this._register(this.editor.getOriginalEditor().onDidScrollChange(e => { + if (this._isSettingScrollTop) { + return; + } + + if (!e.scrollTopChanged || !this._data) { + return; + } + const delta = e.scrollTop - this._lastScrollTop; + this._data.deltaScrollVertical(delta); + })); + this._register(autorun(reader => { const isFocused = this.isFocused.read(reader); this._elements.root.classList.toggle('focused', isFocused); @@ -162,7 +175,10 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _dataStore = new DisposableStore(); + private _data: TemplateData | undefined; + public setData(data: TemplateData): void { + this._data = data; function updateOptions(options: IDiffEditorOptions): IDiffEditorOptions { return { ...options, @@ -222,6 +238,9 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< private readonly _headerHeight = /*this._elements.header.clientHeight*/ 48; + private _lastScrollTop = -1; + private _isSettingScrollTop = false; + public render(verticalRange: OffsetRange, width: number, editorScroll: number, viewPort: OffsetRange): void { this._elements.root.style.visibility = 'visible'; this._elements.root.style.top = `${verticalRange.start}px`; @@ -240,7 +259,13 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject< height: verticalRange.length - this._outerEditorHeight, }); }); - this.editor.getOriginalEditor().setScrollTop(editorScroll); + try { + this._isSettingScrollTop = true; + this._lastScrollTop = editorScroll; + this.editor.getOriginalEditor().setScrollTop(editorScroll); + } finally { + this._isSettingScrollTop = false; + } this._elements.header.classList.toggle('shadow', delta > 0 || editorScroll > 0); this._elements.header.classList.toggle('collapsed', delta === maxDelta); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts index a913cdad35054..36a568f25241b 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -76,7 +76,9 @@ export class MultiDiffEditorWidgetImpl extends Disposable { } const items = vm.items.read(reader); return items.map(d => { - const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft)); + const item = store.add(new VirtualizedViewItem(d, this._objectPool, this.scrollLeft, delta => { + this._scrollableElement.setScrollPosition({ scrollTop: this._scrollableElement.getScrollPosition().scrollTop + delta }); + })); const data = this._lastDocStates?.[item.getKey()]; if (data) { transaction(tx => { @@ -344,6 +346,7 @@ class VirtualizedViewItem extends Disposable { public readonly viewModel: DocumentDiffItemViewModel, private readonly _objectPool: ObjectPool, private readonly _scrollLeft: IObservable, + private readonly _deltaScrollVertical: (delta: number) => void, ) { super(); @@ -434,7 +437,7 @@ class VirtualizedViewItem extends Disposable { let ref = this._templateRef.get(); if (!ref) { - ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel)); + ref = this._objectPool.getUnusedObj(new TemplateData(this.viewModel, this._deltaScrollVertical)); this._templateRef.set(ref, undefined); const selections = this.viewModel.lastTemplateData.get().selections; From 078596ba3bee4e1ba65e242b7bb4aa8e3f9aa2c0 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:49:20 +0100 Subject: [PATCH 0555/1863] Fix TreeView Mouse Hover Persistence (#205970) * fix treeview mouse hover persistence * fix persistence --- src/vs/workbench/browser/parts/views/treeView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index a46d7bc51d612..0245752c80987 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -62,7 +62,7 @@ import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme import { Extensions, ITreeItem, ITreeItemLabel, ITreeView, ITreeViewDataProvider, ITreeViewDescriptor, ITreeViewDragAndDropController, IViewBadge, IViewDescriptorService, IViewsRegistry, ResolvableTreeItem, TreeCommand, TreeItemCollapsibleState, TreeViewItemHandleArg, TreeViewPaneHandleArg, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IHoverService, WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { CodeDataTransfers, LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; import { toExternalVSDataTransfer } from 'vs/editor/browser/dnd'; @@ -73,7 +73,6 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export class TreeViewPane extends ViewPane { @@ -1104,9 +1103,10 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { From 5347f55550c2ae17e7e95d4781dccfdb97d509c8 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 22 Feb 2024 13:50:56 +0100 Subject: [PATCH 0556/1863] adding wip --- .../test/browser/stickyScroll.test.ts | 24 +++++++++++++++---- .../test/browser/config/testConfiguration.ts | 18 +++++++------- src/vs/editor/test/browser/testCodeEditor.ts | 19 ++++++++++++--- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts index 9f15b6ad6815f..04e852765caed 100644 --- a/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts +++ b/src/vs/editor/contrib/stickyScroll/test/browser/stickyScroll.test.ts @@ -137,7 +137,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection: serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection: serviceCollection }, async (editor, _viewModel, instantiationService) => { const languageService = instantiationService.get(ILanguageFeaturesService); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); @@ -162,7 +166,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, _viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); @@ -211,7 +219,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); @@ -305,7 +317,11 @@ suite('Sticky Scroll Tests', () => { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel' - }, serviceCollection + }, + envConfig: { + outerHeight: 500 + }, + serviceCollection }, async (editor, _viewModel, instantiationService) => { const stickyScrollController: StickyScrollController = editor.registerAndInstantiateContribution(StickyScrollController.ID, StickyScrollController); diff --git a/src/vs/editor/test/browser/config/testConfiguration.ts b/src/vs/editor/test/browser/config/testConfiguration.ts index 71f1c4d0e0cfd..b5d42908dedde 100644 --- a/src/vs/editor/test/browser/config/testConfiguration.ts +++ b/src/vs/editor/test/browser/config/testConfiguration.ts @@ -4,25 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { EditorConfiguration, IEnvConfiguration } from 'vs/editor/browser/config/editorConfiguration'; -import { EditorFontLigatures, EditorFontVariations, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { EditorFontLigatures, EditorFontVariations } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; +import { TestCodeEditorCreationOptions } from 'vs/editor/test/browser/testCodeEditor'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; export class TestConfiguration extends EditorConfiguration { - constructor(opts: IEditorOptions) { + constructor(opts: Readonly) { super(false, opts, null, new TestAccessibilityService()); } protected override _readEnvConfiguration(): IEnvConfiguration { + const envConfig = (this.getRawOptions() as TestCodeEditorCreationOptions).envConfig; return { - extraEditorClassName: '', - outerWidth: 100, - outerHeight: 100, - emptySelectionClipboard: true, - pixelRatio: 1, - accessibilitySupport: AccessibilitySupport.Unknown + extraEditorClassName: envConfig?.extraEditorClassName ?? '', + outerWidth: envConfig?.outerWidth ?? 100, + outerHeight: envConfig?.outerHeight ?? 100, + emptySelectionClipboard: envConfig?.emptySelectionClipboard ?? true, + pixelRatio: envConfig?.pixelRatio ?? 1, + accessibilitySupport: envConfig?.accessibilitySupport ?? AccessibilitySupport.Unknown }; } diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 507dbe2cc6f5f..b29e05c805364 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -5,7 +5,7 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; -import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { EditorConfiguration } from 'vs/editor/browser/config/editorConfiguration'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view'; @@ -30,7 +30,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEditorWorkerService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibilitySupport, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { TestClipboardService } from 'vs/platform/clipboard/test/common/testClipboardService'; @@ -68,7 +68,7 @@ export interface ITestCodeEditor extends IActiveCodeEditor { export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { //#region testing overrides - protected override _createConfiguration(isSimpleWidget: boolean, options: Readonly): EditorConfiguration { + protected override _createConfiguration(isSimpleWidget: boolean, options: Readonly): EditorConfiguration { return new TestConfiguration(options); } protected override _createView(viewModel: ViewModel): [View, boolean] { @@ -116,6 +116,10 @@ export interface TestCodeEditorCreationOptions extends editorOptions.IEditorOpti * Defaults to true. */ hasTextFocus?: boolean; + /** + * Env configuration + */ + envConfig?: ITestEnvConfiguration; } export interface TestCodeEditorInstantiationOptions extends TestCodeEditorCreationOptions { @@ -125,6 +129,15 @@ export interface TestCodeEditorInstantiationOptions extends TestCodeEditorCreati serviceCollection?: ServiceCollection; } +export interface ITestEnvConfiguration { + extraEditorClassName?: string; + outerWidth?: number; + outerHeight?: number; + emptySelectionClipboard?: boolean; + pixelRatio?: number; + accessibilitySupport?: AccessibilitySupport; +} + export function withTestCodeEditor(text: ITextModel | string | string[] | ITextBufferFactory, options: TestCodeEditorInstantiationOptions, callback: (editor: ITestCodeEditor, viewModel: ViewModel, instantiationService: TestInstantiationService) => void): void { return _withTestCodeEditor(text, options, callback); } From fa7b64426e10dd85be598048842bc5d47aed7a0b Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Thu, 22 Feb 2024 14:49:14 +0100 Subject: [PATCH 0557/1863] voice chat: Fix mic icon centering in all contexts (#205978) * voice chat: Fix mic icon centering in all contexts --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 79572 -> 79568 bytes .../actions/voiceChatActions.ts | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index b34cfdc245c6a94f79b0f22da57ca8fe64b9721d..373f406613faa7f8633273ad735116e0f7a5e212 100644 GIT binary patch delta 638 zcmccemgT}*mI)3#lhx-QWMELYVPN=jV4{yHv&B!9i7iS((tj8j7=IMx7nj_6|5}!T zQTEHk9R@5{|G)Cz_~yJy{S5m<_WvBr9LgM4Is9_eag1`D;<(B2o0FW=5~q945zdQT z+FbUzd~gkOt#Uo#rsdY;w$Gi#J;MEhhl)p;M~}xIPbN<*&mhk#&sm*EmR;hAhaxWRp^s2y|A9JePMUPCBm0P@I`b)T!=J?To!pR zN+PN#>RGf-^o$sVm}jvpu}X0QaZYg&admMg;`!qB;uGSRB$y4wrTWoBg)$~Kg}E0-!SDW6mkR&lH{qw)bul}c5@ zW*c?my83^N0u0;?{0zzr4CW?gMq;vzB5X=(rsAf?qUvmVjHbpSa*WoDMs^?;AER=d zqMjlU=?e)-X-f)=D2WP*Dv1b7YD?V^S5y@DmQYlb=v7lwV+6{ID2o7*q_!jj0|N^X zGcf#Plmgl%!=S)m0<_H(WZmRISrhXKo^nCELZzB#W_Kg<4z1A~KwLxsZ{hd+*bjxmnY9Je_Ba8htu=Jdch%6W-P zhsyz%Pp%QJHLj=Jblh6p4!EM3_8RA*vImh#lmx0$VZwc=z z?={|kd^CLeeD?V~@wM@d@J;hm@N4s1<*(wu!2el5SU_LEu|T;%pTL^>AfBMKphLkj z!7jlW!9BrGLRdmHLTW;8gbIZQg;s>F34Io35Y`uVAnabaRQR$8frze%OOZyADgP622uWCAuV5 zB?%a^5LX<}&+X;ac}rK_dar7uc|ARJf_|PLWp8 zhGL)Mn&NXM3?()tlS;0XDwH~uZYup&W>Ge&Y*X2Xa+&h7@+lP&6(=gQDj%^_sZ}Ly zwox~(tN+3%z`)JG&!Eh}U@j`crle-eXkun0#%^jXs?MgzXlg7X$7s!HWCvpLF)ANb z)K>%|Ct+bpZAl>!Wf5Tzmei5F;Vq%4DB&%xsQ6w@O^s1XTM{S%L_lT0q`(5iz!btL z1++_sL4m;pXq%~t9i!OfcT4p( { 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) { - outline: 2px solid ${activeRecordingColor}; border-radius: 50%; + outline: 2px solid ${activeRecordingColor}; animation: pulseAnimation 1500ms ease-in-out infinite !important; - padding-left: 4px; - height: 17px; } @keyframes pulseAnimation { 0% { outline-width: 2px; } - 50% { + 62% { outline-width: 5px; outline-color: ${activeRecordingDimmedColor}; } From 71b0a729fea632cc36b581abd788d5ab317dcf09 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 22 Feb 2024 14:03:19 +0000 Subject: [PATCH 0558/1863] Avoid losing chat input after accidentally navigating history (#205977) Fix microsoft/vscode-copilot-release#661 --- .../workbench/contrib/chat/browser/chatInputPart.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 04b6ffc381065..f469287859878 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -86,6 +86,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private historyNavigationBackwardsEnablement!: IContextKey; private historyNavigationForewardsEnablement!: IContextKey; private onHistoryEntry = false; + private inHistoryNavigation = false; private inputModel: ITextModel | undefined; private inputEditorHasText: IContextKey; private chatCursorAtTop: IContextKey; @@ -160,7 +161,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.onHistoryEntry = previous || this.history.current() !== null; aria.status(historyEntry.text); + + this.inHistoryNavigation = true; this.setValue(historyEntry.text); + this.inHistoryNavigation = false; + this._onDidLoadInputState.fire(historyEntry.state); if (previous) { this._inputEditor.setPosition({ lineNumber: 1, column: 1 }); @@ -269,6 +274,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const model = this._inputEditor.getModel(); const inputHasText = !!model && model.getValueLength() > 0; this.inputEditorHasText.set(inputHasText); + + // If the user is typing on a history entry, then reset the onHistoryEntry flag so that history navigation can be disabled + if (!this.inHistoryNavigation) { + this.onHistoryEntry = false; + } + if (!this.onHistoryEntry) { this.historyNavigationForewardsEnablement.set(!inputHasText); this.historyNavigationBackwardsEnablement.set(!inputHasText); From 0b5864063ccf09844f84bb4f0cfcf5aff7297e9b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 22 Feb 2024 15:12:44 +0100 Subject: [PATCH 0559/1863] voice - default keybinding for editor dictation (#205981) --- .../codeEditor/browser/dictation/editorDictation.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts index 67863783054b7..842953efd0dfb 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/dictation/editorDictation.ts @@ -16,7 +16,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorAction2, EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -41,7 +41,11 @@ export class EditorDictationStartAction extends EditorAction2 { title: localize2('startDictation', "Start Dictation in Editor"), category: VOICE_CATEGORY, precondition: ContextKeyExpr.and(HasSpeechProvider, EDITOR_DICTATION_IN_PROGRESS.toNegated(), EditorContextKeys.readOnly.toNegated()), - f1: true + f1: true, + keybinding: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyV, + weight: KeybindingWeight.WorkbenchContrib + } }); } From 4c57ecb33c6f2c9fec1d7853aaf8e8069b7ff474 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 14:48:29 +0100 Subject: [PATCH 0560/1863] rename suggestions: try no. 3 at fixing width to not hide parts of long suggested names --- .../rename/browser/renameInputField.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 794ef418d9c64..5b30d72e900c2 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight } from 'vs/base/browser/dom'; +import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -238,7 +238,10 @@ export class RenameInputField implements IContentWidget { totalHeightAvailable = this._nPxAvailableAbove; } - this._candidatesView!.layout({ height: totalHeightAvailable - labelHeight - inputBoxHeight }); + this._candidatesView!.layout({ + height: totalHeightAvailable - labelHeight - inputBoxHeight, + width: getTotalWidth(this._input!), + }); } @@ -429,6 +432,7 @@ class CandidatesView { private _lineHeight: number; private _availableHeight: number; + private _minimumWidth: number; private _disposables: DisposableStore; @@ -437,6 +441,7 @@ class CandidatesView { this._disposables = new DisposableStore(); this._availableHeight = 0; + this._minimumWidth = 0; this._lineHeight = opts.fontInfo.lineHeight; @@ -505,27 +510,31 @@ class CandidatesView { } // height - max height allowed by parent element - public layout({ height }: { height: number }): void { + public layout({ height, width }: { height: number; width: number }): void { this._availableHeight = height; - if (this._listWidget.length > 0) { // candidates have been set - this._listWidget.layout(this._pickListHeight(this._listWidget.length)); - } + this._minimumWidth = width; + this._listContainer.style.width = `${this._minimumWidth}px`; } public setCandidates(candidates: NewSymbolName[]): void { - const height = this._pickListHeight(candidates.length); + // insert candidates into list widget this._listWidget.splice(0, 0, candidates); - const width = Math.max(200, 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + this._pickListWidth(candidates)); // TODO@ulugbekna: approximate calc - clean this up + // adjust list widget layout + const height = this._pickListHeight(candidates.length); + const width = this._pickListWidth(candidates); + this._listWidget.layout(height, width); + // adjust list container layout this._listContainer.style.height = `${height}px`; this._listContainer.style.width = `${width}px`; } public clearCandidates(): void { this._listContainer.style.height = '0px'; + this._listContainer.style.width = '0px'; this._listWidget.splice(0, this._listWidget.length, []); } @@ -599,7 +608,13 @@ class CandidatesView { } private _pickListWidth(candidates: NewSymbolName[]): number { - return Math.ceil(Math.max(...candidates.map(c => c.newSymbolName.length)) * 7.2) /* approximate # of pixes taken by a single character */; + const APPROXIMATE_CHAR_WIDTH = 7.2; // 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, + 4 /* padding */ + 16 /* sparkle icon */ + 5 /* margin-left */ + longestCandidateWidth + 10 /* (possibly visible) scrollbar width */ // TODO@ulugbekna: approximate calc - clean this up + ); + return width; } } From fa6c148ea26f0e6e1644f04f8f0eac144527ee4c Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 14:55:28 +0100 Subject: [PATCH 0561/1863] rename suggestions: extract magic number into a var --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 5b30d72e900c2..93d0d23fdd8c8 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -603,7 +603,8 @@ class CandidatesView { private _pickListHeight(nCandidates: number) { const heightToFitAllCandidates = this._candidateViewHeight * nCandidates; - const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * 7 /* max # of candidates we want to show at once */); + const MAX_N_CANDIDATES = 7; // @ulugbekna: max # of candidates we want to show at once + const height = Math.min(heightToFitAllCandidates, this._availableHeight, this._candidateViewHeight * MAX_N_CANDIDATES); return height; } From 17f02a45a399235084bdcd55d2a7bfce16f71e8d Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 15:11:15 +0100 Subject: [PATCH 0562/1863] rename suggestions: notify when rename suggestions arrive --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 93d0d23fdd8c8..f7021dd689748 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, getClientArea, getDomNodePagePosition, getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -530,6 +531,8 @@ class CandidatesView { // adjust list container layout this._listContainer.style.height = `${height}px`; this._listContainer.style.width = `${width}px`; + + aria.status(localize('renameSuggestionsReceivedAria', "Received {0} rename suggestions", candidates.length)); } public clearCandidates(): void { From 0b82c6e374fc6bb1a6a1d3145e7cce90c85d7ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Thu, 22 Feb 2024 16:06:26 +0100 Subject: [PATCH 0563/1863] Inline edit - don't send reject callback on blur (#205976) --- .../contrib/inlineEdit/browser/inlineEditController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts index 81d6fdb9d54bc..8355ef312cff5 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/inlineEditController.ts @@ -123,7 +123,7 @@ export class InlineEditController extends Disposable { } this._currentRequestCts?.dispose(); this._currentRequestCts = undefined; - this.clear(); + this.clear(false); })); //Invoke provider on focus @@ -288,9 +288,9 @@ export class InlineEditController extends Disposable { this.editor.revealPositionInCenterIfOutsideViewport(position); } - public clear() { + public clear(sendRejection: boolean = true) { const edit = this._currentEdit.get()?.edit; - if (edit && edit?.rejected && !this._isAccepting) { + if (edit && edit?.rejected && !this._isAccepting && sendRejection) { this._commandService.executeCommand(edit.rejected.id, ...edit.rejected.arguments || []); } if (edit) { From 838a59f24088acf14e61ecef2639e7384c46ef60 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 22 Feb 2024 16:39:24 +0100 Subject: [PATCH 0564/1863] send `onLanguageModelAccess` activation event (#205995) https://github.com/microsoft/vscode/issues/205726 --- .../api/browser/mainThreadChatProvider.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadChatProvider.ts index dd76d12f105c6..4b7665abdb635 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadChatProvider.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -82,15 +81,20 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { } async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { + + const activate = this._extensionService.activateByEvent(`onLanguageModelAccess:${providerId}`); const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); - // TODO: This should use a real activation event. Perhaps following what authentication does. - for (let i = 0; i < 3; i++) { - if (metadata) { - return metadata; - } - await timeout(2000); + + if (metadata) { + return metadata; } - return undefined; + + await Promise.race([ + activate, + Event.toPromise(Event.filter(this._chatProviderService.onDidChangeProviders, e => Boolean(e.added?.includes(providerId)))) + ]); + + return this._chatProviderService.lookupChatResponseProvider(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { From 9c05ffbbbd6dab21dea4dba825276d5c97a2c0ff Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Thu, 22 Feb 2024 17:10:20 +0100 Subject: [PATCH 0565/1863] rename suggestions: include more telemetry --- .../editor/contrib/rename/browser/rename.ts | 45 +++++++++++++------ .../rename/browser/renameInputField.ts | 15 ++++--- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 13d42c42e28fc..8b7bc9b51b8bf 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -242,7 +242,9 @@ class RenameController implements IEditorContribution { const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts); trace('received response from rename input field'); - this._reportTelemetry(inputFieldResult); + if (newSymbolNamesProviders.length > 0) { // @ulugbekna: we're interested only in telemetry for rename suggestions currently + this._reportTelemetry(newSymbolNamesProviders.length, model.getLanguageId(), inputFieldResult); + } // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { @@ -330,36 +332,51 @@ class RenameController implements IEditorContribution { this._renameInputField.focusPreviousRenameSuggestion(); } - private _reportTelemetry(inputFieldResult: boolean | RenameInputFieldResult) { + private _reportTelemetry(nRenameSuggestionProviders: number, languageId: string, inputFieldResult: boolean | RenameInputFieldResult) { type RenameInvokedEvent = { kind: 'accepted' | 'cancelled'; - /** provided only if kind = 'accepted' */ - wantsPreview?: boolean; + languageId: string; + nRenameSuggestionProviders: number; + /** provided only if kind = 'accepted' */ source?: RenameInputFieldResult['source']; /** provided only if kind = 'accepted' */ - hadRenameSuggestions?: boolean; + nRenameSuggestions?: number; + /** provided only if kind = 'accepted' */ + wantsPreview?: boolean; }; type RenameInvokedClassification = { owner: 'ulugbekna'; comment: 'A rename operation was invoked.'; + kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the rename operation was cancelled or accepted.' }; - wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Document language ID.' }; + nRenameSuggestionProviders: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of rename providers for this document.'; isMeasurement: true }; + source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the new name came from the input field or rename suggestions.' }; - hadRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user had rename suggestions.'; isMeasurement: true }; + nRenameSuggestions?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of rename suggestions user has got'; isMeasurement: true }; + wantsPreview?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If user wanted preview.'; isMeasurement: true }; }; - this._telemetryService.publicLog2( - 'renameInvokedEvent', - typeof inputFieldResult === 'boolean' ? { kind: 'cancelled' } : { + const value: RenameInvokedEvent = typeof inputFieldResult === 'boolean' + ? { + kind: 'cancelled', + languageId, + nRenameSuggestionProviders, + } + : { kind: 'accepted', - wantsPreview: inputFieldResult.wantsPreview, + languageId, + nRenameSuggestionProviders, + source: inputFieldResult.source, - hadRenameSuggestions: inputFieldResult.hadRenameSuggestions, - } - ); + nRenameSuggestions: inputFieldResult.nRenameSuggestions, + wantsPreview: inputFieldResult.wantsPreview, + }; + + this._telemetryService.publicLog2('renameInvokedEvent', value); } } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index f7021dd689748..cf76d531a2a77 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -51,7 +51,7 @@ export interface RenameInputFieldResult { newName: string; wantsPreview?: boolean; source: 'inputField' | 'renameSuggestion'; - hadRenameSuggestions: boolean; + nRenameSuggestions: number; } export class RenameInputField implements IContentWidget { @@ -304,13 +304,14 @@ export class RenameInputField implements IContentWidget { assertType(this._input !== undefined); assertType(this._candidatesView !== undefined); - const hadRenameSuggestions = this._candidatesView.hasCandidates(); + const nRenameSuggestions = this._candidatesView.nCandidates; let newName: string; let source: 'inputField' | 'renameSuggestion'; - if (this._candidatesView.focusedCandidate !== undefined) { + const focusedCandidate = this._candidatesView.focusedCandidate; + if (focusedCandidate !== undefined) { this._trace('using new name from renameSuggestion'); - newName = this._candidatesView.focusedCandidate; + newName = focusedCandidate; source = 'renameSuggestion'; } else { this._trace('using new name from inputField'); @@ -331,7 +332,7 @@ export class RenameInputField implements IContentWidget { newName, wantsPreview: supportPreview && wantsPreview, source, - hadRenameSuggestions, + nRenameSuggestions, }); }; @@ -541,8 +542,8 @@ class CandidatesView { this._listWidget.splice(0, this._listWidget.length, []); } - public hasCandidates() { - return this._listWidget.length > 0; + public get nCandidates() { + return this._listWidget.length; } public get focusedCandidate(): string | undefined { From a6671b6ce40a75438455b10e0d888d49203bfc55 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 10:35:48 -0800 Subject: [PATCH 0566/1863] fix #205964 --- .../accessibility/browser/accessibilityConfiguration.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 567bcc87a8dfa..1011f2b8743bf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -834,6 +834,7 @@ Registry.as(WorkbenchExtensions.ConfigurationMi announcement = announcement ? 'auto' : 'off'; } } + configurationKeyValuePairs.push([`${item.legacySoundSettingsKey}`, { value: undefined }]); configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); return configurationKeyValuePairs; } @@ -844,11 +845,13 @@ Registry.as(WorkbenchExtensions.ConfigurationMi key: item.legacyAnnouncementSettingsKey!, migrateFn: (announcement, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - const sound = accessor(item.legacySoundSettingsKey); + const sound = accessor(item.settingsKey)?.sound || accessor(item.legacySoundSettingsKey); if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; } configurationKeyValuePairs.push([`${item.settingsKey}`, { value: announcement !== undefined ? { announcement, sound } : { sound } }]); + configurationKeyValuePairs.push([`${item.legacyAnnouncementSettingsKey}`, { value: undefined }]); + configurationKeyValuePairs.push([`${item.legacySoundSettingsKey}`, { value: undefined }]); return configurationKeyValuePairs; } }))); From f5583237a0caba961e61bcf8075d23c85da971e0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 10:52:27 -0800 Subject: [PATCH 0567/1863] tweak tooltip description --- .../contrib/accessibilitySignals/browser/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts index 8561b125cd0a9..329cabe4bf38f 100644 --- a/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts +++ b/src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts @@ -40,7 +40,7 @@ export class ShowSignalSoundHelp extends Action2 { signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('sounds.help.settings', 'Enable/Disable Sound'), + tooltip: localize('sounds.help.settings', 'Configure Sound'), alwaysVisible: true }] : [] })); @@ -108,7 +108,7 @@ export class ShowAccessibilityAnnouncementHelp extends Action2 { signal, buttons: userGestureSignals.includes(signal) ? [{ iconClass: ThemeIcon.asClassName(Codicon.settingsGear), - tooltip: localize('announcement.help.settings', 'Enable/Disable Announcement'), + tooltip: localize('announcement.help.settings', 'Configure Announcement'), alwaysVisible: true, }] : [] })); From 96d451b00e77cf7bab89b1956142320bc525bbae Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 22 Feb 2024 11:10:55 -0800 Subject: [PATCH 0568/1863] Fix #205987. (#206012) --- .../controller/chat/notebookChatController.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index b22b51850d6b9..6181098363282 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -177,6 +177,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito private _sessionCtor: CancelablePromise | undefined; private _activeSession?: Session; private _warmupRequestCts?: CancellationTokenSource; + private _activeRequestCts?: CancellationTokenSource; private readonly _ctxHasActiveRequest: IContextKey; private readonly _ctxCellWidgetFocused: IContextKey; private readonly _ctxUserDidEdit: IContextKey; @@ -397,8 +398,9 @@ export class NotebookChatController extends Disposable implements INotebookEdito } async acceptInput() { - assertType(this._activeSession); assertType(this._widget); + await this._sessionCtor; + assertType(this._activeSession); this._warmupRequestCts?.dispose(true); this._warmupRequestCts = undefined; this._activeSession.addInput(new SessionPrompt(this._widget.inlineChatWidget.value)); @@ -449,18 +451,19 @@ export class NotebookChatController extends Disposable implements INotebookEdito //TODO: update progress in a newly inserted cell below the widget instead of the fake editor - const requestCts = new CancellationTokenSource(); + this._activeRequestCts?.cancel(); + this._activeRequestCts = new CancellationTokenSource(); const progressEdits: TextEdit[][] = []; const progressiveEditsQueue = new Queue(); const progressiveEditsClock = StopWatch.create(); const progressiveEditsAvgDuration = new MovingAverage(); - const progressiveEditsCts = new CancellationTokenSource(requestCts.token); + const progressiveEditsCts = new CancellationTokenSource(this._activeRequestCts.token); let progressiveChatResponse: IInlineChatMessageAppender | undefined; const progress = new AsyncProgress(async data => { // console.log('received chunk', data, request); - if (requestCts.token.isCancellationRequested) { + if (this._activeRequestCts?.token.isCancellationRequested) { return; } @@ -502,7 +505,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito } }); - const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, requestCts.token); + const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, progress, this._activeRequestCts.token); let response: ReplyResponse | ErrorResponse | EmptyResponse; try { @@ -512,7 +515,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget?.inlineChatWidget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : ''); this._ctxHasActiveRequest.set(true); - const reply = await raceCancellationError(Promise.resolve(task), requestCts.token); + const reply = await raceCancellationError(Promise.resolve(task), this._activeRequestCts.token); if (progressiveEditsQueue.size > 0) { // we must wait for all edits that came in via progress to complete await Event.toPromise(progressiveEditsQueue.onDrained); @@ -575,7 +578,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._widget?.inlineChatWidget.updateInfo(''); this._widget?.inlineChatWidget.updateToolbar(true); - this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); + this._activeSession?.addExchange(new SessionExchange(this._activeSession.lastInput, response)); this._ctxLastResponseType.set(response instanceof ReplyResponse ? response.raw.type : undefined); } @@ -762,15 +765,12 @@ export class NotebookChatController extends Disposable implements INotebookEdito this._strategy?.cancel(); } - if (this._activeSession) { - this._inlineChatSessionService.releaseSession(this._activeSession); - } - - this._activeSession = undefined; + this._activeRequestCts?.cancel(); } discard() { this._strategy?.cancel(); + this._activeRequestCts?.cancel(); this._widget?.discardChange(); this.dismiss(); } From a85dd8c427b6030d45b2ce5ba8b1965a565f3f8b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 11:32:25 -0800 Subject: [PATCH 0569/1863] fix #205974 --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/common/terminalConfiguration.ts | 25 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index a0b94d2a7f755..2a2df0a7a448a 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -79,6 +79,7 @@ export const enum TerminalSettingId { ConfirmOnExit = 'terminal.integrated.confirmOnExit', ConfirmOnKill = 'terminal.integrated.confirmOnKill', EnableBell = 'terminal.integrated.enableBell', + EnableVisualBell = 'terminal.integrated.enableVisualBell', CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell', AllowChords = 'terminal.integrated.allowChords', AllowMnemonics = 'terminal.integrated.allowMnemonics', diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8b11cc8d9e87a..84bde0d8006d0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -754,7 +754,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // starts up or reconnects disposableTimeout(() => { this._register(xterm.raw.onBell(() => { - if (this._configHelper.config.enableBell) { + if (this._configurationService.getValue(TerminalSettingId.EnableBell) || this._configurationService.getValue(TerminalSettingId.EnableVisualBell)) { this.statusList.add({ id: TerminalStatus.Bell, severity: Severity.Warning, diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ac0903b8ae3eb..4557474f63ee1 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -12,6 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Codicon } from 'vs/base/common/codicons'; import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import product from 'vs/platform/product/common/product'; +import { Extensions as WorkbenchExtensions, IConfigurationMigrationRegistry, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; const terminalDescriptors = '\n- ' + [ '`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"), @@ -364,7 +365,12 @@ const terminalConfiguration: IConfigurationNode = { default: 'editor' }, [TerminalSettingId.EnableBell]: { - description: localize('terminal.integrated.enableBell', "Controls whether the terminal bell is enabled. This shows up as a visual bell next to the terminal's name."), + markdownDeprecationMessage: localize('terminal.integrated.enableBell', "This is now deprecated. Instead use the `terminal.integrated.enableVisualBell` and `accessibility.signals.terminalBell` settings."), + type: 'boolean', + default: false + }, + [TerminalSettingId.EnableVisualBell]: { + description: localize('terminal.integrated.enableVisualBell', "Controls whether the visual terminal bell is enabled. This shows up next to the terminal's name."), type: 'boolean', default: false }, @@ -660,3 +666,20 @@ export function registerTerminalConfiguration() { const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration(terminalConfiguration); } + +Registry.as(WorkbenchExtensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: TerminalSettingId.EnableBell, + migrateFn: (enableBell, accessor) => { + const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; + configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); + let announcement = accessor('accessibility.signals.terminalBell')?.announcement ?? accessor('accessibility.alert.terminalBell'); + if (announcement !== undefined && typeof announcement !== 'string') { + announcement = announcement ? 'auto' : 'off'; + } + configurationKeyValuePairs.push(['accessibility.signals.terminalBell', { value: { sound: enableBell ? 'on' : 'off', announcement } }]); + configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); + configurationKeyValuePairs.push([TerminalSettingId.EnableVisualBell, { value: enableBell }]); + return configurationKeyValuePairs; + } + }]); From 4e291018c1fb79a0ebe7f19b521b665ea7af4dea Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 12:39:00 -0800 Subject: [PATCH 0570/1863] fix #206019 --- .../contrib/accessibility/browser/accessibilityConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 1011f2b8743bf..9a42093991cdb 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -546,7 +546,7 @@ const configuration: IConfigurationNode = { }, 'accessibility.signals.clear': { ...signalFeatureBase, - 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel). When this is disabled, an ARIA alert will announce 'Cleared'."), + 'description': localize('accessibility.signals.clear', "Plays a signal when a feature is cleared (for example, the terminal, Debug Console, or Output channel)."), 'properties': { 'sound': { 'description': localize('accessibility.signals.clear.sound', "Plays a sound when a feature is cleared."), From 9e9b9fd43025f08ee637b8b8425c414cf75ac004 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 22 Feb 2024 13:22:04 -0800 Subject: [PATCH 0571/1863] rm duplicated line --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 4557474f63ee1..0df9dc1171c47 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -672,7 +672,6 @@ Registry.as(WorkbenchExtensions.ConfigurationMi key: TerminalSettingId.EnableBell, migrateFn: (enableBell, accessor) => { const configurationKeyValuePairs: ConfigurationKeyValuePairs = []; - configurationKeyValuePairs.push([TerminalSettingId.EnableBell, { value: undefined }]); let announcement = accessor('accessibility.signals.terminalBell')?.announcement ?? accessor('accessibility.alert.terminalBell'); if (announcement !== undefined && typeof announcement !== 'string') { announcement = announcement ? 'auto' : 'off'; From 709da0f7b2d481e82f6fd634d74a581979b7e1d8 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 09:39:09 +0100 Subject: [PATCH 0572/1863] setting default enablement state to true --- src/vs/editor/common/config/editorOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f0b1c9e4f8a69..bd51c9ec27dec 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2811,7 +2811,7 @@ export type EditorStickyScrollOptions = Readonly { constructor() { - const defaults: EditorStickyScrollOptions = { enabled: false, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; + const defaults: EditorStickyScrollOptions = { enabled: true, maxLineCount: 5, defaultModel: 'outlineModel', scrollWithEditor: true }; super( EditorOption.stickyScroll, 'stickyScroll', defaults, { From 63d967f8c8251c95cf4d14a780b7ca320b007acd Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:48:12 +0100 Subject: [PATCH 0573/1863] Fix tooltips in quick pick actions (#206043) fix #206039 --- src/vs/platform/quickinput/browser/quickInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index f23f8447a02b4..96dfeee7ea8cb 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1264,7 +1264,7 @@ export class QuickInputHoverDelegate extends WorkbenchHoverDelegate { @IConfigurationService configurationService: IConfigurationService, @IHoverService hoverService: IHoverService ) { - super('mouse', true, (options) => this.getOverrideOptions(options), configurationService, hoverService); + super('element', false, (options) => this.getOverrideOptions(options), configurationService, hoverService); } private getOverrideOptions(options: IHoverDelegateOptions): Partial { From 0f282e2a59809a32606fb6f2720c82a034c0856e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= Date: Fri, 23 Feb 2024 11:16:40 +0100 Subject: [PATCH 0574/1863] Fix fullscreen container dimension detection when not directly on body (#205884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix container dimension detection when not directly on body * Add some comments Co-authored-by: Benjamin Pasero --------- Co-authored-by: Loïc Mangeonjean Co-authored-by: Benjamin Pasero --- src/vs/workbench/browser/layout.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a2b6007fb797c..797a1713dd805 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1541,7 +1541,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi layout(): void { if (!this.disposed) { - this._mainContainerDimension = getClientArea(this.parent); + this._mainContainerDimension = getClientArea(this.state.runtime.mainWindowFullscreen ? + mainWindow.document.body : // in fullscreen mode, make sure to use element because + this.parent // in that case the workbench will span the entire site + ); this.logService.trace(`Layout#layout, height: ${this._mainContainerDimension.height}, width: ${this._mainContainerDimension.width}`); position(this.mainContainer, 0, 0, 0, 0, 'relative'); From b60b4d70b7a808a5fcf7b14ba8b9be179ffc9fc6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 11:20:48 +0100 Subject: [PATCH 0575/1863] doing checks on length and line number --- .../browser/inlineCompletionsModel.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 0be9f26bde91a..a35ce5f910854 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -421,6 +421,9 @@ export class InlineCompletionsModel extends Disposable { const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); const positions = this._positions.get(); + if (positions.length === 0) { + return; + } const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. @@ -470,6 +473,10 @@ export class InlineCompletionsModel extends Disposable { } export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] { + if (positions.length === 1) { + // No secondary cursor positions + return []; + } const primaryPosition = positions[0]; const secondaryPositions = positions.slice(1); const primaryEditStartPosition = primaryEdit.range.getStartPosition(); @@ -478,6 +485,9 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos Range.fromPositions(primaryPosition, primaryEditEndPosition) ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); + if (positionWithinTextEdit.lineNumber < 1) { + return []; + } const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); return secondaryPositions.map(pos => { const posEnd = addPositions(subtractPositions(pos, primaryEditStartPosition), primaryEditEndPosition); From 3658a5124f2b9d58cefd62f1826e4e180f7c337d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:33:33 +0100 Subject: [PATCH 0576/1863] More hover adoptions and debt (#205972) * Hover Fixes * Updated hover behavior and accessibility attributes across multiple components --- .../ui/dropdown/dropdownActionViewItem.ts | 2 +- src/vs/base/browser/ui/hover/hoverDelegate.ts | 2 +- .../browser/ui/iconLabel/iconLabelHover.ts | 28 ++++++++-- src/vs/platform/hover/browser/hover.ts | 10 ++-- .../browser/parts/compositeBarActions.ts | 4 +- .../workbench/browser/parts/compositePart.ts | 10 ++-- .../notifications/notificationsViewer.ts | 28 +++++----- .../browser/parts/paneCompositePart.ts | 2 +- .../workbench/browser/parts/views/viewPane.ts | 10 ++-- .../contrib/comments/browser/commentReply.ts | 4 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/timestamp.ts | 9 ++-- .../contrib/debug/browser/baseDebugView.ts | 7 +-- .../contrib/debug/browser/breakpointsView.ts | 29 +++++----- .../contrib/debug/browser/callStackView.ts | 29 ++++++---- .../debug/browser/debugActionViewItems.ts | 7 ++- .../contrib/debug/browser/replViewer.ts | 4 +- .../files/browser/views/explorerView.ts | 2 +- .../notebook/browser/notebookEditor.ts | 5 +- .../browser/view/cellParts/cellActionView.ts | 5 +- .../browser/view/cellParts/cellToolbars.ts | 14 +++-- .../viewParts/notebookEditorToolbar.ts | 53 +++++++++++-------- .../browser/viewParts/notebookKernelView.ts | 5 +- .../contrib/remote/browser/tunnelView.ts | 2 +- 24 files changed, 172 insertions(+), 103 deletions(-) diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 419658a21bbc4..995b8f4081783 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -93,7 +93,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.element.setAttribute('aria-haspopup', 'true'); this.element.setAttribute('aria-expanded', 'false'); if (this._action.label) { - this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, this._action.label)); + this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.element, this._action.label)); } this.element.ariaLabel = this._action.label || ''; diff --git a/src/vs/base/browser/ui/hover/hoverDelegate.ts b/src/vs/base/browser/ui/hover/hoverDelegate.ts index 6682d739c269d..6d2dfef371aed 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate.ts @@ -21,7 +21,7 @@ export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mou } export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate; -export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover: true): IScopedHoverDelegate; +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 diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 20ad4662b0d9c..bdcdfa7c7da07 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -163,9 +163,24 @@ class UpdatableHoverWidget implements IDisposable { } } +function getHoverTargetElement(element: HTMLElement, stopElement?: HTMLElement): HTMLElement { + stopElement = stopElement ?? dom.getWindow(element).document.body; + while (!element.hasAttribute('custom-hover') && element !== stopElement) { + element = element.parentElement!; + } + return element; +} + export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContentOrFactory, options?: IUpdatableHoverOptions): ICustomHover { - let hoverPreparation: IDisposable | undefined; + htmlElement.setAttribute('custom-hover', 'true'); + + if (htmlElement.title !== '') { + console.warn('HTML element already has a title attribute, which will conflict with the custom hover. Please remove the title attribute.'); + console.trace('Stack trace:', htmlElement.title); + htmlElement.title = ''; + } + let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; const hideHover = (disposeWidget: boolean, disposePreparation: boolean) => { @@ -206,7 +221,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hideHover(false, (e).fromElement === htmlElement); }, true); - const onMouseOver = () => { + const onMouseOver = (e: MouseEvent) => { if (hoverPreparation) { return; } @@ -221,15 +236,20 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM // track the mouse position const onMouseMove = (e: MouseEvent) => { target.x = e.x + 10; - if ((e.target instanceof HTMLElement) && e.target.classList.contains('action-label')) { + if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target, htmlElement) !== htmlElement) { hideHover(true, true); } }; toDispose.add(dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_MOVE, onMouseMove, true)); } - toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); hoverPreparation = toDispose; + + if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target as HTMLElement, htmlElement) !== htmlElement) { + return; // Do not show hover when the mouse is over another hover target + } + + toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); }; const mouseOverDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_OVER, onMouseOver, true); diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index c9edff5d2a315..82d9574ca0615 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -282,14 +282,18 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate return this.hoverService.showHover({ ...options, persistence: { - hideOnHover: true + hideOnHover: true, + hideOnKeyDown: true, }, ...overrideOptions }, focus); } - setOptions(options: Partial | ((options: IHoverDelegateOptions, focus?: boolean) => Partial)): void { - this.overrideOptions = options; + setInstantHoverTimeLimit(timeLimit: number): void { + if (!this.instantHover) { + throw new Error('Instant hover is not enabled'); + } + this.timeLimit = timeLimit; } onDidHideHover(): void { diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index fb06c28c67d35..cfa2d348aa006 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -706,12 +706,12 @@ export class CompositeActionViewItem extends CompositeBarActionViewItem { protected override updateChecked(): void { if (this.action.checked) { this.container.classList.add('checked'); - this.container.setAttribute('aria-label', this.container.title); + this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title); this.container.setAttribute('aria-expanded', 'true'); this.container.setAttribute('aria-selected', 'true'); } else { this.container.classList.remove('checked'); - this.container.setAttribute('aria-label', this.container.title); + this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title); this.container.setAttribute('aria-expanded', 'false'); this.container.setAttribute('aria-selected', 'false'); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index b4d406a636487..81a034ffa918c 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -35,6 +35,7 @@ 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'; export interface ICompositeTitleLabel { @@ -62,7 +63,7 @@ export abstract class CompositePart extends Part { protected toolBar: WorkbenchToolBar | undefined; protected titleLabelElement: HTMLElement | undefined; - protected readonly hoverDelegate: IHoverDelegate; + protected readonly toolbarHoverDelegate: IHoverDelegate; private readonly mapCompositeToCompositeContainer = new Map(); private readonly mapActionsBindingToComposite = new Map void>(); @@ -96,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.hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.toolbarHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); } protected openComposite(id: string, focus?: boolean): Composite | undefined { @@ -407,7 +408,7 @@ export abstract class CompositePart extends Part { anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('viewsAndMoreActions', "Views and More Actions..."), telemetrySource: this.nameForTelemetry, - hoverDelegate: this.hoverDelegate + hoverDelegate: this.toolbarHoverDelegate })); this.collectCompositeActions()(); @@ -419,6 +420,7 @@ export abstract class CompositePart extends Part { const titleContainer = append(parent, $('.title-label')); const titleLabel = append(titleContainer, $('h2')); this.titleLabelElement = titleLabel; + const hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), titleLabel, '')); const $this = this; return { @@ -426,7 +428,7 @@ export abstract class CompositePart extends Part { // The title label is shared for all composites in the base CompositePart if (!this.activeComposite || this.activeComposite.getId() === id) { titleLabel.innerText = title; - titleLabel.title = keybinding ? localize('titleTooltip', "{0} ({1})", title, keybinding) : title; + hover.update(keybinding ? localize('titleTooltip', "{0} ({1})", title, keybinding) : title); } }, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 484ee51adb4cb..e66dc53ad6105 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -29,6 +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'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -235,7 +237,7 @@ export class NotificationRenderer implements IListRenderer { + actionViewItemProvider: (action, options) => { if (action instanceof ConfigureNotificationAction) { return data.toDispose.add(new DropdownMenuActionViewItem(action, { getActions() { @@ -262,6 +264,7 @@ export class NotificationRenderer implements IListRenderer this.openerService.open(URI.parse(link), { allowCommands: true }), @@ -425,11 +430,8 @@ export class NotificationTemplateRenderer extends Disposable { })); const messageOverflows = notification.canCollapse && !notification.expanded && this.template.message.scrollWidth > this.template.message.clientWidth; - if (messageOverflows) { - this.template.message.title = this.template.message.textContent + ''; - } else { - this.template.message.removeAttribute('title'); - } + + customHover.update(messageOverflows ? this.template.message.textContent + '' : ''); return messageOverflows; } @@ -470,13 +472,13 @@ export class NotificationTemplateRenderer extends Disposable { actions.forEach(action => this.template.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) })); } - private renderSource(notification: INotificationViewItem): void { + private renderSource(notification: INotificationViewItem, sourceCustomHover: ICustomHover): void { if (notification.expanded && notification.source) { this.template.source.textContent = localize('notificationSource', "Source: {0}", notification.source); - this.template.source.title = notification.source; + sourceCustomHover.update(notification.source); } else { this.template.source.textContent = ''; - this.template.source.removeAttribute('title'); + sourceCustomHover.update(''); } } diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 6ef4dfe09a3d6..3a1cb5382e4ef 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -307,7 +307,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), toggleMenuTitle: localize('moreActions', "More Actions..."), - hoverDelegate: this.hoverDelegate + hoverDelegate: this.toolbarHoverDelegate })); this.updateGlobalToolbarActions(); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 978baf437cd54..9cc1b35c3545e 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -47,6 +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'; export enum ViewPaneShowActions { /** Show the actions when the view is hovered. This is the default behavior. */ @@ -342,6 +344,7 @@ export abstract class ViewPane extends Pane implements IView { private titleContainer?: HTMLElement; private titleDescriptionContainer?: HTMLElement; private iconContainer?: HTMLElement; + private iconContainerHover?: ICustomHover; protected twistiesContainer?: HTMLElement; private viewWelcomeController!: ViewWelcomeController; @@ -519,13 +522,14 @@ export abstract class ViewPane extends Pane implements IView { } const calculatedTitle = this.calculateTitle(title); - this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); + this.titleContainer = append(container, $('h3.title', {}, calculatedTitle)); + setupCustomHover(getDefaultHoverDelegate('mouse'), this.titleContainer, calculatedTitle); if (this._titleDescription) { this.setTitleDescription(this._titleDescription); } - this.iconContainer.title = calculatedTitle; + this.iconContainerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle)); this.iconContainer.setAttribute('aria-label', calculatedTitle); } @@ -537,7 +541,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.iconContainer) { - this.iconContainer.title = calculatedTitle; + this.iconContainerHover?.update(calculatedTitle); this.iconContainer.setAttribute('aria-label', calculatedTitle); } diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 21aeb3f22d5a4..4d2a9e4b887f1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -30,6 +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'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; @@ -355,7 +357,7 @@ export class CommentReply extends Disposable { private createReplyButton(commentEditor: ICodeEditor, commentForm: HTMLElement) { this._reviewThreadReplyButton = dom.append(commentForm, dom.$(`button.review-thread-reply-button.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`)); - this._reviewThreadReplyButton.title = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._reviewThreadReplyButton, this._commentOptions?.prompt || nls.localize('reply', "Reply..."))); this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 69ffc3d3f6d8a..585d253442c18 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -32,6 +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'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; @@ -219,7 +221,7 @@ export class CommentNodeRenderer implements IListRenderer const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables); templateData.disposables.push(renderedComment); templateData.threadMetadata.commentPreview.appendChild(renderedComment.element.firstElementChild ?? renderedComment.element); - templateData.threadMetadata.commentPreview.title = renderedComment.element.textContent ?? ''; + templateData.disposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.threadMetadata.commentPreview, renderedComment.element.textContent ?? '')); } if (node.element.range) { diff --git a/src/vs/workbench/contrib/comments/browser/timestamp.ts b/src/vs/workbench/contrib/comments/browser/timestamp.ts index 2b9f79d4a8802..2d1fcf15b4895 100644 --- a/src/vs/workbench/contrib/comments/browser/timestamp.ts +++ b/src/vs/workbench/contrib/comments/browser/timestamp.ts @@ -4,6 +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 { fromNow } from 'vs/base/common/date'; import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; @@ -15,12 +17,15 @@ export class TimestampWidget extends Disposable { private _timestamp: Date | undefined; private _useRelativeTime: boolean; + private hover: ICustomHover; + constructor(private configurationService: IConfigurationService, container: HTMLElement, timeStamp?: Date) { super(); this._date = dom.append(container, dom.$('span.timestamp')); this._date.style.display = 'none'; this._useRelativeTime = this.useRelativeTimeSetting; this.setTimestamp(timeStamp); + this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._date, '')); } private get useRelativeTimeSetting(): boolean { @@ -52,9 +57,7 @@ export class TimestampWidget extends Disposable { } this._date.textContent = textContent; - if (tooltip) { - this._date.title = tooltip; - } + this.hover.update(tooltip ?? ''); } } diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index ac626b82f408e..90aba19a7f2bf 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -7,6 +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 { 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'; @@ -187,19 +189,18 @@ export abstract class AbstractExpressionsRenderer implements IT abstract get templateId(): string; renderTemplate(container: HTMLElement): IExpressionTemplateData { + const templateDisposable = new DisposableStore(); const expression = dom.append(container, $('.expression')); const name = dom.append(expression, $('span.name')); const lazyButton = dom.append(expression, $('span.lazy-button')); lazyButton.classList.add(...ThemeIcon.asClassNameArray(Codicon.eye)); - lazyButton.title = localize('debug.lazyButton.tooltip', "Click to expand"); + 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 inputBoxContainer = dom.append(expression, $('.inputBoxContainer')); - const templateDisposable = new DisposableStore(); - let actionBar: ActionBar | undefined; if (this.renderActionBar) { dom.append(expression, $('.span.actionbar-spacer')); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index e60c8079a1418..5de52259df4a1 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -8,7 +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 { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; 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'; @@ -549,7 +551,7 @@ class BreakpointsRenderer implements IListRenderer t.stopped); @@ -603,11 +608,11 @@ class SessionsRenderer implements ICompressibleTreeRenderer, _index: number, data: IThreadTemplateData): void { const thread = element.element; - data.thread.title = thread.name; + data.elementDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.thread, thread.name)); data.label.set(thread.name, createMatches(element.filterData)); data.stateLabel.textContent = thread.stateLabel; data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception'); @@ -743,10 +748,12 @@ class StackFramesRenderer implements ICompressibleTreeRenderer, index: number, data: IErrorTemplateData): void { const error = element.element; data.label.textContent = error; - data.label.title = error; + data.templateDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.label, error)); } renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: IErrorTemplateData, height: number | undefined): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 433a562e4bfed..2f31d9c5aaf5f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -22,6 +22,8 @@ import { BaseActionViewItem, IBaseActionViewItemOptions, SelectActionViewItem } import { debugStart } from 'vs/workbench/contrib/debug/browser/debugIcons'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const $ = dom.$; @@ -74,9 +76,10 @@ export class StartDebugActionViewItem extends BaseActionViewItem { this.start = dom.append(container, $(ThemeIcon.asCSSSelector(debugStart))); const keybinding = this.keybindingService.lookupKeybinding(this.action.id)?.getLabel(); const keybindingLabel = keybinding ? ` (${keybinding})` : ''; - this.start.title = this.action.label + keybindingLabel; + const title = this.action.label + keybindingLabel; + this.toDispose.push(setupCustomHover(getDefaultHoverDelegate('mouse'), this.start, title)); this.start.setAttribute('role', 'button'); - this.start.ariaLabel = this.start.title; + this.start.ariaLabel = title; this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { this.start.blur(); diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 1ba443a95f18a..64c3f3d6ccda1 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -28,6 +28,8 @@ import { IDebugConfiguration, IDebugService, IDebugSession, IExpression, IExpres import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplOutputElement, ReplVariableElement } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; const $ = dom.$; @@ -199,7 +201,7 @@ export class ReplOutputElementRenderer implements ITreeRenderer element.sourceData; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 71960988d832a..d7e695e195822 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -276,7 +276,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { const workspace = this.contextService.getWorkspace(); const title = workspace.folders.map(folder => folder.name).join(); titleElement.textContent = this.name; - titleElement.title = title; + this.updateTitle(title); this.ariaHeaderLabel = nls.localize('explorerSection', "Explorer Section: {0}", this.name); titleElement.setAttribute('aria-label', this.ariaHeaderLabel); }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 10ae44a781d10..0a4cc62f4ed8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -47,6 +47,7 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { ILogService } from 'vs/platform/log/common/log'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -137,10 +138,10 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane { this._rootElement.id = `notebook-editor-element-${generateUuid()}`; } - override getActionViewItem(action: IAction): IActionViewItem | undefined { + override getActionViewItem(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this); + return this._instantiationService.createInstance(NotebooKernelActionViewItem, action, this, options); } return undefined; } 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 c2c7f3e740a78..35007bedeb845 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -57,7 +57,7 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { @IContextMenuService _contextMenuService: IContextMenuService, @IThemeService _themeService: IThemeService ) { - super(action, options, _keybindingService, _contextMenuService, _themeService); + super(action, { ...options, hoverDelegate: options?.hoverDelegate ?? getDefaultHoverDelegate('element') }, _keybindingService, _contextMenuService, _themeService); } override render(container: HTMLElement): void { @@ -66,8 +66,7 @@ export class UnifiedSubmenuActionView extends SubmenuEntryActionViewItem { this._actionLabel = document.createElement('a'); container.appendChild(this._actionLabel); - const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element'); - this._hover = this._register(setupCustomHover(hoverDelegate, this._actionLabel, '')); + this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('element'), this._actionLabel, '')); this.updateLabel(); } 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 a7b79d856849b..12b86db201eca 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -22,6 +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'; export class BetweenCellToolbar extends CellOverlayPart { private _betweenCellToolbar: ToolBar | undefined; @@ -165,15 +167,16 @@ export class CellTitleToolbarPart extends CellOverlayPart { if (this._view) { return this._view; } - + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); const toolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, this.toolbarContainer, { actionViewItemProvider: (action, options) => { return createActionViewItem(this.instantiationService, action, options); }, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate })); - const deleteToolbar = this._register(this.instantiationService.invokeFunction(accessor => createDeleteToolbar(accessor, this.toolbarContainer, 'cell-delete-toolbar'))); + const deleteToolbar = this._register(this.instantiationService.invokeFunction(accessor => createDeleteToolbar(accessor, this.toolbarContainer, hoverDelegate, 'cell-delete-toolbar'))); if (model.deleteActions.primary.length !== 0 || model.deleteActions.secondary.length !== 0) { deleteToolbar.setActions(model.deleteActions.primary, model.deleteActions.secondary); } @@ -269,7 +272,7 @@ function getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IA return result; } -function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, elementClass?: string): ToolBar { +function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, hoverDelegate: IHoverDelegate, elementClass?: string): ToolBar { const contextMenuService = accessor.get(IContextMenuService); const keybindingService = accessor.get(IKeybindingService); const instantiationService = accessor.get(IInstantiationService); @@ -278,7 +281,8 @@ function createDeleteToolbar(accessor: ServicesAccessor, container: HTMLElement, actionViewItemProvider: (action, options) => { return createActionViewItem(instantiationService, action, options); }, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate }); if (elementClass) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index e2972e82e8aea..969127bc91714 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -28,6 +28,8 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookO import { IActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { disposableTimeout } from 'vs/base/common/async'; import { HiddenItemStrategy, IWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { WorkbenchHoverDelegate } from 'vs/platform/hover/browser/hover'; interface IActionModel { action: IAction; @@ -75,18 +77,18 @@ class WorkbenchAlwaysLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + return this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, true, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } @@ -115,25 +117,25 @@ class WorkbenchNeverLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction) { if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, false, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } }, this.actionProvider.bind(this)); } else { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } @@ -159,20 +161,20 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { readonly goToMenu: IMenu, readonly instantiationService: IInstantiationService) { } - actionProvider(action: IAction): IActionViewItem | undefined { + actionProvider(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (!a || a.renderLabel) { if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ActionViewWithLabel, action, undefined); + return this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction && action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, true, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, true, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } @@ -182,18 +184,18 @@ class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy { return undefined; } else { if (action instanceof MenuItemAction) { - this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } if (action instanceof SubmenuItemAction) { if (action.item.submenu.id === MenuId.NotebookCellExecuteGoTo.id) { - return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, undefined, false, { + return this.instantiationService.createInstance(UnifiedSubmenuActionView, action, { hoverDelegate: options.hoverDelegate }, false, { getActions: () => { return this.goToMenu.getActions().find(([group]) => group === 'navigation/execute')?.[1] ?? []; } }, this.actionProvider.bind(this)); } else { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } @@ -321,24 +323,29 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { notebookEditor: this.notebookEditor }; - const actionProvider = (action: IAction) => { + const actionProvider = (action: IAction, options: IActionViewItemOptions) => { if (action.id === SELECT_KERNEL_ID) { // this is being disposed by the consumer - return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor); + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor, options); } if (this._renderLabel !== RenderLabel.Never) { const a = this._primaryActions.find(a => a.action.id === action.id); if (a && a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, { hoverDelegate: options.hoverDelegate }) : undefined; } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined; } } else { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined; } }; + // Make sure both toolbars have the same hover delegate for instant hover to work + // Due to the elements being further apart than normal toolbars, the default time limit is to short and has to be increased + const hoverDelegate = this._register(this.instantiationService.createInstance(WorkbenchHoverDelegate, 'element', true, {})); + hoverDelegate.setInstantHoverTimeLimit(600); + const leftToolbarOptions: IWorkbenchToolBarOptions = { hiddenItemStrategy: HiddenItemStrategy.RenderInSecondaryGroup, resetMenu: MenuId.NotebookToolbar, @@ -347,6 +354,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { }, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), renderDropdownAsChildElement: true, + hoverDelegate }; this._notebookLeftToolbar = this.instantiationService.createInstance( @@ -363,7 +371,8 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { this._notebookRightToolbar = new ToolBar(this._notebookTopRightToolbarContainer, this.contextMenuService, { getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), actionViewItemProvider: actionProvider, - renderDropdownAsChildElement: true + renderDropdownAsChildElement: true, + hoverDelegate }); this._register(this._notebookRightToolbar); this._notebookRightToolbar.context = context; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 34958f1e7c0e9..8c3ecd8243687 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { localize, localize2 } from 'vs/nls'; @@ -136,13 +136,14 @@ export class NotebooKernelActionViewItem extends ActionViewItem { constructor( actualAction: IAction, private readonly _editor: { onDidChangeModel: Event; textModel: NotebookTextModel | undefined; scopedContextKeyService?: IContextKeyService } | INotebookEditor, + options: IActionViewItemOptions, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @INotebookKernelHistoryService private readonly _notebookKernelHistoryService: INotebookKernelHistoryService, ) { super( undefined, new Action('fakeAction', undefined, ThemeIcon.asClassName(selectKernelIcon), true, (event) => actualAction.run(event)), - { label: false, icon: true } + { ...options, label: false, icon: true } ); this._register(_editor.onDidChangeModel(this._update, this)); this._register(_notebookKernelService.onDidAddKernel(this._update, this)); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 102de6b52f8da..8b0318487b686 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -355,7 +355,7 @@ class ActionBarRenderer extends Disposable implements ITableRenderer Date: Fri, 23 Feb 2024 11:33:58 +0100 Subject: [PATCH 0577/1863] chore - rename chatProvider-files to languageModel (#206054) chore - rename chatProvider-file to languageModel --- .../api/browser/extensionHost.contribution.ts | 2 +- ...rovider.ts => mainThreadLanguageModels.ts} | 30 ++++++++-------- .../workbench/api/common/extHost.api.impl.ts | 4 +-- .../workbench/api/common/extHost.protocol.ts | 14 ++++---- ...atProvider.ts => extHostLanguageModels.ts} | 16 ++++----- .../api/common/extHostTypeConverters.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 4 +-- .../contrib/chat/common/chatServiceImpl.ts | 2 +- .../contrib/chat/common/chatSlashCommands.ts | 2 +- .../{chatProvider.ts => languageModels.ts} | 34 +++++++++---------- 10 files changed, 55 insertions(+), 55 deletions(-) rename src/vs/workbench/api/browser/{mainThreadChatProvider.ts => mainThreadLanguageModels.ts} (86%) rename src/vs/workbench/api/common/{extHostChatProvider.ts => extHostLanguageModels.ts} (93%) rename src/vs/workbench/contrib/chat/common/{chatProvider.ts => languageModels.ts} (62%) diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index c986c67b5e91c..2b59166259d45 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -17,7 +17,7 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar // --- mainThread participants import './mainThreadLocalization'; import './mainThreadBulkEdits'; -import './mainThreadChatProvider'; +import './mainThreadLanguageModels'; import './mainThreadChatAgents2'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; diff --git a/src/vs/workbench/api/browser/mainThreadChatProvider.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts similarity index 86% rename from src/vs/workbench/api/browser/mainThreadChatProvider.ts rename to src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 4b7665abdb635..6e33d7da99dd7 100644 --- a/src/vs/workbench/api/browser/mainThreadChatProvider.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -11,24 +11,24 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgress, Progress } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ExtHostLanguageModelsShape, ExtHostContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ILanguageModelChatMetadata, IChatResponseFragment, ILanguageModelsService, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationProvider, IAuthenticationProviderCreateSessionOptions, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from 'vs/workbench/services/authentication/common/authentication'; import { Extensions, IExtensionFeaturesManagementService, IExtensionFeaturesRegistry } from 'vs/workbench/services/extensionManagement/common/extensionFeatures'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -@extHostNamedCustomer(MainContext.MainThreadChatProvider) -export class MainThreadChatProvider implements MainThreadChatProviderShape { +@extHostNamedCustomer(MainContext.MainThreadLanguageModels) +export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { - private readonly _proxy: ExtHostChatProviderShape; + private readonly _proxy: ExtHostLanguageModelsShape; private readonly _store = new DisposableStore(); private readonly _providerRegistrations = new DisposableMap(); private readonly _pendingProgress = new Map>(); constructor( extHostContext: IExtHostContext, - @IChatProviderService private readonly _chatProviderService: IChatProviderService, + @ILanguageModelsService private readonly _chatProviderService: ILanguageModelsService, @IExtensionFeaturesManagementService private readonly _extensionFeaturesManagementService: IExtensionFeaturesManagementService, @ILogService private readonly _logService: ILogService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @@ -36,8 +36,8 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); - this._proxy.$updateLanguageModels({ added: _chatProviderService.getProviders() }); - this._store.add(_chatProviderService.onDidChangeProviders(this._proxy.$updateLanguageModels, this._proxy)); + this._proxy.$updateLanguageModels({ added: _chatProviderService.getLanguageModelIds() }); + this._store.add(_chatProviderService.onDidChangeLanguageModels(this._proxy.$updateLanguageModels, this._proxy)); } dispose(): void { @@ -45,9 +45,9 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._store.dispose(); } - $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { + $registerLanguageModelProvider(handle: number, identifier: string, metadata: ILanguageModelChatMetadata): void { const dipsosables = new DisposableStore(); - dipsosables.add(this._chatProviderService.registerChatResponseProvider(identifier, { + dipsosables.add(this._chatProviderService.registerLanguageModelChat(identifier, { metadata, provideChatResponse: async (messages, from, options, progress, token) => { const requestId = (Math.random() * 1e6) | 0; @@ -80,10 +80,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._providerRegistrations.deleteAndDispose(handle); } - async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { + async $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise { const activate = this._extensionService.activateByEvent(`onLanguageModelAccess:${providerId}`); - const metadata = this._chatProviderService.lookupChatResponseProvider(providerId); + const metadata = this._chatProviderService.lookupLanguageModel(providerId); if (metadata) { return metadata; @@ -91,10 +91,10 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { await Promise.race([ activate, - Event.toPromise(Event.filter(this._chatProviderService.onDidChangeProviders, e => Boolean(e.added?.includes(providerId)))) + Event.toPromise(Event.filter(this._chatProviderService.onDidChangeLanguageModels, e => Boolean(e.added?.includes(providerId)))) ]); - return this._chatProviderService.lookupChatResponseProvider(providerId); + return this._chatProviderService.lookupLanguageModel(providerId); } async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { @@ -102,7 +102,7 @@ export class MainThreadChatProvider implements MainThreadChatProviderShape { this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); - const task = this._chatProviderService.fetchChatResponse(providerId, extension, messages, options, new Progress(value => { + const task = this._chatProviderService.makeLanguageModelChatRequest(providerId, extension, messages, options, new Progress(value => { this._proxy.$handleResponseFragment(requestId, value); }), token); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 71ec5250fb9a0..f89c741dd497b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -29,7 +29,7 @@ import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentica import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; @@ -207,7 +207,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); - const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService, extHostAuthentication)); + const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostLanguageModels(rpcProtocol, extHostLogService, extHostAuthentication)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5c18ab8657ab5..01c49f8d26119 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,7 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; @@ -1178,16 +1178,16 @@ export interface ExtHostSpeechShape { $cancelKeywordRecognitionSession(session: number): Promise; } -export interface MainThreadChatProviderShape extends IDisposable { - $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void; +export interface MainThreadLanguageModelsShape extends IDisposable { + $registerLanguageModelProvider(handle: number, identifier: string, metadata: ILanguageModelChatMetadata): void; $unregisterProvider(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise; - $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise; + $prepareChatAccess(extension: ExtensionIdentifier, providerId: string, justification?: string): Promise; $fetchResponse(extension: ExtensionIdentifier, provider: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise; } -export interface ExtHostChatProviderShape { +export interface ExtHostLanguageModelsShape { $updateLanguageModels(data: { added?: string[]; removed?: string[] }): void; $updateModelAccesslist(data: { from: ExtensionIdentifier; to: ExtensionIdentifier; enabled: boolean }[]): void; $provideLanguageModelResponse(handle: number, requestId: number, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise; @@ -2765,7 +2765,7 @@ export interface MainThreadTestingShape { export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), - MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), + MainThreadLanguageModels: createProxyIdentifier('MainThreadLanguageModels'), MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), @@ -2890,7 +2890,7 @@ export const ExtHostContext = { ExtHostChat: createProxyIdentifier('ExtHostChat'), ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), - ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), + ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostSpeech: createProxyIdentifier('ExtHostSpeech'), ExtHostAiRelatedInformation: createProxyIdentifier('ExtHostAiRelatedInformation'), ExtHostAiEmbeddingVector: createProxyIdentifier('ExtHostAiEmbeddingVector'), diff --git a/src/vs/workbench/api/common/extHostChatProvider.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts similarity index 93% rename from src/vs/workbench/api/common/extHostChatProvider.ts rename to src/vs/workbench/api/common/extHostLanguageModels.ts index 8ad8318f9e182..7baf1d9353d08 100644 --- a/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -6,11 +6,11 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostLanguageModelsShape, IMainContext, MainContext, MainThreadLanguageModelsShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; -import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +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'; @@ -100,11 +100,11 @@ class LanguageModelRequest { } -export class ExtHostChatProvider implements ExtHostChatProviderShape { +export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { private static _idPool = 1; - private readonly _proxy: MainThreadChatProviderShape; + private readonly _proxy: MainThreadLanguageModelsShape; private readonly _onDidChangeModelAccess = new Emitter<{ from: ExtensionIdentifier; to: ExtensionIdentifier }>(); private readonly _onDidChangeProviders = new Emitter(); readonly onDidChangeProviders = this._onDidChangeProviders.event; @@ -120,7 +120,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { private readonly _logService: ILogService, private readonly _extHostAuthentication: ExtHostAuthentication, ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); + this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageModels); } dispose(): void { @@ -130,7 +130,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { registerLanguageModel(extension: IExtensionDescription, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { - const handle = ExtHostChatProvider._idPool++; + const handle = ExtHostLanguageModels._idPool++; this._languageModels.set(handle, { extension: extension.identifier, provider }); let auth; if (metadata.auth) { @@ -139,7 +139,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { accountLabel: typeof metadata.auth === 'object' ? metadata.auth.label : undefined }; } - this._proxy.$registerProvider(handle, identifier, { + this._proxy.$registerLanguageModelProvider(handle, identifier, { extension: extension.identifier, model: metadata.name ?? '', auth @@ -299,7 +299,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { this.$updateModelAccesslist([{ from: from.identifier, to: to.identifier, enabled: true }]); } - private _isUsingAuth(from: ExtensionIdentifier, toMetadata: IChatResponseProviderMetadata): toMetadata is IChatResponseProviderMetadata & { auth: NonNullable } { + private _isUsingAuth(from: ExtensionIdentifier, toMetadata: ILanguageModelChatMetadata): toMetadata is ILanguageModelChatMetadata & { auth: NonNullable } { // If the 'to' extension uses an auth check return !!toMetadata.auth // And we're asking from a different extension diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index db879caea6bb0..816f02273615a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -38,7 +38,7 @@ import { getPrivateApiFor } from 'vs/workbench/api/common/extHostTestingPrivateA import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from 'vs/workbench/common/editor'; import { IViewBadge } from 'vs/workbench/common/views'; import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import * as chatProvider from 'vs/workbench/contrib/chat/common/chatProvider'; +import * as chatProvider from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatCommandButton, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgressMessage, IChatTreeData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d1cdc123a6b25..0d34b49aff88c 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -46,7 +46,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; -import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { LanguageModelsService, ILanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; @@ -328,7 +328,7 @@ registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delay registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); -registerSingleton(IChatProviderService, ChatProviderService, InstantiationType.Delayed); +registerSingleton(ILanguageModelsService, LanguageModelsService, InstantiationType.Delayed); registerSingleton(IChatSlashCommandService, ChatSlashCommandService, InstantiationType.Delayed); registerSingleton(IChatAgentService, ChatAgentService, InstantiationType.Delayed); registerSingleton(IChatVariablesService, ChatVariablesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 89081fe437d25..6d89b981afc0c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -24,7 +24,7 @@ import { IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workb 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'; -import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index 3d758da31137e..2d43f1c039682 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress } from 'vs/platform/progress/common/progress'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/chat/common/chatProvider.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts similarity index 62% rename from src/vs/workbench/contrib/chat/common/chatProvider.ts rename to src/vs/workbench/contrib/chat/common/languageModels.ts index c393a73de98c4..7f93594aee3f3 100644 --- a/src/vs/workbench/contrib/chat/common/chatProvider.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -26,7 +26,7 @@ export interface IChatResponseFragment { part: string; } -export interface IChatResponseProviderMetadata { +export interface ILanguageModelChatMetadata { readonly extension: ExtensionIdentifier; readonly model: string; readonly description?: string; @@ -36,50 +36,50 @@ export interface IChatResponseProviderMetadata { }; } -export interface IChatResponseProvider { - metadata: IChatResponseProviderMetadata; +export interface ILanguageModelChat { + metadata: ILanguageModelChatMetadata; provideChatResponse(messages: IChatMessage[], from: ExtensionIdentifier, options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } -export const IChatProviderService = createDecorator('chatProviderService'); +export const ILanguageModelsService = createDecorator('ILanguageModelsService'); -export interface IChatProviderService { +export interface ILanguageModelsService { readonly _serviceBrand: undefined; - onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }>; + onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }>; - getProviders(): string[]; + getLanguageModelIds(): string[]; - lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined; + lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined; - registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable; + registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable; - fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; + makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise; } -export class ChatProviderService implements IChatProviderService { +export class LanguageModelsService implements ILanguageModelsService { readonly _serviceBrand: undefined; - private readonly _providers: Map = new Map(); + private readonly _providers: Map = new Map(); private readonly _onDidChangeProviders = new Emitter<{ added?: string[]; removed?: string[] }>(); - readonly onDidChangeProviders: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; + readonly onDidChangeLanguageModels: Event<{ added?: string[]; removed?: string[] }> = this._onDidChangeProviders.event; dispose() { this._onDidChangeProviders.dispose(); this._providers.clear(); } - getProviders(): string[] { + getLanguageModelIds(): string[] { return Array.from(this._providers.keys()); } - lookupChatResponseProvider(identifier: string): IChatResponseProviderMetadata | undefined { + lookupLanguageModel(identifier: string): ILanguageModelChatMetadata | undefined { return this._providers.get(identifier)?.metadata; } - registerChatResponseProvider(identifier: string, provider: IChatResponseProvider): IDisposable { + registerLanguageModelChat(identifier: string, provider: ILanguageModelChat): IDisposable { if (this._providers.has(identifier)) { throw new Error(`Chat response provider with identifier ${identifier} is already registered.`); } @@ -92,7 +92,7 @@ export class ChatProviderService implements IChatProviderService { }); } - fetchChatResponse(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { + makeLanguageModelChatRequest(identifier: string, from: ExtensionIdentifier, messages: IChatMessage[], options: { [name: string]: any }, progress: IProgress, token: CancellationToken): Promise { const provider = this._providers.get(identifier); if (!provider) { throw new Error(`Chat response provider with identifier ${identifier} is not registered.`); From 1ad43ea49ad9c3f325a2944d04e2f761a7882587 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 11:39:20 +0100 Subject: [PATCH 0578/1863] adding telemetry when error happens --- .../inlineCompletions/browser/inlineCompletionsModel.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index a35ce5f910854..006c9873c520b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mapFindFirst } from 'vs/base/common/arraysFind'; -import { BugIndicatingError, onUnexpectedExternalError } from 'vs/base/common/errors'; +import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, recomputeInitiallyAndOnChange, observableSignal, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; import { commonPrefixLength, splitLinesIncludeSeparators } from 'vs/base/common/strings'; @@ -421,9 +421,6 @@ export class InlineCompletionsModel extends Disposable { const partialGhostTextVal = ghostTextVal.substring(0, acceptUntilIndexExclusive); const positions = this._positions.get(); - if (positions.length === 0) { - return; - } const cursorPosition = positions[0]; // Executing the edit might free the completion, so we have to hold a reference on it. @@ -486,6 +483,10 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos ); const positionWithinTextEdit = subtractPositions(primaryPosition, primaryEditStartPosition); if (positionWithinTextEdit.lineNumber < 1) { + onUnexpectedError(new BugIndicatingError( + `positionWithinTextEdit line number should be bigger than 0. + Invalid subtraction between ${primaryPosition.toString()} and ${primaryEditStartPosition.toString()}` + )); return []; } const secondaryEditText = substringPos(primaryEdit.text, positionWithinTextEdit); From 64355fa7f44db5bb01cbcc3bc7bbc704866f1879 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 11:55:32 +0100 Subject: [PATCH 0579/1863] Fixes #195384 (#206004) --- .../browser/widget/diffEditor/diffEditorWidget.ts | 15 +++++---------- .../diffEditor/features/revertButtonsFeature.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 6b57de66397a4..5ad6e0543ef34 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -20,10 +20,11 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; -import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; import { DiffEditorViewZones } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones'; +import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature'; import { MovedBlocksLinesFeature } from 'vs/editor/browser/widget/diffEditor/features/movedBlocksLinesFeature'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; +import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature'; import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, bindContextKey, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; @@ -31,7 +32,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; -import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; @@ -40,11 +41,10 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { DelegatingEditor } from './delegatingEditorImpl'; import { DiffEditorEditors } from './components/diffEditorEditors'; +import { DelegatingEditor } from './delegatingEditorImpl'; import { DiffEditorOptions } from './diffEditorOptions'; import { DiffEditorViewModel, DiffMapping, DiffState } from './diffEditorViewModel'; -import { RevertButtonsFeature } from 'vs/editor/browser/widget/diffEditor/features/revertButtonsFeature'; export interface IDiffCodeEditorWidgetOptions { originalEditor?: ICodeEditorWidgetOptions; @@ -477,12 +477,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { }; } - revert(diff: DetailedLineRangeMapping): void { - if (diff.innerChanges) { - this.revertRangeMappings(diff.innerChanges); - return; - } - + revert(diff: LineRangeMapping): void { const model = this._diffModel.get(); if (!model || !model.isDiffUpToDate.get()) { return; } diff --git a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts index c82167c3d13da..532786f2da1ea 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/revertButtonsFeature.ts @@ -15,7 +15,7 @@ import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEdi import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; import { Range } from 'vs/editor/common/core/range'; -import { RangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { GlyphMarginLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; @@ -62,7 +62,7 @@ export class RevertButtonsFeature extends Disposable { const btn = store.add(new RevertButton( m.lineRangeMapping.modified.startLineNumber, this._widget, - m.lineRangeMapping.innerChanges, + m.lineRangeMapping, false )); this._editors.modified.addGlyphMarginWidget(btn); @@ -122,7 +122,7 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { constructor( private readonly _lineNumber: number, private readonly _widget: DiffEditorWidget, - private readonly _diffs: RangeMapping[], + private readonly _diffs: RangeMapping[] | LineRangeMapping, private readonly _revertSelection: boolean, ) { super(); @@ -142,7 +142,11 @@ export class RevertButton extends Disposable implements IGlyphMarginWidget { })); this._register(addDisposableListener(this._domNode, EventType.CLICK, (e) => { - this._widget.revertRangeMappings(this._diffs); + if (this._diffs instanceof LineRangeMapping) { + this._widget.revert(this._diffs); + } else { + this._widget.revertRangeMappings(this._diffs); + } e.stopPropagation(); e.preventDefault(); })); From 3363acb98bf941b741e4d11e7ea4764142e6b3f2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 12:28:04 +0100 Subject: [PATCH 0580/1863] Fixes revealing unchanged code bug (#206065) --- .../diffEditor/features/hideUnchangedRegionsFeature.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index f2eb90c6d2f12..6767a375a3c81 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -349,9 +349,13 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { didMove = didMove || Math.abs(delta) > 2; const lineDelta = Math.round(delta / editor.getOption(EditorOption.lineHeight)); const newVal = Math.max(0, Math.min(cur - lineDelta, this._unchangedRegion.getMaxVisibleLineCountBottom())); - const top = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); + const top = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel()!.getLineCount() + ? editor.getContentHeight() + : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); this._unchangedRegion.visibleLineCountBottom.set(newVal, undefined); - const top2 = editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); + const top2 = this._unchangedRegionRange.endLineNumberExclusive > editor.getModel()!.getLineCount() + ? editor.getContentHeight() + : editor.getTopForLineNumber(this._unchangedRegionRange.endLineNumberExclusive); editor.setScrollTop(editor.getScrollTop() + (top2 - top)); }); From 640131f6e32a65b82a65c390c53e868690c469cf Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 23 Feb 2024 13:28:00 +0100 Subject: [PATCH 0581/1863] bumping up the version from package json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f734771d90735..1c3d3fc095fc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.87.0", + "version": "1.88.0", "distro": "b314654a31bdba8cd2b0c7548e931916d03416bf", "author": { "name": "Microsoft Corporation" From 7127151f30bdba6f87ce18bdd446e26d83b249a4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 13:46:26 +0100 Subject: [PATCH 0582/1863] Move hoverWidget to hoverService (#206070) --- .../{widget/hoverWidget => services/hoverService}/hover.css | 0 .../editor/browser/services/{ => hoverService}/hoverService.ts | 2 +- .../hoverWidget => services/hoverService}/hoverWidget.ts | 0 src/vs/editor/standalone/browser/standaloneServices.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename src/vs/editor/browser/{widget/hoverWidget => services/hoverService}/hover.css (100%) rename src/vs/editor/browser/services/{ => hoverService}/hoverService.ts (99%) rename src/vs/editor/browser/{widget/hoverWidget => services/hoverService}/hoverWidget.ts (100%) diff --git a/src/vs/editor/browser/widget/hoverWidget/hover.css b/src/vs/editor/browser/services/hoverService/hover.css similarity index 100% rename from src/vs/editor/browser/widget/hoverWidget/hover.css rename to src/vs/editor/browser/services/hoverService/hover.css diff --git a/src/vs/editor/browser/services/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts similarity index 99% rename from src/vs/editor/browser/services/hoverService.ts rename to src/vs/editor/browser/services/hoverService/hoverService.ts index 9dea5dab8125d..f7338ae7b5240 100644 --- a/src/vs/editor/browser/services/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -9,7 +9,7 @@ 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { HoverWidget } from 'vs/editor/browser/widget/hoverWidget/hoverWidget'; +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 { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom'; diff --git a/src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts similarity index 100% rename from src/vs/editor/browser/widget/hoverWidget/hoverWidget.ts rename to src/vs/editor/browser/services/hoverService/hoverWidget.ts diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 351f85537d8e2..c815e1b3d1337 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -10,7 +10,7 @@ import 'vs/platform/undoRedo/common/undoRedoService'; import 'vs/editor/common/services/languageFeatureDebounce'; import 'vs/editor/common/services/semanticTokensStylingService'; import 'vs/editor/common/services/languageFeaturesService'; -import 'vs/editor/browser/services/hoverService'; +import 'vs/editor/browser/services/hoverService/hoverService'; import * as strings from 'vs/base/common/strings'; import * as dom from 'vs/base/browser/dom'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index a7d0a54f6ddc6..61028e388dd5b 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -104,7 +104,7 @@ import 'vs/workbench/services/views/browser/viewsService'; import 'vs/workbench/services/quickinput/browser/quickInputService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService'; import 'vs/workbench/services/authentication/browser/authenticationService'; -import 'vs/editor/browser/services/hoverService'; +import 'vs/editor/browser/services/hoverService/hoverService'; import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; import 'vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl'; From 22508a50008764da21bc5704712c3473d6a566f6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:30:27 +0100 Subject: [PATCH 0583/1863] Moves testDiffProviderFactoryService (#206078) --- .../{ => test}/browser/diff/testDiffProviderFactoryService.ts | 2 +- .../inlineChat/test/browser/inlineChatController.test.ts | 2 +- .../contrib/inlineChat/test/browser/inlineChatSession.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/vs/editor/{ => test}/browser/diff/testDiffProviderFactoryService.ts (95%) diff --git a/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts similarity index 95% rename from src/vs/editor/browser/diff/testDiffProviderFactoryService.ts rename to src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts index 08ed249b8b95c..275c4995988ee 100644 --- a/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts +++ b/src/vs/editor/test/browser/diff/testDiffProviderFactoryService.ts @@ -18,7 +18,7 @@ export class TestDiffProviderFactoryService implements IDiffProviderFactoryServi } } -export class SyncDocumentDiffProvider implements IDocumentDiffProvider { +class SyncDocumentDiffProvider implements IDocumentDiffProvider { computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions, cancellationToken: CancellationToken): Promise { const result = linesDiffComputers.getDefault().computeDiff(original.getLinesContent(), modified.getLinesContent(), options); return Promise.resolve({ diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 932cb5056b176..f998c0969d7c0 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -11,7 +11,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; +import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 53b0cb87519a1..16556bef77381 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; +import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; From 1c16af45f1e08ad444b32ea044e49a129854683a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:34:07 +0100 Subject: [PATCH 0584/1863] multiDiffEditorWidget -> multiDiffEditor (#206075) --- .../{multiDiffEditorWidget => multiDiffEditor}/colors.ts | 0 .../diffEditorItemTemplate.ts | 4 ++-- .../{multiDiffEditorWidget => multiDiffEditor}/model.ts | 0 .../multiDiffEditorViewModel.ts | 2 +- .../multiDiffEditorWidget.ts | 8 ++++---- .../multiDiffEditorWidgetImpl.ts | 4 ++-- .../objectPool.ts | 0 .../{multiDiffEditorWidget => multiDiffEditor}/style.css | 0 .../{multiDiffEditorWidget => multiDiffEditor}/utils.ts | 0 .../workbenchUIElementFactory.ts | 0 src/vs/editor/standalone/browser/standaloneEditor.ts | 2 +- .../contrib/bulkEdit/browser/preview/bulkEditPane.ts | 2 +- .../contrib/multiDiffEditor/browser/multiDiffEditor.ts | 8 ++++---- .../multiDiffEditor/browser/multiDiffEditorInput.ts | 4 ++-- 14 files changed, 17 insertions(+), 17 deletions(-) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/colors.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/diffEditorItemTemplate.ts (99%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/model.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorViewModel.ts (98%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorWidget.ts (95%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/multiDiffEditorWidgetImpl.ts (99%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/objectPool.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/style.css (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/utils.ts (100%) rename src/vs/editor/browser/widget/{multiDiffEditorWidget => multiDiffEditor}/workbenchUIElementFactory.ts (100%) diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts b/src/vs/editor/browser/widget/multiDiffEditor/colors.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts rename to src/vs/editor/browser/widget/multiDiffEditor/colors.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts similarity index 99% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts rename to src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts index f5433ba99d774..84cffd7e94edc 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts @@ -10,8 +10,8 @@ import { autorun, derived, observableFromEvent } from 'vs/base/common/observable import { IObservable, globalTransaction, observableValue } from 'vs/base/common/observableInternal/base'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { DocumentDiffItemViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditor/model.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts rename to src/vs/editor/browser/widget/multiDiffEditor/model.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts similarity index 98% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts index 3a26a82c526ab..5a225008e03f0 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.ts @@ -8,7 +8,7 @@ import { observableFromEvent, observableValue, transaction } from 'vs/base/commo import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; -import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditor/model'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts similarity index 95% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts index 6867bb84ac7cc..af73b75af6a4d 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget.ts @@ -7,13 +7,13 @@ import { Dimension } from 'vs/base/browser/dom'; import { Disposable } from 'vs/base/common/lifecycle'; import { derived, derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; -import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { IMultiDiffEditorViewState, IMultiDiffResource, MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { MultiDiffEditorViewModel } from './multiDiffEditorViewModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import './colors'; -import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { DiffEditorItemTemplate } from 'vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts similarity index 99% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts rename to src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts index 36a568f25241b..4e23c4e6cb648 100644 --- a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts +++ b/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts @@ -14,8 +14,8 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./style'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; -import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; -import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { RevealOptions } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; +import { IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts b/src/vs/editor/browser/widget/multiDiffEditor/objectPool.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts rename to src/vs/editor/browser/widget/multiDiffEditor/objectPool.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditor/style.css similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/style.css rename to src/vs/editor/browser/widget/multiDiffEditor/style.css diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/utils.ts b/src/vs/editor/browser/widget/multiDiffEditor/utils.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/utils.ts rename to src/vs/editor/browser/widget/multiDiffEditor/utils.ts diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts b/src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts similarity index 100% rename from src/vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory.ts rename to src/vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory.ts diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 059a4928862c6..0fa038765afbf 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -39,7 +39,7 @@ import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarker, IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; /** * Create a new editor under `domElement`. diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 278de48c47c63..ba85dee93b2c0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -37,7 +37,7 @@ import { ButtonBar } from 'vs/base/browser/ui/button/button'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { Mutable } from 'vs/base/common/types'; import { IResourceDiffEditorInput } from 'vs/workbench/common/editor'; -import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IMultiDiffEditorOptions } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { IRange } from 'vs/editor/common/core/range'; const enum State { diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts index c1491431828f7..6f66dbec274b6 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -5,8 +5,8 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; -import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditorWidget/workbenchUIElementFactory'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget'; +import { IResourceLabel, IWorkbenchUIElementFactory } from 'vs/editor/browser/widget/multiDiffEditor/workbenchUIElementFactory'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -22,8 +22,8 @@ import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/brows import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; -import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; -import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; +import { IMultiDiffEditorOptions, IMultiDiffEditorViewState } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditor } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts index 058373fd46540..4344d01587391 100644 --- a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -16,8 +16,8 @@ import { constObservable, mapObservableArrayCached } from 'vs/base/common/observ import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined, isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; -import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorViewModel'; +import { ConstLazyPromise, IDocumentDiffItem, IMultiDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditor/model'; +import { MultiDiffEditorViewModel } from 'vs/editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; From af1f874650e7d7bde5513d091619f1202aed8e71 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 23 Feb 2024 14:34:41 +0100 Subject: [PATCH 0585/1863] add "Chat" as 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 413c1db06f189..62977ee07e3b1 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', + 'Chat', 'Other', ]; From 0bd70d48ad8b3e2fb1922aa54f87c786ff2b4bd8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 14:35:45 +0100 Subject: [PATCH 0586/1863] code editor move (#206074) --- .../codeEditorContributions.ts | 0 .../{ => codeEditor}/codeEditorWidget.ts | 95 +++++++++---------- .../widget/{media => codeEditor}/editor.css | 0 .../embeddedCodeEditorWidget.ts | 54 +---------- .../components/diffEditorEditors.ts | 2 +- .../diffEditorViewZones.ts | 2 +- .../inlineDiffDeletedCodeMargin.ts | 2 +- .../widget/diffEditor/delegatingEditorImpl.ts | 2 +- .../widget/diffEditor/diffEditorWidget.ts | 2 +- .../diffEditor/embeddedDiffEditorWidget.ts | 55 +++++++++++ .../features/overviewRulerFeature.ts | 2 +- src/vs/editor/contrib/dnd/browser/dnd.ts | 2 +- .../gotoSymbol/browser/goToCommands.ts | 2 +- .../browser/peek/referencesWidget.ts | 2 +- .../contrib/peekView/browser/peekView.ts | 2 +- .../browser/stickyScrollWidget.ts | 2 +- .../contrib/suggest/browser/suggestWidget.ts | 2 +- src/vs/editor/editor.all.ts | 2 +- .../browser/standaloneCodeEditor.ts | 2 +- src/vs/editor/test/browser/testCodeEditor.ts | 2 +- src/vs/workbench/browser/codeeditor.ts | 2 +- .../browser/parts/editor/textCodeEditor.ts | 2 +- .../accessibility/browser/accessibleView.ts | 2 +- .../browser/callHierarchyPeek.ts | 2 +- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/codeBlockPart.ts | 2 +- .../codeEditor/browser/diffEditorHelper.ts | 2 +- .../codeEditor/browser/simpleEditorOptions.ts | 2 +- .../suggestEnabledInput.ts | 2 +- .../comments/browser/commentsController.ts | 2 +- .../comments/browser/simpleCommentEditor.ts | 2 +- .../contrib/debug/browser/breakpointWidget.ts | 2 +- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../contrib/files/browser/fileCommands.ts | 2 +- .../inlineChat/browser/inlineChatActions.ts | 3 +- .../browser/inlineChatLivePreviewWidget.ts | 3 +- .../inlineChat/browser/inlineChatWidget.ts | 5 +- .../browser/interactive.contribution.ts | 2 +- .../interactive/browser/interactiveEditor.ts | 2 +- .../contrib/mergeEditor/browser/utils.ts | 2 +- .../mergeEditor/browser/view/editorGutter.ts | 2 +- .../browser/view/editors/codeEditorView.ts | 2 +- .../browser/view/scrollSynchronizer.ts | 2 +- .../controller/chat/notebookChatController.ts | 2 +- .../notebook/browser/diff/diffComponents.ts | 2 +- .../browser/diff/notebookDiffEditorBrowser.ts | 2 +- .../notebook/browser/diff/notebookDiffList.ts | 2 +- .../browser/view/cellParts/markupCell.ts | 2 +- .../browser/view/renderers/cellRenderer.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 2 +- .../contrib/search/browser/searchView.ts | 2 +- .../searchEditor/browser/searchEditor.ts | 2 +- .../testing/browser/testingOutputPeek.ts | 5 +- .../browser/typeHierarchyPeek.ts | 2 +- .../browser/walkThroughPart.ts | 2 +- .../dialogs/browser/fileDialogService.ts | 2 +- 57 files changed, 164 insertions(+), 152 deletions(-) rename src/vs/editor/browser/widget/{ => codeEditor}/codeEditorContributions.ts (100%) rename src/vs/editor/browser/widget/{ => codeEditor}/codeEditorWidget.ts (99%) rename src/vs/editor/browser/widget/{media => codeEditor}/editor.css (100%) rename src/vs/editor/browser/widget/{ => codeEditor}/embeddedCodeEditorWidget.ts (59%) create mode 100644 src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts diff --git a/src/vs/editor/browser/widget/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts similarity index 100% rename from src/vs/editor/browser/widget/codeEditorContributions.ts rename to src/vs/editor/browser/widget/codeEditor/codeEditorContributions.ts diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts similarity index 99% rename from src/vs/editor/browser/widget/codeEditorWidget.ts rename to src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index 5466f913d244b..39cba20b26863 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/editor/browser/services/markerDecorations'; - import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -14,7 +13,7 @@ import { Emitter, EmitterOptions, Event, EventDeliveryQueue, createEventDelivery import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import 'vs/css!./media/editor'; +import 'vs/css!./editor'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; @@ -25,7 +24,7 @@ import { IContentWidgetData, IGlyphMarginWidgetData, IOverlayWidgetData, View } import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; -import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditorContributions'; +import { CodeEditorContributions } from 'vs/editor/browser/widget/codeEditor/codeEditorContributions'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; @@ -61,51 +60,6 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -let EDITOR_ID = 0; - -export interface ICodeEditorWidgetOptions { - /** - * Is this a simple widget (not a real code editor)? - * Defaults to false. - */ - isSimpleWidget?: boolean; - - /** - * Contributions to instantiate. - * When provided, only the contributions included will be instantiated. - * To include the defaults, those must be provided as well via [...EditorExtensionsRegistry.getEditorContributions()] - * Defaults to EditorExtensionsRegistry.getEditorContributions(). - */ - contributions?: IEditorContributionDescription[]; - - /** - * Telemetry data associated with this CodeEditorWidget. - * Defaults to null. - */ - telemetryData?: object; -} - -class ModelData { - constructor( - public readonly model: ITextModel, - public readonly viewModel: ViewModel, - public readonly view: View, - public readonly hasRealView: boolean, - public readonly listenersToRemove: IDisposable[], - public readonly attachedView: IAttachedView, - ) { - } - - public dispose(): void { - dispose(this.listenersToRemove); - this.model.onBeforeDetached(this.attachedView); - if (this.hasRealView) { - this.view.dispose(); - } - this.viewModel.dispose(); - } -} - export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor { private static readonly dropIntoEditorDecorationOptions = ModelDecorationOptions.register({ @@ -1932,6 +1886,51 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } } +let EDITOR_ID = 0; + +export interface ICodeEditorWidgetOptions { + /** + * Is this a simple widget (not a real code editor)? + * Defaults to false. + */ + isSimpleWidget?: boolean; + + /** + * Contributions to instantiate. + * When provided, only the contributions included will be instantiated. + * To include the defaults, those must be provided as well via [...EditorExtensionsRegistry.getEditorContributions()] + * Defaults to EditorExtensionsRegistry.getEditorContributions(). + */ + contributions?: IEditorContributionDescription[]; + + /** + * Telemetry data associated with this CodeEditorWidget. + * Defaults to null. + */ + telemetryData?: object; +} + +class ModelData { + constructor( + public readonly model: ITextModel, + public readonly viewModel: ViewModel, + public readonly view: View, + public readonly hasRealView: boolean, + public readonly listenersToRemove: IDisposable[], + public readonly attachedView: IAttachedView, + ) { + } + + public dispose(): void { + dispose(this.listenersToRemove); + this.model.onBeforeDetached(this.attachedView); + if (this.hasRealView) { + this.view.dispose(); + } + this.viewModel.dispose(); + } +} + const enum BooleanEventValue { NotSet, False, diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/codeEditor/editor.css similarity index 100% rename from src/vs/editor/browser/widget/media/editor.css rename to src/vs/editor/browser/widget/codeEditor/editor.css diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts similarity index 59% rename from src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts rename to src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts index 4a5dffa53477d..9fb3a8e69c2b9 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts @@ -4,24 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { DiffEditorWidget, IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { ConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IThemeService } from 'vs/platform/theme/common/themeService'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { - private readonly _parentEditor: ICodeEditor; private readonly _overwriteOptions: IEditorOptions; @@ -38,7 +34,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @INotificationService notificationService: INotificationService, @IAccessibilityService accessibilityService: IAccessibilityService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService ) { super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService); @@ -65,45 +61,3 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { super.updateOptions(this._overwriteOptions); } } - -export class EmbeddedDiffEditorWidget extends DiffEditorWidget { - - private readonly _parentEditor: ICodeEditor; - private readonly _overwriteOptions: IDiffEditorOptions; - - constructor( - domElement: HTMLElement, - options: Readonly, - codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, - parentEditor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService, - @ICodeEditorService codeEditorService: ICodeEditorService, - @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, - @IEditorProgressService editorProgressService: IEditorProgressService, - ) { - super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); - - this._parentEditor = parentEditor; - this._overwriteOptions = options; - - // Overwrite parent's options - super.updateOptions(this._overwriteOptions); - - this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); - } - - getParentEditor(): ICodeEditor { - return this._parentEditor; - } - - private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void { - super.updateOptions(this._parentEditor.getRawOptions()); - super.updateOptions(this._overwriteOptions); - } - - override updateOptions(newOptions: IEditorOptions): void { - objects.mixin(this._overwriteOptions, newOptions, true); - super.updateOptions(this._overwriteOptions); - } -} diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts index 79d6509b225c1..1818ead661f3a 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorunHandleChanges, derivedOpts, observableFromEvent } from 'vs/base/common/observable'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { OverviewRulerFeature } from 'vs/editor/browser/widget/diffEditor/features/overviewRulerFeature'; import { EditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts index ac5b8886ac3fa..d6bcac91567d2 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.ts @@ -12,7 +12,7 @@ import { IObservable, autorun, derived, derivedWithStore, observableFromEvent, o import { ThemeIcon } from 'vs/base/common/themables'; import { assertIsDefined } from 'vs/base/common/types'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { diffDeleteDecoration, diffRemoveIcon } from 'vs/editor/browser/widget/diffEditor/registrations.contribution'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorViewModel, DiffMapping } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts index ced12b99f7bd0..f6d88c3afe73b 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/inlineDiffDeletedCodeMargin.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; diff --git a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts index ff3b66c5e09fc..96a85f35eefc0 100644 --- a/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts +++ b/src/vs/editor/browser/widget/diffEditor/delegatingEditorImpl.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; import { IPosition, Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 5ad6e0543ef34..7499fa8e0bcbe 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -16,7 +16,7 @@ import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/edi import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { AccessibleDiffViewer, AccessibleDiffViewerModelFromEditors } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/components/diffEditorDecorations'; import { DiffEditorSash } from 'vs/editor/browser/widget/diffEditor/components/diffEditorSash'; diff --git a/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts new file mode 100644 index 0000000000000..9156c17ead354 --- /dev/null +++ b/src/vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { DiffEditorWidget, IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +export class EmbeddedDiffEditorWidget extends DiffEditorWidget { + + private readonly _parentEditor: ICodeEditor; + private readonly _overwriteOptions: IDiffEditorOptions; + + constructor( + domElement: HTMLElement, + options: Readonly, + codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, + parentEditor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @IAccessibilitySignalService accessibilitySignalService: IAccessibilitySignalService, + @IEditorProgressService editorProgressService: IEditorProgressService + ) { + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, accessibilitySignalService, editorProgressService); + + this._parentEditor = parentEditor; + this._overwriteOptions = options; + + // Overwrite parent's options + super.updateOptions(this._overwriteOptions); + + this._register(parentEditor.onDidChangeConfiguration(e => this._onParentConfigurationChanged(e))); + } + + getParentEditor(): ICodeEditor { + return this._parentEditor; + } + + private _onParentConfigurationChanged(e: ConfigurationChangedEvent): void { + super.updateOptions(this._parentEditor.getRawOptions()); + super.updateOptions(this._overwriteOptions); + } + + override updateOptions(newOptions: IEditorOptions): void { + objects.mixin(this._overwriteOptions, newOptions, true); + super.updateOptions(this._overwriteOptions); + } +} diff --git a/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts index bd1997491e147..8141cd9452cf0 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/overviewRulerFeature.ts @@ -10,7 +10,7 @@ import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState'; import { Color } from 'vs/base/common/color'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, autorunWithStore, derived, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/components/diffEditorEditors'; import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { appendRemoveOnDispose } from 'vs/editor/browser/widget/diffEditor/utils'; diff --git a/src/vs/editor/contrib/dnd/browser/dnd.ts b/src/vs/editor/contrib/dnd/browser/dnd.ts index d4576e66c8d9a..9e355410d4842 100644 --- a/src/vs/editor/contrib/dnd/browser/dnd.ts +++ b/src/vs/editor/contrib/dnd/browser/dnd.ts @@ -11,7 +11,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import 'vs/css!./dnd'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index b968f2489049f..aa6b78654392b 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -13,7 +13,7 @@ import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/edit import { IActiveCodeEditor, ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions'; import * as corePosition from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index d23e38b81abd5..b80e75d47eca3 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -16,7 +16,7 @@ import { Schemas } from 'vs/base/common/network'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import 'vs/css!./referencesWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index 5c0882b8db4b4..a0f2dfd914ed5 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -17,7 +17,7 @@ import 'vs/css!./media/peekViewWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 06fb2ce428f5f..bdcaafb489137 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -11,7 +11,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./stickyScroll'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { getColumnOfNodeOffset } from 'vs/editor/browser/viewParts/lines/viewLine'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorLayoutInfo, EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index fc41e265fc40c..2eeb94d99b610 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -16,7 +16,7 @@ import { clamp } from 'vs/base/common/numbers'; import * as strings from 'vs/base/common/strings'; import 'vs/css!./media/suggest'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { SuggestWidgetStatus } from 'vs/editor/contrib/suggest/browser/suggestWidgetStatus'; diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 227595d6224d8..37ed9f9f4fdb0 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/editor/browser/coreCommands'; -import 'vs/editor/browser/widget/codeEditorWidget'; +import 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; import 'vs/editor/contrib/anchorSelect/browser/anchorSelect'; import 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 479bb75745c4e..3e903d5bab9af 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -7,7 +7,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import { IModelChangedEvent } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index b29e05c805364..c0a4b9d1cf339 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -9,7 +9,7 @@ import { EditorConfiguration } from 'vs/editor/browser/config/editorConfiguratio import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index 33358b10156d5..dcce3224ba991 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference, isCodeEditor, isCompositeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; diff --git a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts index 2efc763e041e5..2edad61d83c64 100644 --- a/src/vs/workbench/browser/parts/editor/textCodeEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textCodeEditor.ts @@ -12,7 +12,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { isEqual } from 'vs/base/common/resources'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 5f3a2df9d06a5..cfbfa70a2423c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -18,7 +18,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 6eb9f63cba9bf..3dada0c5698bf 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -19,7 +19,7 @@ import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/spl import { Dimension, isKeyboardEvent } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index f469287859878..e149d81cadb4d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -14,7 +14,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { HoverController } from 'vs/editor/contrib/hover/browser/hover'; diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 17b169bc4f1c4..8669cadcce7dd 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -15,7 +15,7 @@ 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/codeEditorWidget'; +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'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 204ebdd135844..a19d88cca333e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -10,7 +10,7 @@ import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensio import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 6acfd4495b78e..b4f6826502630 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 32ac5cb4dec8e..60e1339879082 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -11,7 +11,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { mixin } from 'vs/base/common/objects'; import { isMacintosh } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index d2fa2c78e8a0a..4faf49116c0ef 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -25,7 +25,7 @@ import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commen import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 2a4f2c3a2bbf6..d41c334028d45 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -6,7 +6,7 @@ import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorAction, EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 26ae95ccf1d06..7289d25ba29ac 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -16,7 +16,7 @@ import 'vs/css!./media/breakpointWidget'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, ServicesAccessor, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ffcda601ff903..21f7ed8ba4771 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -25,7 +25,7 @@ import 'vs/css!./media/repl'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 6fa04545a0ea0..da1e9c4dcd5fc 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -37,7 +37,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { coalesce } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isCancellationError } from 'vs/base/common/errors'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 8deb70a073e2b..75ca16a1f249c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -7,7 +7,8 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 1af9ecfa2f14c..85d9762dc22c1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -7,7 +7,8 @@ import { Dimension, getWindow, h, runAtThisOrScheduleAtNextAnimationFrame } from import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorOption, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index f65363a736b28..50bba01479eb3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -19,9 +19,10 @@ import 'vs/css!./inlineChat'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 05d7d253673f3..6d300714340ce 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -12,7 +12,7 @@ import { extname, isEqual } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ITextModel } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index f9c766ca849e1..af62b50a84c4b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -10,7 +10,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ICodeEditorViewState, IDecorationOptions } from 'vs/editor/common/editorCommon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index d2853645051ab..b43d06358f8de 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -7,7 +7,7 @@ import { ArrayQueue, CompareResult } from 'vs/base/common/arrays'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts, observableFromEvent } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 9d349b594ec72..b751b6bc0ade2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -6,7 +6,7 @@ import { h, reset } from 'vs/base/browser/dom'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun, IReader, observableFromEvent, observableSignal, observableSignalFromEvent, transaction } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index 38266ed06008b..8cd243e377726 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -9,7 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, derived, observableFromEvent } from 'vs/base/common/observable'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts index bbbe978f30293..94857ee472633 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { DocumentLineRangeMap } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 6181098363282..93a98b32d60e1 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -15,7 +15,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 23e8dec447835..c1218b15ce826 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -9,7 +9,7 @@ import { Schemas } from 'vs/base/common/network'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffElementViewModelBase, getFormattedMetadataJSON, getFormattedOutputJSON, OutputComparison, outputEqual, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 6741f2a3ab870..031c2afdc7f31 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 11aa9bfb6b192..9248d1cee6ea2 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -17,7 +17,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { DiffElementViewModelBase, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DIFF_CELL_MARGIN, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { DeletedElement, getOptimizedNestedCodeEditorWidgetOptions, InsertElement, ModifiedElement } from 'vs/workbench/contrib/notebook/browser/diff/diffComponents'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index 5806dd97b5f1c..d334bb1db3f20 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -11,7 +11,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 6913333712507..25a1310af4371 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -9,7 +9,7 @@ import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 742c76b7d3710..adcf99c35cdf7 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -29,7 +29,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { rot } from 'vs/base/common/numbers'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; import { IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index f91410f9b8d2b..f35fe3e0edade 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -43,7 +43,7 @@ import { flatten } from 'vs/base/common/arrays'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 649a025c5c3c1..6911cdad05873 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -23,7 +23,7 @@ import * as network from 'vs/base/common/network'; import 'vs/css!./media/searchview'; import { getCodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditor } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 5f36473df1593..dec67977c0578 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -13,7 +13,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; -import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index a64fc0274f345..7f5dcfb792095 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -37,9 +37,10 @@ import 'vs/css!./testingOutputPeek'; import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts index 8753d7436f57f..d5bf22e671a29 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts @@ -15,7 +15,7 @@ import { FuzzyScore } from 'vs/base/common/filters'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts index 177b1a4a14e53..50efd6cfc8469 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts @@ -17,7 +17,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WalkThroughInput } from 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index c55daa6fdc309..de6a4ad822721 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -19,7 +19,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { extractFileListData } from 'vs/platform/dnd/browser/dnd'; import { Iterable } from 'vs/base/common/iterator'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { From c18d58d9629f4b45e0359fb14812526b96ebf67d Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:36:18 +0100 Subject: [PATCH 0587/1863] Adopt Custom Hover for search/find/replace views/widgets (#206077) * Adopt Custom Hover for search/find/replace views * tree search filters --- src/vs/base/browser/ui/button/button.ts | 23 ++++++++++++++----- src/vs/base/browser/ui/findinput/findInput.ts | 6 +++++ .../browser/ui/findinput/findInputToggles.ts | 6 +++++ .../base/browser/ui/findinput/replaceInput.ts | 4 +++- src/vs/base/browser/ui/toggle/toggle.ts | 4 +++- src/vs/base/browser/ui/tree/abstractTree.ts | 10 ++++++-- .../contrib/find/browser/findOptionsWidget.ts | 6 +++++ .../editor/contrib/find/browser/findWidget.ts | 17 +++++++++++++- .../search/browser/patternInputWidget.ts | 3 +++ .../search/browser/searchResultsView.ts | 8 +++++-- .../contrib/search/browser/searchView.ts | 8 +++++-- .../contrib/search/browser/searchWidget.ts | 7 ++++-- .../searchEditor/browser/searchEditor.ts | 5 +++- 13 files changed, 89 insertions(+), 18 deletions(-) diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 4675ee8c5eed9..e7ca41dc8b375 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -10,6 +10,7 @@ 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 { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; @@ -29,6 +30,7 @@ export interface IButtonOptions extends Partial { readonly supportIcons?: boolean; readonly supportShortLabel?: boolean; readonly secondary?: boolean; + readonly hoverDelegate?: IHoverDelegate; } export interface IButtonStyles { @@ -115,6 +117,10 @@ export class Button extends Disposable implements IButton { this._element.classList.add('monaco-text-button-with-short-label'); } + if (typeof options.title === 'string') { + this.setTitle(options.title); + } + if (typeof options.ariaLabel === 'string') { this._element.setAttribute('aria-label', options.ariaLabel); } @@ -249,16 +255,13 @@ export class Button extends Disposable implements IButton { } else if (this.options.title) { title = renderStringAsPlaintext(value); } - if (!this._hover) { - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._element, title)); - } else { - this._hover.update(title); - } + + this.setTitle(title); if (typeof this.options.ariaLabel === 'string') { this._element.setAttribute('aria-label', this.options.ariaLabel); } else if (this.options.ariaLabel) { - this._element.setAttribute('aria-label', this._element.title); + this._element.setAttribute('aria-label', title); } this._label = value; @@ -299,6 +302,14 @@ export class Button extends Disposable implements IButton { return !this._element.classList.contains('disabled'); } + private setTitle(title: string) { + if (!this._hover) { + this._hover = this._register(setupCustomHover(this.options.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this._element, title)); + } else { + this._hover.update(title); + } + } + focus(): void { this._element.focus(); } diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 76af849c278b9..c119ad43779ba 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -16,6 +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'; export interface IFindInputOptions { @@ -113,10 +114,13 @@ export class FindInput extends Widget { inputBoxStyles: options.inputBoxStyles, })); + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + if (this.showCommonFindToggles) { this.regex = this._register(new RegexToggle({ appendTitle: appendRegexLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.regex.onChange(viaKeyboard => { @@ -133,6 +137,7 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: appendWholeWordsLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.wholeWords.onChange(viaKeyboard => { @@ -146,6 +151,7 @@ export class FindInput extends Widget { this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: appendCaseSensitiveLabel, isChecked: false, + hoverDelegate, ...options.toggleStyles })); this._register(this.caseSensitive.onChange(viaKeyboard => { diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 591ab98157773..8b3ea12580f32 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -3,6 +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 { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; import * as nls from 'vs/nls'; @@ -13,6 +15,7 @@ export interface IFindInputToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; + readonly hoverDelegate?: IHoverDelegate; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); @@ -25,6 +28,7 @@ export class CaseSensitiveToggle extends Toggle { icon: Codicon.caseSensitive, title: NLS_CASE_SENSITIVE_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -38,6 +42,7 @@ export class WholeWordsToggle extends Toggle { icon: Codicon.wholeWord, title: NLS_WHOLE_WORD_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -51,6 +56,7 @@ export class RegexToggle extends Toggle { icon: Codicon.regex, title: NLS_REGEX_TOGGLE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 6cd7d4fb1c682..8e20025d75795 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -16,6 +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'; export interface IReplaceInputOptions { @@ -44,9 +45,10 @@ class PreserveCaseToggle extends Toggle { icon: Codicon.preserveCase, title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, - inputActiveOptionBackground: opts.inputActiveOptionBackground + inputActiveOptionBackground: opts.inputActiveOptionBackground, }); } } diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 54b9130d9e4f5..540bb17db2f93 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -15,6 +15,7 @@ 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'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; @@ -22,6 +23,7 @@ export interface IToggleOpts extends IToggleStyles { readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; + readonly hoverDelegate?: IHoverDelegate; } export interface IToggleStyles { @@ -130,7 +132,7 @@ export class Toggle extends Widget { } this.domNode = document.createElement('div'); - this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); + this._hover = this._register(setupCustomHover(opts.hoverDelegate ?? getDefaultHoverDelegate('mouse'), this.domNode, this._opts.title)); this.domNode.classList.add(...classes); if (!this._opts.notFocusable) { this.domNode.tabIndex = 0; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 16f985264d4ec..c42cd0ba3368c 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -33,6 +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'; class TreeElementsDragAndDropData extends ElementsDragAndDropData { @@ -679,6 +681,7 @@ export interface ITreeFindToggleOpts { readonly inputActiveOptionBorder: string | undefined; readonly inputActiveOptionForeground: string | undefined; readonly inputActiveOptionBackground: string | undefined; + readonly hoverDelegate?: IHoverDelegate; } export class ModeToggle extends Toggle { @@ -687,6 +690,7 @@ export class ModeToggle extends Toggle { icon: Codicon.listFilter, title: localize('filter', "Filter"), isChecked: opts.isChecked ?? false, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -700,6 +704,7 @@ export class FuzzyToggle extends Toggle { icon: Codicon.searchFuzzy, title: localize('fuzzySearch', "Fuzzy Match"), isChecked: opts.isChecked ?? false, + hoverDelegate: opts.hoverDelegate ?? getDefaultHoverDelegate('element'), inputActiveOptionBorder: opts.inputActiveOptionBorder, inputActiveOptionForeground: opts.inputActiveOptionForeground, inputActiveOptionBackground: opts.inputActiveOptionBackground @@ -802,8 +807,9 @@ class FindWidget extends Disposable { this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; } - this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter })); - this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy })); + const toggleHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + 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); this.onDidChangeMatchType = Event.map(this.matchTypeToggle.onChange, () => this.matchTypeToggle.checked ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous, this._store); diff --git a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts index 9ebf4167820e0..7b693a1f28c56 100644 --- a/src/vs/editor/contrib/find/browser/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/browser/findOptionsWidget.ts @@ -13,6 +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'; export class FindOptionsWidget extends Widget implements IOverlayWidget { @@ -52,9 +53,12 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground), }; + const hoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + this.caseSensitive = this._register(new CaseSensitiveToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), isChecked: this._state.matchCase, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.caseSensitive.domNode); @@ -67,6 +71,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.wholeWords = this._register(new WholeWordsToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), isChecked: this._state.wholeWord, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.wholeWords.domNode); @@ -79,6 +84,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this.regex = this._register(new RegexToggle({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), isChecked: this._state.isRegex, + hoverDelegate, ...toggleStyles })); this._domNode.appendChild(this.regex.domNode); diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index f89e3d8183751..76189e64741a2 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,6 +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'; 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.')); @@ -1010,10 +1013,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._matchesCount.className = 'matchesCount'; this._updateMatchesCount(); + // Create a scoped hover delegate for all find related buttons + const hoverDelegate = getDefaultHoverDelegate('element', true); + // Previous button this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), icon: findPreviousMatchIcon, + hoverDelegate, onTrigger: () => { assertIsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1023,6 +1030,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), icon: findNextMatchIcon, + hoverDelegate, onTrigger: () => { assertIsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError); } @@ -1077,6 +1085,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), icon: widgetClose, + hoverDelegate, onTrigger: () => { this._state.change({ isRevealed: false, searchScope: null }, false); }, @@ -1138,10 +1147,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } })); + // Create scoped hover delegate for replace actions + const replaceHoverDelegate = this._register(getDefaultHoverDelegate('element', true)); + // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), icon: findReplaceIcon, + hoverDelegate: replaceHoverDelegate, onTrigger: () => { this._controller.replace(); }, @@ -1157,6 +1170,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction), icon: findReplaceAllIcon, + hoverDelegate: replaceHoverDelegate, onTrigger: () => { this._controller.replaceAll(); } @@ -1299,6 +1313,7 @@ export interface ISimpleButtonOpts { readonly label: string; readonly className?: string; readonly icon?: ThemeIcon; + readonly hoverDelegate?: IHoverDelegate; readonly onTrigger: () => void; readonly onKeyDown?: (e: IKeyboardEvent) => void; } @@ -1321,11 +1336,11 @@ export class SimpleButton extends Widget { } this._domNode = document.createElement('div'); - this._domNode.title = this._opts.label; this._domNode.tabIndex = 0; this._domNode.className = className; this._domNode.setAttribute('role', 'button'); this._domNode.setAttribute('aria-label', this._opts.label); + this._register(setupCustomHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label)); this.onclick(this._domNode, (e) => { this._opts.onTrigger(); diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 6d167046d3771..c7dfc7eae6099 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -19,6 +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'; export interface IOptions { placeholder?: string; @@ -219,6 +220,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { icon: Codicon.book, title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles })); this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { @@ -271,6 +273,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { actionClassName: 'useExcludesAndIgnoreFiles', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles })); this._register(this.useExcludesAndIgnoreFilesBox.onChange(viaKeyboard => { diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 3f71827f12c8f..38a4f53640352 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -30,6 +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'; interface IFolderMatchTemplate { label: IResourceLabel; @@ -360,7 +362,9 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.match.classList.toggle('replace', replace); templateData.replace.textContent = replace ? match.replaceString : ''; templateData.after.textContent = preview.after; - templateData.parent.title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); + + const title = (preview.fullBefore + (replace ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999); + templateData.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.parent, title)); SearchContext.IsEditableItemKey.bindTo(templateData.contextKeyService).set(!(match instanceof MatchInNotebook && match.isReadonly())); @@ -372,7 +376,7 @@ export class MatchRenderer extends Disposable implements ICompressibleTreeRender templateData.lineNumber.classList.toggle('show', (numLines > 0) || showLineNumbers); templateData.lineNumber.textContent = lineNumberStr + extraLinesStr; - templateData.lineNumber.setAttribute('title', this.getMatchTitle(match, showLineNumbers)); + templateData.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.lineNumber, this.getMatchTitle(match, showLineNumbers))); templateData.actions.context = { viewer: this.searchView.getControl(), element: match }; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6911cdad05873..4a0a156fcf613 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -81,6 +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'; const $ = dom.$; @@ -405,7 +407,8 @@ export class SearchView extends ViewPane { // Toggle query details button this.toggleQueryDetailsButton = dom.append(this.queryDetails, - $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); + $('.more' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); + this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, nls.localize('moreSearch', "Toggle Search Details"))); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); @@ -2133,7 +2136,8 @@ class SearchLinkButton extends Disposable { constructor(label: string, handler: (e: dom.EventLike) => unknown, tooltip?: string) { super(); - this.element = $('a.pointer', { tabindex: 0, title: tooltip }, label); + this.element = $('a.pointer', { tabindex: 0 }, label); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, tooltip)); this.addEventHandlers(handler); } diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 26acd42d459f6..731653a482831 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -42,6 +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'; /** Specified in searchview.css */ const SingleLineInputHeight = 26; @@ -370,7 +371,9 @@ export class SearchWidget extends Widget { buttonSecondaryBackground: undefined, buttonSecondaryForeground: undefined, buttonSecondaryHoverBackground: undefined, - buttonSeparator: undefined + buttonSeparator: undefined, + title: nls.localize('search.replace.toggle.button.title', "Toggle Replace"), + hoverDelegate: getDefaultHoverDelegate('element'), }; this.toggleReplaceButton = this._register(new Button(parent, opts)); this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); @@ -378,7 +381,6 @@ export class SearchWidget extends Widget { this.toggleReplaceButton.icon = searchHideReplaceIcon; // TODO@joao need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); - this.toggleReplaceButton.element.title = nls.localize('search.replace.toggle.button.title', "Toggle Replace"); } private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void { @@ -441,6 +443,7 @@ export class SearchWidget extends Widget { isChecked: false, title: appendKeyBindingLabel(nls.localize('showContext', "Toggle Context Lines"), this.keybindingService.lookupKeybinding(ToggleSearchEditorContextLinesCommandId)), icon: searchShowContextIcon, + hoverDelegate: getDefaultHoverDelegate('element'), ...defaultToggleStyles }); this._register(this.showContextToggle.onChange(() => this.onContextLinesChanged())); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index dec67977c0578..02b833d6737b3 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -62,6 +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'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(: | )(\s*)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -161,7 +163,8 @@ export class SearchEditor extends AbstractTextCodeEditor this.includesExcludesContainer = DOM.append(container, DOM.$('.includes-excludes')); // Toggle query details button - this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); + this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand' + ThemeIcon.asCSSSelector(searchDetailsIcon), { tabindex: 0, role: 'button' })); + this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.toggleQueryDetailsButton, localize('moreSearch', "Toggle Search Details"))); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e); this.toggleIncludesExcludes(); From 11a6b428f829f0b843f14fc278412d79ab4f2dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:06:44 +0100 Subject: [PATCH 0588/1863] Custom hover adoption for test explorer (#206086) ustom hover adoption for test explorer --- .../testing/browser/testingExplorerFilter.ts | 5 ++- .../testing/browser/testingExplorerView.ts | 42 +++++++++++-------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index ba9050a610bc9..a7490c873cff9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -46,11 +46,12 @@ export class TestingExplorerFilter extends BaseActionViewItem { constructor( action: IAction, + options: IBaseActionViewItemOptions, @ITestExplorerFilterState private readonly state: ITestExplorerFilterState, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITestService private readonly testService: ITestService, ) { - super(null, action); + super(null, action, options); this.updateFilterActiveState(); this._register(testService.excluded.onTestExclusionsChanged(this.updateFilterActiveState, this)); } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index ba7f2800830cc..8eb58e118a3d4 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -5,8 +5,11 @@ import * as dom from 'vs/base/browser/dom'; 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 { 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'; @@ -285,18 +288,18 @@ export class TestingExplorerView extends ViewPane { } /** @override */ - public override getActionViewItem(action: IAction): IActionViewItem | undefined { + public override getActionViewItem(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined { switch (action.id) { case TestCommandId.FilterAction: - this.filter.value = this.instantiationService.createInstance(TestingExplorerFilter, action); + this.filter.value = this.instantiationService.createInstance(TestingExplorerFilter, action, options); this.filterFocusListener.value = this.filter.value.onDidFocus(() => this.lastFocusState = LastFocusState.Input); return this.filter.value; case TestCommandId.RunSelectedAction: - return this.getRunGroupDropdown(TestRunProfileBitset.Run, action); + return this.getRunGroupDropdown(TestRunProfileBitset.Run, action, options); case TestCommandId.DebugSelectedAction: - return this.getRunGroupDropdown(TestRunProfileBitset.Debug, action); + return this.getRunGroupDropdown(TestRunProfileBitset.Debug, action, options); default: - return super.getActionViewItem(action); + return super.getActionViewItem(action, options); } } @@ -380,10 +383,10 @@ export class TestingExplorerView extends ViewPane { super.saveState(); } - private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction) { + private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction, options: IActionViewItemOptions) { const dropdownActions = this.getTestConfigGroupActions(group); if (dropdownActions.length < 2) { - return super.getActionViewItem(defaultAction); + return super.getActionViewItem(defaultAction, options); } const primaryAction = this.instantiationService.createInstance(MenuItemAction, { @@ -401,13 +404,13 @@ export class TestingExplorerView extends ViewPane { primaryAction, dropdownAction, dropdownActions, '', this.contextMenuService, - {} + options ); } private createFilterActionBar() { const bar = new ActionBar(this.treeHeader, { - actionViewItemProvider: action => this.getActionViewItem(action), + actionViewItemProvider: (action, options) => this.getActionViewItem(action, options), triggerKeys: { keyDown: false, keys: [] }, }); bar.push(new Action(TestCommandId.FilterAction)); @@ -442,6 +445,7 @@ class ResultSummaryView extends Disposable { private elementsWereAttached = false; private badgeType: TestingCountBadge; private lastBadge?: NumberBadge | IconBadge; + private countHover: ICustomHover; private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly renderLoop = this._register(new RunOnceScheduler(() => this.render(), SUMMARY_RENDER_INTERVAL)); private readonly elements = dom.h('div.result-summary', [ @@ -472,6 +476,8 @@ class ResultSummaryView extends Disposable { } })); + this.countHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.elements.count, '')); + const ab = this._register(new ActionBar(this.elements.rerun, { actionViewItemProvider: (action, options) => createActionViewItem(instantiationService, action, options), })); @@ -518,7 +524,7 @@ class ResultSummaryView extends Disposable { } count.textContent = `${counts.passed}/${counts.totalWillBeRun}`; - count.title = getTestProgressText(counts); + this.countHover.update(getTestProgressText(counts)); this.renderActivityBadge(counts); if (!this.elementsWereAttached) { @@ -1301,6 +1307,7 @@ class IdentityProvider implements IIdentityProvider { interface IErrorTemplateData { label: HTMLElement; + disposable: DisposableStore; } class ErrorRenderer implements ITreeRenderer { @@ -1318,7 +1325,7 @@ class ErrorRenderer implements ITreeRenderer, _: number, data: IErrorTemplateData): void { @@ -1330,12 +1337,11 @@ class ErrorRenderer implements ITreeRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined })); @@ -1451,7 +1457,7 @@ class TestItemRenderer extends Disposable data.icon.className += ' retired'; } - data.label.title = getLabelForTestTreeElement(node.element); + data.elementDisposable.add(setupCustomHover(getDefaultHoverDelegate('mouse'), data.label, getLabelForTestTreeElement(node.element))); if (node.element.test.item.label.trim()) { dom.reset(data.label, ...renderLabelWithIcons(node.element.test.item.label)); } else { From 529e73d71b794d152671942cb3ae42a33e4c7d3b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 23 Feb 2024 15:15:06 +0100 Subject: [PATCH 0589/1863] api - sketch up `vscode.lm.makeChatRequest` alternative to requesting chat access (#206088) re https://github.com/microsoft/vscode/issues/205800 --- .../workbench/api/common/extHost.api.impl.ts | 16 ++++++++- .../api/common/extHostLanguageModels.ts | 36 +++++++++++++++++++ .../vscode.proposed.languageModels.d.ts | 26 ++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f89c741dd497b..3bf78d8ed2402 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 { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, 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'; @@ -1436,6 +1436,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeLanguageModels: (listener, thisArgs?, disposables?) => { checkProposedApiEnabled(extension, 'languageModels'); return extHostChatProvider.onDidChangeProviders(listener, thisArgs, disposables); + }, + makeChatRequest(languageModel: string, messages: vscode.LanguageModelMessage[], optionsOrToken: { [name: string]: any } | vscode.CancellationToken, 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); } }; diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 7baf1d9353d08..aa45f8f58aaad 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -38,6 +38,10 @@ class LanguageModelResponseStream { class LanguageModelRequest { + static fromError(err: Error): vscode.LanguageModelResponse { + return new LanguageModelRequest(Promise.reject(err), new CancellationTokenSource()).apiObject; + } + readonly apiObject: vscode.LanguageModelResponse; private readonly _responseStreams = new Map(); @@ -223,6 +227,38 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } + async makeChatRequest(extension: IExtensionDescription, languageModelId: string, messages: vscode.LanguageModelMessage[], options: Record, token: CancellationToken) { + + const from = extension.identifier; + // const justification = options?.justification; // TODO@jrieken + const metadata = await this._proxy.$prepareChatAccess(from, languageModelId, undefined); + + if (!metadata || !this._languageModelIds.has(languageModelId)) { + return LanguageModelRequest.fromError(new Error(`Language model ${languageModelId} is unknown`)); + } + + if (this._isUsingAuth(from, metadata)) { + await this._getAuthAccess(extension, { identifier: metadata.extension, displayName: metadata.auth.providerLabel }, undefined); + + if (!this._modelAccessList.get(from)?.has(metadata.extension)) { + return LanguageModelRequest.fromError(new Error('Access to chat has been revoked')); + } + } + + 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); + this._pendingRequest.set(requestId, { languageModelId, res }); + + requestPromise.finally(() => { + this._pendingRequest.delete(requestId); + cts.dispose(); + }); + + return res.apiObject; + } + async requestLanguageModelAccess(extension: IExtensionDescription, languageModelId: string, options?: vscode.LanguageModelAccessOptions): Promise { const from = extension.identifier; const justification = options?.justification; diff --git a/src/vscode-dts/vscode.proposed.languageModels.d.ts b/src/vscode-dts/vscode.proposed.languageModels.d.ts index 0e35351833b8f..bde1bd4aad406 100644 --- a/src/vscode-dts/vscode.proposed.languageModels.d.ts +++ b/src/vscode-dts/vscode.proposed.languageModels.d.ts @@ -171,6 +171,32 @@ declare module 'vscode' { */ 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 + * + * - 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 + */ + // TODO@API refine doc + // TODO@API define specific error types? + export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): Thenable; + + /** + * @see {@link makeChatRequest} + */ + export function makeChatRequest(languageModel: string, messages: LanguageModelMessage[], token: CancellationToken): Thenable; + /** * The identifiers of all language models that are currently available. */ From aca9718d33e8f489cfd26903bf3eb2b7dedb714b Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:17:44 +0100 Subject: [PATCH 0590/1863] Register treeview hover delegate (#206089) dispose treeview hover delegate --- src/vs/workbench/browser/parts/views/treeView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 0245752c80987..0eb478a354441 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1106,7 +1106,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.rerender())); this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { From 95e10d938f4681a2d41df588a0b38788ebf5a43e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 23 Feb 2024 15:19:33 +0100 Subject: [PATCH 0591/1863] Remove `livePreview` and its implementation (#206073) * Remove `livePreview` and its implementation fixes https://github.com/microsoft/vscode/issues/205535 * make compiler happy --- .../browser/inlineChatController.ts | 5 +- .../browser/inlineChatFileCreationWidget.ts | 256 +++++++++ .../browser/inlineChatLivePreviewWidget.ts | 532 ------------------ .../browser/inlineChatStrategies.ts | 161 +----- .../contrib/inlineChat/common/inlineChat.ts | 4 +- .../test/browser/inlineChatController.test.ts | 9 +- 6 files changed, 262 insertions(+), 705 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts delete mode 100644 src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 38ea05687a15d..775b62fee6856 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -41,7 +41,7 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IInlineChatSavingService } from './inlineChatSavingService'; import { EmptyResponse, ErrorResponse, ExpansionState, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from './inlineChatSessionService'; -import { EditModeStrategy, IEditObserver, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; +import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -342,9 +342,6 @@ export class InlineChatController implements IEditorContribution { case EditMode.Preview: this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._editor, this._zone.value); break; - case EditMode.LivePreview: - this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value); - break; case EditMode.Live: default: this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts new file mode 100644 index 0000000000000..eca335cb37533 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget.ts @@ -0,0 +1,256 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension, h } from 'vs/base/browser/dom'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Range } from 'vs/editor/common/core/range'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; +import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { INLINE_CHAT_ID, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { Position } from 'vs/editor/common/core/position'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { FileKind } from 'vs/platform/files/common/files'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IAction, toAction } from 'vs/base/common/actions'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Codicon } from 'vs/base/common/codicons'; +import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; +import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; + +export class InlineChatFileCreatePreviewWidget extends ZoneWidget { + + private static TitleHeight = 35; + + private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [ + h('div.title@title', [ + h('span.name.show-file-icons@name'), + h('span.detail@detail'), + ]), + h('div.editor@editor'), + ]); + + private readonly _name: ResourceLabel; + private readonly _previewEditor: ICodeEditor; + private readonly _previewStore = new MutableDisposable(); + private readonly _buttonBar: ButtonBarWidget; + private _dim: Dimension | undefined; + + constructor( + parentEditor: ICodeEditor, + @IInstantiationService instaService: IInstantiationService, + @IThemeService themeService: IThemeService, + @ITextModelService private readonly _textModelResolverService: ITextModelService, + @IEditorService private readonly _editorService: IEditorService, + ) { + super(parentEditor, { + showArrow: false, + showFrame: true, + frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER), + frameWidth: 1, + isResizeable: true, + isAccessible: true, + showInHiddenAreas: true, + ordinal: 10000 + 2 + }); + super.create(); + + this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true }); + this._elements.detail.appendChild(renderIcon(Codicon.circleFilled)); + + const contributions = EditorExtensionsRegistry + .getEditorContributions() + .filter(c => c.id !== INLINE_CHAT_ID); + + this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { + scrollBeyondLastLine: false, + stickyScroll: { enabled: false }, + minimap: { enabled: false }, + scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, }, + }, { isSimpleWidget: true, contributions }, parentEditor); + + const doStyle = () => { + const theme = themeService.getColorTheme(); + const overrides: [target: string, source: string][] = [ + [colorRegistry.editorBackground, inlineChatRegionHighlight], + [editorColorRegistry.editorGutter, inlineChatRegionHighlight], + ]; + + for (const [target, source] of overrides) { + const value = theme.getColor(source); + if (value) { + this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); + } + } + }; + doStyle(); + this._disposables.add(themeService.onDidColorThemeChange(doStyle)); + + this._buttonBar = instaService.createInstance(ButtonBarWidget); + this._elements.title.appendChild(this._buttonBar.domNode); + } + + override dispose(): void { + this._name.dispose(); + this._buttonBar.dispose(); + this._previewEditor.dispose(); + this._previewStore.dispose(); + super.dispose(); + } + + protected override _fillContainer(container: HTMLElement): void { + container.appendChild(this._elements.domNode); + } + + override show(): void { + throw new Error('Use showFileCreation'); + } + + async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise { + + const store = new DisposableStore(); + this._previewStore.value = store; + + this._name.element.setFile(untitledTextModel.resource, { + fileKind: FileKind.FILE, + fileDecorations: { badges: true, colors: true } + }); + + const actionSave = toAction({ + id: '1', + label: localize('save', "Create"), + run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT }) + }); + const actionSaveAs = toAction({ + id: '2', + label: localize('saveAs', "Create As"), + run: async () => { + const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY }); + await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT }); + } + }); + + this._buttonBar.update([ + [actionSave, actionSaveAs], + [(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))] + ]); + + store.add(Event.any( + untitledTextModel.onDidRevert, + untitledTextModel.onDidSave, + untitledTextModel.onDidChangeDirty, + untitledTextModel.onWillDispose + )(() => this.hide())); + + await untitledTextModel.resolve(); + + const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource); + store.add(ref); + + const model = ref.object.textEditorModel; + this._previewEditor.setModel(model); + + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + + this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`; + const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight; + + const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33)); + const lines = Math.min(maxLines, model.getLineCount()); + + super.show(where, titleHightInLines + lines); + } + + override hide(): void { + this._previewStore.clear(); + super.hide(); + } + + // --- layout + + protected override revealRange(range: Range, isLastLine: boolean): void { + // ignore + } + + protected override _onWidth(widthInPixel: number): void { + if (this._dim) { + this._doLayout(this._dim.height, widthInPixel); + } + } + + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { + + const { lineNumbersLeft } = this.editor.getLayoutInfo(); + this._elements.title.style.marginLeft = `${lineNumbersLeft}px`; + + const newDim = new Dimension(widthInPixel, heightInPixel); + if (!Dimension.equals(this._dim, newDim)) { + this._dim = newDim; + this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight)); + } + } +} + + +class ButtonBarWidget { + + private readonly _domNode = h('div.buttonbar-widget'); + private readonly _buttonBar: ButtonBar; + private readonly _store = new DisposableStore(); + + constructor( + @IContextMenuService private _contextMenuService: IContextMenuService, + ) { + this._buttonBar = new ButtonBar(this.domNode); + + } + + update(allActions: IAction[][]): void { + this._buttonBar.clear(); + let secondary = false; + for (const actions of allActions) { + let btn: IButton; + const [first, ...rest] = actions; + if (!first) { + continue; + } else if (rest.length === 0) { + // single action + btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary }); + } else { + btn = this._buttonBar.addButtonWithDropdown({ + ...defaultButtonStyles, + addPrimaryActionToDropdown: false, + actions: rest, + contextMenuProvider: this._contextMenuService + }); + } + btn.label = first.label; + this._store.add(btn.onDidClick(() => first.run())); + secondary = true; + } + } + + dispose(): void { + this._buttonBar.dispose(); + this._store.dispose(); + } + + get domNode() { + return this._domNode.root; + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts deleted file mode 100644 index 85d9762dc22c1..0000000000000 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ /dev/null @@ -1,532 +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 { Dimension, getWindow, h, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import { assertType } from 'vs/base/common/types'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/embeddedDiffEditorWidget'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; -import { EditorOption, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { INLINE_CHAT_ID, inlineChatDiffInserted, inlineChatDiffRemoved, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { LineRange } from 'vs/editor/common/core/lineRange'; -import { Position } from 'vs/editor/common/core/position'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; -import { ILogService } from 'vs/platform/log/common/log'; -import { invertLineRange, asRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { FileKind } from 'vs/platform/files/common/files'; -import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; -import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { generateUuid } from 'vs/base/common/uuid'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IAction, toAction } from 'vs/base/common/actions'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Codicon } from 'vs/base/common/codicons'; -import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; -import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; - -export class InlineChatLivePreviewWidget extends ZoneWidget { - - private readonly _hideId = `overlayDiff:${generateUuid()}`; - - private readonly _elements = h('div.inline-chat-diff-widget@domNode'); - - private readonly _decorationCollection: IEditorDecorationsCollection; - private readonly _diffEditor: DiffEditorWidget; - - private _dim: Dimension | undefined; - private _isVisible: boolean = false; - - constructor( - editor: ICodeEditor, - private readonly _session: Session, - options: IDiffEditorOptions, - onDidChangeDiff: (() => void) | undefined, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @ILogService private readonly _logService: ILogService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - ) { - super(editor, { showArrow: false, showFrame: false, isResizeable: false, isAccessible: true, allowUnlimitedHeight: true, showInHiddenAreas: true, keepEditorSelection: true, ordinal: 10000 + 1 }); - super.create(); - assertType(editor.hasModel()); - - this._decorationCollection = editor.createDecorationsCollection(); - - const diffContributions = EditorExtensionsRegistry - .getEditorContributions() - .filter(c => c.id !== INLINE_CHAT_ID && c.id !== FoldingController.ID); - - this._diffEditor = instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.domNode, { - scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false, ignoreHorizontalScrollbarInContentHeight: true, }, - scrollBeyondLastLine: false, - renderMarginRevertIcon: true, - renderOverviewRuler: false, - rulers: undefined, - overviewRulerBorder: undefined, - overviewRulerLanes: 0, - diffAlgorithm: 'advanced', - splitViewDefaultRatio: 0.35, - padding: { top: 0, bottom: 0 }, - folding: false, - diffCodeLens: false, - stickyScroll: { enabled: false }, - minimap: { enabled: false }, - isInEmbeddedEditor: true, - useInlineViewWhenSpaceIsLimited: false, - overflowWidgetsDomNode: editor.getOverflowWidgetsDomNode(), - onlyShowAccessibleDiffViewer: this.accessibilityService.isScreenReaderOptimized(), - ...options - }, { - originalEditor: { contributions: diffContributions }, - modifiedEditor: { contributions: diffContributions } - }, editor); - - this._disposables.add(this._diffEditor); - this._diffEditor.setModel({ original: this._session.textModel0, modified: editor.getModel() }); - this._diffEditor.updateOptions({ - lineDecorationsWidth: editor.getLayoutInfo().decorationsWidth - }); - - if (onDidChangeDiff) { - this._disposables.add(this._diffEditor.onDidUpdateDiff(() => { onDidChangeDiff(); })); - - const render = this._disposables.add(new MutableDisposable()); - this._disposables.add(this._diffEditor.onDidContentSizeChange(e => { - if (!this._isVisible || !e.contentHeightChanged) { - return; - } - render.value = runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._diffEditor.getContainerDomNode()), () => { - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - const heightInLines = e.contentHeight / lineHeight; - this._logService.debug(`[IE] relaying with ${heightInLines} lines height`); - this._relayout(heightInLines); - }); - })); - } - - - const doStyle = () => { - const theme = themeService.getColorTheme(); - const overrides: [target: string, source: string][] = [ - [colorRegistry.editorBackground, inlineChatRegionHighlight], - [editorColorRegistry.editorGutter, inlineChatRegionHighlight], - [colorRegistry.diffInsertedLine, inlineChatDiffInserted], - [colorRegistry.diffInserted, inlineChatDiffInserted], - [colorRegistry.diffRemovedLine, inlineChatDiffRemoved], - [colorRegistry.diffRemoved, inlineChatDiffRemoved], - ]; - - for (const [target, source] of overrides) { - const value = theme.getColor(source); - if (value) { - this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); - } - } - }; - doStyle(); - this._disposables.add(themeService.onDidColorThemeChange(doStyle)); - } - - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this._elements.domNode); - } - - // --- show / hide -------------------- - - get isVisible(): boolean { - return this._isVisible; - } - - override hide(): void { - this._decorationCollection.clear(); - this._cleanupFullDiff(); - super.hide(); - this._isVisible = false; - } - - override show(): void { - throw new Error('use showForChanges'); - } - - showForChanges(hunk: HunkInformation): void { - const hasFocus = this._diffEditor.hasTextFocus(); - this._isVisible = true; - - const onlyInserts = hunk.isInsertion(); - - if (onlyInserts || this._session.textModel0.getValueLength() === 0) { - // no change or changes to an empty file - this._logService.debug('[IE] livePreview-mode: no diff'); - this._cleanupFullDiff(); - this._renderInsertWithHighlight(hunk); - } else { - // complex changes - this._logService.debug('[IE] livePreview-mode: full diff'); - this._decorationCollection.clear(); - this._renderChangesWithFullDiff(hunk); - } - - // TODO@jrieken find a better fix for this. this is the challenge: - // the `_updateFromChanges` method invokes show of the zone widget which removes and adds the - // zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose - // focus - if (hasFocus) { - this._diffEditor.focus(); - } - } - - private _renderInsertWithHighlight(hunk: HunkInformation) { - assertType(this.editor.hasModel()); - - const options: IModelDecorationOptions = { - description: 'inline-chat-insert', - showIfCollapsed: false, - isWholeLine: true, - className: 'inline-chat-lines-inserted-range', - }; - - this._decorationCollection.set([{ - range: hunk.getRangesN()[0], - options - }]); - } - - // --- full diff - - private _renderChangesWithFullDiff(hunk: HunkInformation) { - assertType(this.editor.hasModel()); - - const ranges = this._computeHiddenRanges(this._session.textModelN, hunk); - - this._hideEditorRanges(this.editor, [ranges.modifiedHidden]); - this._hideEditorRanges(this._diffEditor.getOriginalEditor(), ranges.originalDiffHidden); - this._hideEditorRanges(this._diffEditor.getModifiedEditor(), ranges.modifiedDiffHidden); - - // this._diffEditor.revealLine(ranges.modifiedHidden.startLineNumber, ScrollType.Immediate); - - const lineCountModified = ranges.modifiedHidden.length; - const lineCountOriginal = ranges.originalHidden.length; - - const heightInLines = Math.max(lineCountModified, lineCountOriginal); - - super.show(ranges.anchor, heightInLines); - this._logService.debug(`[IE] diff SHOWING at ${ranges.anchor} with ${heightInLines} (approx) lines height`); - } - - private _cleanupFullDiff() { - this.editor.setHiddenAreas([], this._hideId); - this._diffEditor.getOriginalEditor().setHiddenAreas([], this._hideId); - this._diffEditor.getModifiedEditor().setHiddenAreas([], this._hideId); - super.hide(); - this._isVisible = false; - } - - private _computeHiddenRanges(model: ITextModel, hunk: HunkInformation) { - - - const modifiedLineRange = LineRange.fromRangeInclusive(hunk.getRangesN()[0]); - let originalLineRange = LineRange.fromRangeInclusive(hunk.getRanges0()[0]); - if (originalLineRange.isEmpty) { - originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + 1); - } - - const originalDiffHidden = invertLineRange(originalLineRange, this._session.textModel0); - const modifiedDiffHidden = invertLineRange(modifiedLineRange, model); - - return { - originalHidden: originalLineRange, - originalDiffHidden, - modifiedHidden: modifiedLineRange, - modifiedDiffHidden, - anchor: new Position(modifiedLineRange.startLineNumber - 1, 1) - }; - } - - private _hideEditorRanges(editor: ICodeEditor, lineRanges: LineRange[]): void { - assertType(editor.hasModel()); - - lineRanges = lineRanges.filter(range => !range.isEmpty); - if (lineRanges.length === 0) { - // todo? - this._logService.debug(`[IE] diff NOTHING to hide for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); - return; - } - - let hiddenRanges: Range[]; - const hiddenLinesCount = lineRanges.reduce((p, c) => p + c.length, 0); // assumes no overlap - if (hiddenLinesCount >= editor.getModel().getLineCount()) { - // TODO: not every line can be hidden, keep the first line around - hiddenRanges = [editor.getModel().getFullModelRange().delta(1)]; - } else { - hiddenRanges = lineRanges.map(lr => asRange(lr, editor.getModel())); - } - editor.setHiddenAreas(hiddenRanges, this._hideId); - this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); - } - - protected override revealRange(range: Range, isLastLine: boolean): void { - // ignore - } - - // --- layout ------------------------- - - protected override _onWidth(widthInPixel: number): void { - if (this._dim) { - this._doLayout(this._dim.height, widthInPixel); - } - } - - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - const newDim = new Dimension(widthInPixel, heightInPixel); - if (!Dimension.equals(this._dim, newDim)) { - this._dim = newDim; - this._diffEditor.layout(this._dim.with(undefined, this._dim.height)); - this._logService.debug('[IE] diff LAYOUT', this._dim); - } - } -} - - -export class InlineChatFileCreatePreviewWidget extends ZoneWidget { - - private static TitleHeight = 35; - - private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [ - h('div.title@title', [ - h('span.name.show-file-icons@name'), - h('span.detail@detail'), - ]), - h('div.editor@editor'), - ]); - - private readonly _name: ResourceLabel; - private readonly _previewEditor: ICodeEditor; - private readonly _previewStore = new MutableDisposable(); - private readonly _buttonBar: ButtonBarWidget; - private _dim: Dimension | undefined; - - constructor( - parentEditor: ICodeEditor, - @IInstantiationService instaService: IInstantiationService, - @IThemeService themeService: IThemeService, - @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IEditorService private readonly _editorService: IEditorService, - ) { - super(parentEditor, { - showArrow: false, - showFrame: true, - frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER), - frameWidth: 1, - isResizeable: true, - isAccessible: true, - showInHiddenAreas: true, - ordinal: 10000 + 2 - }); - super.create(); - - this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true }); - this._elements.detail.appendChild(renderIcon(Codicon.circleFilled)); - - const contributions = EditorExtensionsRegistry - .getEditorContributions() - .filter(c => c.id !== INLINE_CHAT_ID); - - this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, { - scrollBeyondLastLine: false, - stickyScroll: { enabled: false }, - minimap: { enabled: false }, - scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, }, - }, { isSimpleWidget: true, contributions }, parentEditor); - - const doStyle = () => { - const theme = themeService.getColorTheme(); - const overrides: [target: string, source: string][] = [ - [colorRegistry.editorBackground, inlineChatRegionHighlight], - [editorColorRegistry.editorGutter, inlineChatRegionHighlight], - ]; - - for (const [target, source] of overrides) { - const value = theme.getColor(source); - if (value) { - this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); - } - } - }; - doStyle(); - this._disposables.add(themeService.onDidColorThemeChange(doStyle)); - - this._buttonBar = instaService.createInstance(ButtonBarWidget); - this._elements.title.appendChild(this._buttonBar.domNode); - } - - override dispose(): void { - this._name.dispose(); - this._buttonBar.dispose(); - this._previewEditor.dispose(); - this._previewStore.dispose(); - super.dispose(); - } - - protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this._elements.domNode); - } - - override show(): void { - throw new Error('Use showFileCreation'); - } - - async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise { - - const store = new DisposableStore(); - this._previewStore.value = store; - - this._name.element.setFile(untitledTextModel.resource, { - fileKind: FileKind.FILE, - fileDecorations: { badges: true, colors: true } - }); - - const actionSave = toAction({ - id: '1', - label: localize('save', "Create"), - run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT }) - }); - const actionSaveAs = toAction({ - id: '2', - label: localize('saveAs', "Create As"), - run: async () => { - const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY }); - await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT }); - } - }); - - this._buttonBar.update([ - [actionSave, actionSaveAs], - [(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))] - ]); - - store.add(Event.any( - untitledTextModel.onDidRevert, - untitledTextModel.onDidSave, - untitledTextModel.onDidChangeDirty, - untitledTextModel.onWillDispose - )(() => this.hide())); - - await untitledTextModel.resolve(); - - const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource); - store.add(ref); - - const model = ref.object.textEditorModel; - this._previewEditor.setModel(model); - - const lineHeight = this.editor.getOption(EditorOption.lineHeight); - - this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`; - const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight; - - const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33)); - const lines = Math.min(maxLines, model.getLineCount()); - - super.show(where, titleHightInLines + lines); - } - - override hide(): void { - this._previewStore.clear(); - super.hide(); - } - - // --- layout - - protected override revealRange(range: Range, isLastLine: boolean): void { - // ignore - } - - protected override _onWidth(widthInPixel: number): void { - if (this._dim) { - this._doLayout(this._dim.height, widthInPixel); - } - } - - protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - - const { lineNumbersLeft } = this.editor.getLayoutInfo(); - this._elements.title.style.marginLeft = `${lineNumbersLeft}px`; - - const newDim = new Dimension(widthInPixel, heightInPixel); - if (!Dimension.equals(this._dim, newDim)) { - this._dim = newDim; - this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight)); - } - } -} - - -class ButtonBarWidget { - - private readonly _domNode = h('div.buttonbar-widget'); - private readonly _buttonBar: ButtonBar; - private readonly _store = new DisposableStore(); - - constructor( - @IContextMenuService private _contextMenuService: IContextMenuService, - ) { - this._buttonBar = new ButtonBar(this.domNode); - - } - - update(allActions: IAction[][]): void { - this._buttonBar.clear(); - let secondary = false; - for (const actions of allActions) { - let btn: IButton; - const [first, ...rest] = actions; - if (!first) { - continue; - } else if (rest.length === 0) { - // single action - btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary }); - } else { - btn = this._buttonBar.addButtonWithDropdown({ - ...defaultButtonStyles, - addPrimaryActionToDropdown: false, - actions: rest, - contextMenuProvider: this._contextMenuService - }); - } - btn.label = first.label; - this._store.add(btn.onDidClick(() => first.run())); - secondary = true; - } - } - - dispose(): void { - this._buttonBar.dispose(); - this._store.dispose(); - } - - get domNode() { - return this._domNode.root; - } -} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 63088a27ed5e4..30aa3f46b10f3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -28,7 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Progress } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; -import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget'; +import { InlineChatFileCreatePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatFileCreationWidget'; import { HunkInformation, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget'; import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -231,166 +231,7 @@ export interface ProgressingEditsOptions { token: CancellationToken; } -export class LivePreviewStrategy extends EditModeStrategy { - private readonly _previewZone: Lazy; - private readonly _diffZonePool: InlineChatLivePreviewWidget[] = []; - - constructor( - session: Session, - editor: ICodeEditor, - zone: InlineChatZoneWidget, - @IInstantiationService private readonly _instaService: IInstantiationService, - ) { - super(session, editor, zone); - - this._previewZone = new Lazy(() => _instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); - } - - override dispose(): void { - for (const zone of this._diffZonePool) { - zone.hide(); - zone.dispose(); - } - this._previewZone.rawValue?.hide(); - this._previewZone.rawValue?.dispose(); - super.dispose(); - } - - async apply() { - if (this._editCount > 0) { - this._editor.pushUndoStop(); - } - if (!(this._session.lastExchange?.response instanceof ReplyResponse)) { - return; - } - const { untitledTextModel } = this._session.lastExchange.response; - if (untitledTextModel && !untitledTextModel.isDisposed() && untitledTextModel.isDirty()) { - await untitledTextModel.save({ reason: SaveReason.EXPLICIT }); - } - } - - override async undoChanges(altVersionId: number): Promise { - const { textModelN } = this._session; - await undoModelUntil(textModelN, altVersionId); - this._updateDiffZones(); - } - - override async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver): Promise { - return this._makeChanges(edits, obs, undefined, undefined); - } - - override async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions): Promise { - await this._makeChanges(edits, obs, opts, new Progress(() => { - this._updateDiffZones(); - })); - } - - override async renderChanges(response: ReplyResponse): Promise { - - if (response.untitledTextModel && !response.untitledTextModel.isDisposed()) { - this._previewZone.value.showCreation(this._session.wholeRange.value.getStartPosition().delta(-1), response.untitledTextModel); - } else { - this._previewZone.rawValue?.hide(); - } - - return this._updateDiffZones(); - } - - - protected _updateSummaryMessage(hunkCount: number) { - let message: string; - if (hunkCount === 0) { - message = localize('change.0', "Nothing changed"); - } else if (hunkCount === 1) { - message = localize('change.1', "1 change"); - } else { - message = localize('lines.NM', "{0} changes", hunkCount); - } - this._zone.widget.updateStatus(message); - } - - - private _updateDiffZones(): Position | undefined { - - const { hunkData } = this._session; - const hunks = hunkData.getInfo().filter(hunk => hunk.getState() === HunkState.Pending); - - if (hunks.length === 0) { - for (const zone of this._diffZonePool) { - zone.hide(); - } - - if (hunkData.getInfo().find(hunk => hunk.getState() === HunkState.Accepted)) { - this._onDidAccept.fire(); - } else { - this._onDidDiscard.fire(); - } - - return; - } - - this._updateSummaryMessage(hunks.length); - - // create enough zones - const handleDiff = () => this._updateDiffZones(); - - type Data = { position: Position; distance: number; accept: Function; discard: Function }; - let nearest: Data | undefined; - - // create enough zones - while (hunks.length > this._diffZonePool.length) { - this._diffZonePool.push(this._instaService.createInstance(InlineChatLivePreviewWidget, this._editor, this._session, {}, this._diffZonePool.length === 0 ? handleDiff : undefined)); - } - - for (let i = 0; i < hunks.length; i++) { - const hunk = hunks[i]; - this._diffZonePool[i].showForChanges(hunk); - - const modifiedRange = hunk.getRangesN()[0]; - const zoneLineNumber = this._zone.position!.lineNumber; - const distance = zoneLineNumber <= modifiedRange.startLineNumber - ? modifiedRange.startLineNumber - zoneLineNumber - : zoneLineNumber - modifiedRange.endLineNumber; - - if (!nearest || nearest.distance > distance) { - nearest = { - position: modifiedRange.getStartPosition().delta(-1), - distance, - accept: () => { - hunk.acceptChanges(); - handleDiff(); - }, - discard: () => { - hunk.discardChanges(); - handleDiff(); - } - }; - } - - } - // hide unused zones - for (let i = hunks.length; i < this._diffZonePool.length; i++) { - this._diffZonePool[i].hide(); - } - - this.acceptHunk = async () => nearest?.accept(); - this.discardHunk = async () => nearest?.discard(); - - if (nearest) { - this._zone.updatePositionAndHeight(nearest.position); - this._editor.revealPositionInCenterIfOutsideViewport(nearest.position); - } - - return nearest?.position; - } - - override hasFocus(): boolean { - return this._zone.widget.hasFocus() - || Boolean(this._previewZone.rawValue?.hasFocus()) - || this._diffZonePool.some(zone => zone.isVisible && zone.hasFocus()); - } -} type HunkDisplayData = { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index dbf95c3bb0bbb..10e6097adea5a 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -209,9 +209,7 @@ export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewR export const enum EditMode { Live = 'live', - Preview = 'preview', - /** @deprecated */ - LivePreview = 'livePreview', + Preview = 'preview' } Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f998c0969d7c0..6fd5722bc728b 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -512,18 +512,15 @@ suite('InteractiveChatController', function () { configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Live }); await makeRequest(); - configurationService.setUserConfiguration('inlineChat', { mode: EditMode.LivePreview }); - await makeRequest(); configurationService.setUserConfiguration('inlineChat', { mode: EditMode.Preview }); await makeRequest(); - assert.strictEqual(requests.length, 3); + assert.strictEqual(requests.length, 2); assert.strictEqual(requests[0].previewDocument.toString(), model.uri.toString()); // live - assert.strictEqual(requests[1].previewDocument.toString(), model.uri.toString()); // live preview - assert.strictEqual(requests[2].previewDocument.scheme, Schemas.vscode); // preview - assert.strictEqual(requests[2].previewDocument.authority, 'inline-chat'); + assert.strictEqual(requests[1].previewDocument.scheme, Schemas.vscode); // preview + assert.strictEqual(requests[1].previewDocument.authority, 'inline-chat'); }); test('start with existing exchange', async function () { From 10ddce6696c7a66b8f6d9965529ff00c97c967db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Cie=C5=9Blak?= Date: Fri, 23 Feb 2024 16:24:50 +0100 Subject: [PATCH 0592/1863] Fix off-by-one error in rendering removals in inline edits (#205890) --- src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts index da0fd80b5436b..298fcd4452e1f 100644 --- a/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineEdit/browser/ghostTextWidget.ts @@ -176,7 +176,7 @@ export class GhostTextWidget extends Disposable { } else { const lines = uiState.range.endLineNumber - uiState.range.startLineNumber; - for (let i = 0; i <= lines; i++) { + for (let i = 0; i < lines; i++) { const line = uiState.range.startLineNumber + i; const firstNonWhitespace = uiState.targetTextModel.getLineFirstNonWhitespaceColumn(line); const lastNonWhitespace = uiState.targetTextModel.getLineLastNonWhitespaceColumn(line); From 18e68638bdb98413760e1b7a04af30e66a0d66ea Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 16:47:24 +0100 Subject: [PATCH 0593/1863] Fixes #204948 --- .../heuristicSequenceOptimizations.ts | 2 +- .../node/diffing/fixtures/issue-204948/1.txt | 9 ++++++++ .../node/diffing/fixtures/issue-204948/2.txt | 9 ++++++++ .../issue-204948/advanced.expected.diff.json | 22 +++++++++++++++++++ .../issue-204948/legacy.expected.diff.json | 22 +++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index 08efd57813629..fddfb1e0c6186 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -247,7 +247,7 @@ export function extendDiffsToEntireWordIfAppropriate(sequence1: LinesSliceCharSe while (equalMappings.length > 0) { const next = equalMappings[0]; - const intersects = next.seq1Range.intersects(w1) || next.seq2Range.intersects(w2); + const intersects = next.seq1Range.intersects(w.seq1Range) || next.seq2Range.intersects(w.seq2Range); if (!intersects) { break; } diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt b/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt new file mode 100644 index 0000000000000..42f5b92add2d0 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/1.txt @@ -0,0 +1,9 @@ + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt b/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt new file mode 100644 index 0000000000000..2b5867f0165f6 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/2.txt @@ -0,0 +1,9 @@ + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json new file mode 100644 index 0000000000000..4dd454f4a4513 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/advanced.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./1.txt" + }, + "modified": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,20 -> 7,1]", + "modifiedRange": "[6,20 -> 7,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json new file mode 100644 index 0000000000000..97e5b8d4e12cc --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-204948/legacy.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./1.txt" + }, + "modified": { + "content": " \"@babel/types\" \"^7.22.15\"\n\n\"@babel/traverse@^7.23.9\":\n version \"7.23.9\"\n resolved \"https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305\"\n integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==\n dependencies:\n \"@babel/code-frame\" \"^7.23.5\"\n \"@babel/generator\" \"^7.23.6\"", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,20 -> 6,105]", + "modifiedRange": "[6,20 -> 6,105]" + } + ] + } + ] +} \ No newline at end of file From c6eed5d1b50413f7e6de8c5837e7095321d27050 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 16:53:38 +0100 Subject: [PATCH 0594/1863] Stricter assert --- .../defaultLinesDiffComputer.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts index e2de212aa40cc..b7c34e0760486 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer.ts @@ -13,11 +13,11 @@ import { DateTimeout, ITimeout, InfiniteTimeout, SequenceDiff } from 'vs/editor/ import { DynamicProgrammingDiffing } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/dynamicProgrammingDiffing'; import { MyersDiffAlgorithm } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/myersDiffAlgorithm'; import { computeMovedLines } from 'vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines'; -import { extendDiffsToEntireWordIfAppropriate, optimizeSequenceDiffs, removeVeryShortMatchingLinesBetweenDiffs, removeVeryShortMatchingTextBetweenLongDiffs, removeShortMatches } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; +import { extendDiffsToEntireWordIfAppropriate, optimizeSequenceDiffs, removeShortMatches, removeVeryShortMatchingLinesBetweenDiffs, removeVeryShortMatchingTextBetweenLongDiffs } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; +import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; +import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; import { ILinesDiffComputer, ILinesDiffComputerOptions, LinesDiff, MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { DetailedLineRangeMapping, RangeMapping } from '../rangeMapping'; -import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; -import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; export class DefaultLinesDiffComputer implements ILinesDiffComputer { private readonly dynamicProgrammingDiffing = new DynamicProgrammingDiffing(); @@ -256,8 +256,11 @@ export function lineRangeMappingFromRangeMappings(alignments: RangeMapping[], or } assertFn(() => { - if (!dontAssertStartLine) { - if (changes.length > 0 && changes[0].original.startLineNumber !== changes[0].modified.startLineNumber) { + if (!dontAssertStartLine && changes.length > 0) { + if (changes[0].modified.startLineNumber !== changes[0].original.startLineNumber) { + return false; + } + if (modifiedLines.length - changes[changes.length - 1].modified.endLineNumberExclusive !== originalLines.length - changes[changes.length - 1].original.endLineNumberExclusive) { return false; } } From cdb1a962ccd40d6d03132e7974a835c98f6e82e6 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Fri, 23 Feb 2024 16:46:47 +0100 Subject: [PATCH 0595/1863] rename controller: don't throw on cancellation & more type-safety to avoid `MessageController#showMessage` throwing --- src/vs/editor/contrib/rename/browser/rename.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 8b7bc9b51b8bf..cb4f58e1073a6 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -6,7 +6,8 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { CancellationError, onUnexpectedError } from 'vs/base/common/errors'; +import { isMarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; @@ -192,9 +193,15 @@ class RenameController implements IEditorContribution { this._progressService.showWhile(resolveLocationOperation, 250); loc = await resolveLocationOperation; trace('resolved rename location'); - } catch (e) { - trace('resolve rename location failed', JSON.stringify(e, null, '\t')); - MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); + } catch (e: unknown) { + if (e instanceof CancellationError) { + trace('resolve rename location cancelled', JSON.stringify(e, null, '\t')); + } else { + trace('resolve rename location failed', e instanceof Error ? e : JSON.stringify(e, null, '\t')); + if (typeof e === 'string' || isMarkdownString(e)) { + MessageController.get(this.editor)?.showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position); + } + } return undefined; } finally { From 12997e68fd1620ff0cb2cbfae3f7390a0c1e9e13 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 18:14:21 +0100 Subject: [PATCH 0596/1863] Fixes #196084 (#206100) --- extensions/git/src/commands.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7f7f769fec5fa..01a8434c448ea 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1194,7 +1194,7 @@ export class CommandCenter { const activeTextEditor = window.activeTextEditor; // Must extract these now because opening a new document will change the activeTextEditor reference - const previousVisibleRange = activeTextEditor?.visibleRanges[0]; + const previousVisibleRanges = activeTextEditor?.visibleRanges; const previousURI = activeTextEditor?.document.uri; const previousSelection = activeTextEditor?.selection; @@ -1225,8 +1225,13 @@ export class CommandCenter { opts.selection = previousSelection; const editor = await window.showTextDocument(document, opts); // This should always be defined but just in case - if (previousVisibleRange) { - editor.revealRange(previousVisibleRange); + if (previousVisibleRanges && previousVisibleRanges.length > 0) { + let rangeToReveal = previousVisibleRanges[0]; + if (previousSelection && previousVisibleRanges.length > 1) { + // In case of multiple visible ranges, find the one that intersects with the selection + rangeToReveal = previousVisibleRanges.find(r => r.intersection(previousSelection)) ?? rangeToReveal; + } + editor.revealRange(rangeToReveal); } } } From bd4ba72a33caa9f52882bb695880790796e44f51 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 23 Feb 2024 18:52:11 +0100 Subject: [PATCH 0597/1863] Fixes #199290 (#206112) --- .../features/hideUnchangedRegionsFeature.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts index 6767a375a3c81..248f2b46d8168 100644 --- a/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts @@ -59,27 +59,25 @@ export class HideUnchangedRegionsFeature extends Disposable { super(); this._register(this._editors.original.onDidChangeCursorPosition(e => { - if (e.reason === CursorChangeReason.Explicit) { - const m = this._diffModel.get(); - transaction(tx => { - for (const s of this._editors.original.getSelections() || []) { - m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); - m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); - } - }); - } + if (e.reason === CursorChangeReason.ContentFlush) { return; } + const m = this._diffModel.get(); + transaction(tx => { + for (const s of this._editors.original.getSelections() || []) { + m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); + } + }); })); this._register(this._editors.modified.onDidChangeCursorPosition(e => { - if (e.reason === CursorChangeReason.Explicit) { - const m = this._diffModel.get(); - transaction(tx => { - for (const s of this._editors.modified.getSelections() || []) { - m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); - m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); - } - }); - } + if (e.reason === CursorChangeReason.ContentFlush) { return; } + const m = this._diffModel.get(); + transaction(tx => { + for (const s of this._editors.modified.getSelections() || []) { + m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, RevealPreference.FromCloserSide, tx); + m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, RevealPreference.FromCloserSide, tx); + } + }); })); const unchangedRegions = this._diffModel.map((m, reader) => { From 436af204d46a337b78b94423ba66fccefd93c7f3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 23 Feb 2024 11:00:23 -0700 Subject: [PATCH 0598/1863] Bump tas client (#206106) --- extensions/github-authentication/package.json | 2 +- extensions/github-authentication/yarn.lock | 48 +++--------- .../typescript-language-features/package.json | 2 +- .../typescript-language-features/yarn.lock | 77 +++---------------- 4 files changed, 20 insertions(+), 109 deletions(-) diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index dc26d5c07e845..d55e8dcfd03d1 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -61,7 +61,7 @@ "dependencies": { "node-fetch": "2.6.7", "@vscode/extension-telemetry": "^0.9.0", - "vscode-tas-client": "^0.1.47" + "vscode-tas-client": "^0.1.84" }, "devDependencies": { "@types/mocha": "^9.1.1", diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index a1c68b8d5a6b7..724b304c53ed3 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -132,15 +132,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -axios@^1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -153,11 +144,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -follow-redirects@^1.15.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - form-data@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" @@ -167,15 +153,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" @@ -195,29 +172,22 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -tas-client@0.1.73: - version "0.1.73" - resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.73.tgz#2dacf68547a37989ef1554c6510dc108a1ea7a71" - integrity sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w== - dependencies: - axios "^1.6.1" +tas-client@0.2.33: + version "0.2.33" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.2.33.tgz#451bf114a8a64748030ce4068ab7d079958402e6" + integrity sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg== tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -vscode-tas-client@^0.1.47: - version "0.1.75" - resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz#771780a9a178163028299f52d41973300060dd38" - integrity sha512-/+ALFWPI4U3obeRvLFSt39guT7P9bZQrkmcLoiS+2HtzJ/7iPKNt5Sj+XTiitGlPYVFGFc0plxX8AAp6Uxs0xQ== +vscode-tas-client@^0.1.84: + version "0.1.84" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz#906bdcfd8c9e1dc04321d6bc0335184f9119968e" + integrity sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w== dependencies: - tas-client "0.1.73" + tas-client "0.2.33" webidl-conversions@^3.0.0: version "3.0.1" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 9a5f8ff1f86dc..34dad264553fd 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -45,7 +45,7 @@ "@vscode/ts-package-manager": "^0.0.2", "jsonc-parser": "^3.2.0", "semver": "7.5.2", - "vscode-tas-client": "^0.1.63", + "vscode-tas-client": "^0.1.84", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 482239a3cbb79..bc72fe4cb8b72 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -140,46 +140,6 @@ resolved "https://registry.yarnpkg.com/@vscode/ts-package-manager/-/ts-package-manager-0.0.2.tgz#d1cade5ff0d01da8c5b5b00bf79d80e7156771cf" integrity sha512-cXPxGbPVTkEQI8mUiWYUwB6j3ga6M9i7yubUOCrjgZ01GeZPMSnaWRprfJ09uuy81wJjY2gfHgLsOgwrGvUBTw== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^1.6.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -follow-redirects@^1.15.0: - version "1.15.4" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" - integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -192,23 +152,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - semver@7.5.2: version "7.5.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" @@ -216,19 +159,17 @@ semver@7.5.2: dependencies: lru-cache "^6.0.0" -tas-client@0.1.73: - version "0.1.73" - resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.73.tgz#2dacf68547a37989ef1554c6510dc108a1ea7a71" - integrity sha512-UDdUF9kV2hYdlv+7AgqP2kXarVSUhjK7tg1BUflIRGEgND0/QoNpN64rcEuhEcM8AIbW65yrCopJWqRhLZ3m8w== - dependencies: - axios "^1.6.1" +tas-client@0.2.33: + version "0.2.33" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.2.33.tgz#451bf114a8a64748030ce4068ab7d079958402e6" + integrity sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg== -vscode-tas-client@^0.1.63: - version "0.1.75" - resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.75.tgz#771780a9a178163028299f52d41973300060dd38" - integrity sha512-/+ALFWPI4U3obeRvLFSt39guT7P9bZQrkmcLoiS+2HtzJ/7iPKNt5Sj+XTiitGlPYVFGFc0plxX8AAp6Uxs0xQ== +vscode-tas-client@^0.1.84: + version "0.1.84" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz#906bdcfd8c9e1dc04321d6bc0335184f9119968e" + integrity sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w== dependencies: - tas-client "0.1.73" + tas-client "0.2.33" vscode-uri@3.0.3: version "3.0.3" From 3bee3f48a5880e65c7525be0ff77042b7d295bdf Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 10:30:11 -0800 Subject: [PATCH 0599/1863] fix: cwd not escaping when running wt.exe externally (#206113) We actually already set the cwd when spawning wt.exe, so we can use just `.` to reference it and avoid dealing with escaping entirely. --- .../platform/externalTerminal/node/externalTerminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 9fd3892972607..a8df823266ab0 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -107,7 +107,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl // prefer to use the window terminal to spawn if it's available instead // of start, since that allows ctrl+c handling (#81322) spawnExec = wt; - cmdArgs = ['-d', dir || '.', exec, '/c', command]; // default dir fixes #204039 + cmdArgs = ['-d', '.', exec, '/c', command]; } else { spawnExec = WindowsExternalTerminalService.CMD; cmdArgs = ['/c', 'start', title, '/wait', exec, '/c', command]; From 25bc3dfec4e6d38d4899b98135e0bb369b6db51f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 23 Feb 2024 10:37:02 -0800 Subject: [PATCH 0600/1863] fix: use `CancellationToken.None` (#206115) --- src/vs/workbench/api/browser/mainThreadShare.ts | 4 ++-- .../contrib/editSessions/test/browser/editSessions.test.ts | 4 ++-- src/vs/workbench/contrib/share/browser/share.contribution.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadShare.ts b/src/vs/workbench/api/browser/mainThreadShare.ts index 1974180b331d9..d517c23c906bb 100644 --- a/src/vs/workbench/api/browser/mainThreadShare.ts +++ b/src/vs/workbench/api/browser/mainThreadShare.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 { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ExtHostContext, ExtHostShareShape, IDocumentFilterDto, MainContext, MainThreadShareShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -31,7 +31,7 @@ export class MainThreadShare implements MainThreadShareShape { selector, priority, provideShare: async (item: IShareableItem) => { - const result = await this.proxy.$provideShare(handle, item, new CancellationTokenSource().token); + const result = await this.proxy.$provideShare(handle, item, CancellationToken.None); return typeof result === 'string' ? result : URI.revive(result); } }; diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index 4ad6b2fe90026..c6be96b073da1 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -37,7 +37,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IDialogService, IPrompt } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService, ISaveAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -213,7 +213,7 @@ suite('Edit session sync', () => { // Create root folder await fileService.createFolder(folderUri); - await editSessionsContribution.storeEditSession(true, new CancellationTokenSource().token); + await editSessionsContribution.storeEditSession(true, CancellationToken.None); // Verify that we did not attempt to write the edit session assert.equal(writeStub.called, false); diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index ae07114161026..5bdf93d7236b7 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./share'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -110,7 +110,7 @@ class ShareWorkbenchContribution { const result = await progressService.withProgress({ location: ProgressLocation.Window, detail: localize('generating link', 'Generating link...') - }, async () => shareService.provideShare({ resourceUri, selection }, new CancellationTokenSource().token)); + }, async () => shareService.provideShare({ resourceUri, selection }, CancellationToken.None)); if (result) { const uriText = result.toString(); From ec72162ee60247944ec2faa16a08ce0ac580de76 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 23 Feb 2024 11:45:43 -0700 Subject: [PATCH 0601/1863] Deregister before registering to prevent view collisions (#206117) --- src/vs/workbench/contrib/files/browser/explorerViewlet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9785e04a5b126..0a79bee3fc64a 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -94,12 +94,12 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } } - if (viewDescriptorsToRegister.length) { - viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); - } if (viewDescriptorsToDeregister.length) { viewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER); } + if (viewDescriptorsToRegister.length) { + viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); + } mark('code/didRegisterExplorerViews'); } From ef1836b20c47096ef173f82783cfb01568e82f77 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:37:30 -0800 Subject: [PATCH 0602/1863] Exit early when object is disposed Fixes #206116 --- .../stickyScroll/browser/terminalStickyScrollOverlay.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 8868f63beef1a..4dc0908e54ea9 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -95,6 +95,9 @@ export class TerminalStickyScrollOverlay extends Disposable { // Eagerly create the overlay xtermCtor.then(ctor => { + if (this._store.isDisposed) { + return; + } this._stickyScrollOverlay = this._register(new ctor({ rows: 1, cols: this._xterm.raw.cols, From 45e763b40ca1e5d45e92b66ad04d02f320f0a64b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 23 Feb 2024 21:52:10 +0100 Subject: [PATCH 0603/1863] Configure web server base path (#202491) * add support for custom root URL path through command line option `--route-base-url-path` * fix CI/CD errors * refine path joining in `RemoteAuthoritiesImpl` * revert changes to `.gitignore` * avoid RemotePaths global * polish. Option is now called `server-base-path` * revert uri changes * remove unnecessary file * remove unnecessary new line * revert address port change --------- Co-authored-by: sysadmin <> Co-authored-by: Jared C <28533997+oakaigh@users.noreply.github.com> Co-authored-by: sysadmin --- src/vs/base/common/network.ts | 19 ++++++++++++++++--- .../common/extensionResourceLoader.ts | 9 ++++----- .../browser/remoteAuthorityResolverService.ts | 5 +++-- .../remote/common/remoteAgentConnection.ts | 4 ++-- src/vs/platform/remote/common/remoteHosts.ts | 9 --------- .../remoteAuthorityResolverService.ts | 3 +-- .../node/remoteExtensionHostAgentServer.ts | 18 ++++++++++++------ .../server/node/serverEnvironmentService.ts | 7 +++++++ src/vs/server/node/webClientServer.ts | 10 ++++++---- src/vs/workbench/browser/web.api.ts | 7 +++++++ src/vs/workbench/browser/web.main.ts | 4 ++-- .../test/browser/configurationService.test.ts | 6 +++--- .../test/browser/extensionService.test.ts | 2 +- 13 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 974d0c217439f..5cbdad174b7be 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -7,6 +7,7 @@ import * as errors from 'vs/base/common/errors'; import * as platform from 'vs/base/common/platform'; import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import * as paths from 'vs/base/common/path'; export namespace Schemas { @@ -144,7 +145,7 @@ class RemoteAuthoritiesImpl { private readonly _connectionTokens: { [authority: string]: string | undefined } = Object.create(null); private _preferredWebSchema: 'http' | 'https' = 'http'; private _delegate: ((uri: URI) => URI) | null = null; - private _remoteResourcesPath: string = `/${Schemas.vscodeRemoteResource}`; + private _serverRootPath: string = '/'; setPreferredWebSchema(schema: 'http' | 'https') { this._preferredWebSchema = schema; @@ -154,8 +155,16 @@ class RemoteAuthoritiesImpl { this._delegate = delegate; } - setServerRootPath(serverRootPath: string): void { - this._remoteResourcesPath = `${serverRootPath}/${Schemas.vscodeRemoteResource}`; + setServerRootPath(product: { quality?: string; commit?: string }, serverBasePath: string | undefined): void { + this._serverRootPath = getServerRootPath(product, serverBasePath); + } + + getServerRootPath(): string { + return this._serverRootPath; + } + + private get _remoteResourcesPath(): string { + return paths.posix.join(this._serverRootPath, Schemas.vscodeRemoteResource); } set(authority: string, host: string, port: number): void { @@ -202,6 +211,10 @@ class RemoteAuthoritiesImpl { export const RemoteAuthorities = new RemoteAuthoritiesImpl(); +export function getServerRootPath(product: { quality?: string; commit?: string }, basePath: string | undefined): string { + return paths.posix.join(basePath ?? '/', `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`); +} + /** * A string pointing to a path inside the app. It should not begin with ./ or ../ */ diff --git a/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts b/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts index e63c48d0c2ffb..f1660961c5826 100644 --- a/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts +++ b/src/vs/platform/extensionResourceLoader/common/extensionResourceLoader.ts @@ -17,10 +17,9 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { RemoteAuthorities } from 'vs/base/common/network'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; -const WEB_EXTENSION_RESOURCE_END_POINT = 'web-extension-resource'; +const WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT = '/web-extension-resource/'; export const IExtensionResourceLoaderService = createDecorator('extensionResourceLoaderService'); @@ -67,7 +66,6 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi readonly _serviceBrand: undefined; - private readonly _webExtensionResourceEndPoint: string; private readonly _extensionGalleryResourceUrlTemplate: string | undefined; private readonly _extensionGalleryAuthority: string | undefined; @@ -78,7 +76,6 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi private readonly _environmentService: IEnvironmentService, private readonly _configurationService: IConfigurationService, ) { - this._webExtensionResourceEndPoint = `${getRemoteServerRootPath(_productService)}/${WEB_EXTENSION_RESOURCE_END_POINT}/`; if (_productService.extensionsGallery) { this._extensionGalleryResourceUrlTemplate = _productService.extensionsGallery.resourceUrlTemplate; this._extensionGalleryAuthority = this._extensionGalleryResourceUrlTemplate ? this._getExtensionGalleryAuthority(URI.parse(this._extensionGalleryResourceUrlTemplate)) : undefined; @@ -144,7 +141,9 @@ export abstract class AbstractExtensionResourceLoaderService implements IExtensi } protected _isWebExtensionResourceEndPoint(uri: URI): boolean { - return uri.path.startsWith(this._webExtensionResourceEndPoint); + const uriPath = uri.path, serverRootPath = RemoteAuthorities.getServerRootPath(); + // test if the path starts with the server root path followed by the web extension resource end point segment + return uriPath.startsWith(serverRootPath) && uriPath.startsWith(WEB_EXTENSION_RESOURCE_END_POINT_SEGMENT, serverRootPath.length); } } diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 529d9d74999ec..8b85c237151ca 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult, WebSocketRemoteConnection, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; +import { parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -34,6 +34,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot isWorkbenchOptionsBasedResolution: boolean, connectionToken: Promise | string | undefined, resourceUriProvider: ((uri: URI) => URI) | undefined, + serverBasePath: string | undefined, @IProductService productService: IProductService, @ILogService private readonly _logService: ILogService, ) { @@ -44,7 +45,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot if (resourceUriProvider) { RemoteAuthorities.setDelegate(resourceUriProvider); } - RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); + RemoteAuthorities.setServerRootPath(productService, serverBasePath); } async resolveAuthority(authority: string): Promise { diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 9539650dec0e4..45ebbe8df04ad 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -9,6 +9,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { RemoteAuthorities } from 'vs/base/common/network'; import * as performance from 'vs/base/common/performance'; import { StopWatch } from 'vs/base/common/stopwatch'; import { generateUuid } from 'vs/base/common/uuid'; @@ -17,7 +18,6 @@ import { Client, ISocket, PersistentProtocol, SocketCloseEventType } from 'vs/ba import { ILogService } from 'vs/platform/log/common/log'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { RemoteAuthorityResolverError, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -232,7 +232,7 @@ async function connectToRemoteExtensionHostAgent(opt let socket: ISocket; try { - socket = await createSocket(options.logService, options.remoteSocketFactoryService, options.connectTo, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); + socket = await createSocket(options.logService, options.remoteSocketFactoryService, options.connectTo, RemoteAuthorities.getServerRootPath(), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken); } catch (error) { options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`); options.logService.error(error); diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index ccc99953c8d48..ccf58f9accbad 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -25,15 +25,6 @@ export function getRemoteName(authority: string | undefined): string | undefined return authority.substr(0, pos); } -/** - * The root path to use when accessing the remote server. The path contains the quality and commit of the current build. - * @param product - * @returns - */ -export function getRemoteServerRootPath(product: { quality?: string; commit?: string }): string { - return `/${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`; -} - export function parseAuthorityWithPort(authority: string): { host: string; port: number } { const { host, port } = parseAuthority(authority); if (typeof port === 'undefined') { diff --git a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts index debbe333ae8bd..9948495f89862 100644 --- a/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-sandbox/remoteAuthorityResolverService.ts @@ -11,7 +11,6 @@ import { RemoteAuthorities } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRemoteAuthorityResolverService, IRemoteConnectionData, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ElectronRemoteResourceLoader } from 'vs/platform/remote/electron-sandbox/electronRemoteResourceLoader'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -33,7 +32,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot this._canonicalURIRequests = new Map(); this._canonicalURIProvider = null; - RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService)); + RemoteAuthorities.setServerRootPath(productService, undefined); // on the desktop we don't support custom server base paths } resolveAuthority(authority: string): Promise { diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index a90d28e82cf6b..84664bbb39a87 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -15,7 +15,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { isEqualOrParent } from 'vs/base/common/extpath'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { connectionTokenQueryName, FileAccess, Schemas } from 'vs/base/common/network'; +import { connectionTokenQueryName, FileAccess, getServerRootPath, Schemas } from 'vs/base/common/network'; import { dirname, join } from 'vs/base/common/path'; import * as perf from 'vs/base/common/performance'; import * as platform from 'vs/base/common/platform'; @@ -33,7 +33,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ConnectionType, ConnectionTypeRequest, ErrorMessage, HandshakeMessage, IRemoteExtensionHostStartParams, ITunnelConnectionStartParams, SignRequest } from 'vs/platform/remote/common/remoteAgentConnection'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtensionHostConnection } from 'vs/server/node/extensionHostConnection'; import { ManagementConnection } from 'vs/server/node/remoteExtensionManagement'; @@ -75,6 +74,7 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { private readonly _connectionToken: ServerConnectionToken, private readonly _vsdaMod: typeof vsda | null, hasWebClient: boolean, + serverBasePath: string | undefined, @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, @IProductService private readonly _productService: IProductService, @ILogService private readonly _logService: ILogService, @@ -82,13 +82,13 @@ class RemoteExtensionHostAgentServer extends Disposable implements IServerAPI { ) { super(); - this._serverRootPath = getRemoteServerRootPath(_productService); + this._serverRootPath = getServerRootPath(_productService, serverBasePath); this._extHostConnections = Object.create(null); this._managementConnections = Object.create(null); this._allReconnectionTokens = new Set(); this._webClientServer = ( hasWebClient - ? this._instantiationService.createInstance(WebClientServer, this._connectionToken) + ? this._instantiationService.createInstance(WebClientServer, this._connectionToken, serverBasePath ?? '/', this._serverRootPath) : null ); this._logService.info(`Extension host agent started.`); @@ -665,6 +665,7 @@ export interface IServerAPI { } export async function createServer(address: string | net.AddressInfo | null, args: ServerParsedArgs, REMOTE_DATA_FOLDER: string): Promise { + const connectionToken = await determineServerConnectionToken(args); if (connectionToken instanceof ServerConnectionTokenParseError) { console.warn(connectionToken.message); @@ -774,15 +775,20 @@ export async function createServer(address: string | net.AddressInfo | null, arg return null; }); + let serverBasePath = args['server-base-path']; + if (serverBasePath && !serverBasePath.startsWith('/')) { + serverBasePath = `/${serverBasePath}`; + } + const hasWebClient = fs.existsSync(FileAccess.asFileUri('vs/code/browser/workbench/workbench.html').fsPath); if (hasWebClient && address && typeof address !== 'string') { // ships the web ui! const queryPart = (connectionToken.type !== ServerConnectionTokenType.None ? `?${connectionTokenQueryName}=${connectionToken.value}` : ''); - console.log(`Web UI available at http://localhost${address.port === 80 ? '' : `:${address.port}`}/${queryPart}`); + console.log(`Web UI available at http://localhost${address.port === 80 ? '' : `:${address.port}`}${serverBasePath ?? ''}${queryPart}`); } - const remoteExtensionHostAgentServer = instantiationService.createInstance(RemoteExtensionHostAgentServer, socketServer, connectionToken, vsdaMod, hasWebClient); + const remoteExtensionHostAgentServer = instantiationService.createInstance(RemoteExtensionHostAgentServer, socketServer, connectionToken, vsdaMod, hasWebClient, serverBasePath); perf.mark('code/server/ready'); const currentTime = performance.now(); diff --git a/src/vs/server/node/serverEnvironmentService.ts b/src/vs/server/node/serverEnvironmentService.ts index 900815a06d2d1..fce1842f1bd35 100644 --- a/src/vs/server/node/serverEnvironmentService.ts +++ b/src/vs/server/node/serverEnvironmentService.ts @@ -19,6 +19,7 @@ export const serverOptions: OptionDescriptions> = { 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, 'port': { type: 'string', cat: 'o', args: 'port | port range', description: nls.localize('port', "The port the server should listen to. If 0 is passed a random free port is picked. If a range in the format num-num is passed, a free port from the range (end inclusive) is selected.") }, 'socket-path': { type: 'string', cat: 'o', args: 'path', description: nls.localize('socket-path', "The path to a socket file for the server to listen to.") }, + 'server-base-path': { type: 'string', cat: 'o', args: 'path', description: nls.localize('server-base-path', "The path under which the web UI and the code server is provided. Defaults to '/'.`") }, 'connection-token': { type: 'string', cat: 'o', args: 'token', deprecates: ['connectionToken'], description: nls.localize('connection-token', "A secret that must be included with all requests.") }, 'connection-token-file': { type: 'string', cat: 'o', args: 'path', deprecates: ['connection-secret', 'connectionTokenFile'], description: nls.localize('connection-token-file', "Path to a file that contains the connection token.") }, 'without-connection-token': { type: 'boolean', cat: 'o', description: nls.localize('without-connection-token', "Run without a connection token. Only use this if the connection is secured by other means.") }, @@ -102,6 +103,12 @@ export interface ServerParsedArgs { port?: string; 'socket-path'?: string; + /** + * The path under which the web UI and the code server is provided. + * By defaults it is '/'.` + */ + 'server-base-path'?: string; + /** * A secret token that must be provided by the web client with all requests. * Use only `[0-9A-Za-z\-]`. diff --git a/src/vs/server/node/webClientServer.ts b/src/vs/server/node/webClientServer.ts index a46d6748d0b6c..feed129fc94e9 100644 --- a/src/vs/server/node/webClientServer.ts +++ b/src/vs/server/node/webClientServer.ts @@ -28,7 +28,6 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { IProductConfiguration } from 'vs/base/common/product'; import { isString } from 'vs/base/common/types'; import { CharCode } from 'vs/base/common/charCode'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; const textMimeType = { @@ -104,13 +103,15 @@ export class WebClientServer { constructor( private readonly _connectionToken: ServerConnectionToken, + private readonly _basePath: string, + readonly serverRootPath: string, @IServerEnvironmentService private readonly _environmentService: IServerEnvironmentService, @ILogService private readonly _logService: ILogService, @IRequestService private readonly _requestService: IRequestService, @IProductService private readonly _productService: IProductService, ) { this._webExtensionResourceUrlTemplate = this._productService.extensionsGallery?.resourceUrlTemplate ? URI.parse(this._productService.extensionsGallery.resourceUrlTemplate) : undefined; - const serverRootPath = getRemoteServerRootPath(_productService); + this._staticRoute = `${serverRootPath}/static`; this._callbackRoute = `${serverRootPath}/callback`; this._webExtensionRoute = `${serverRootPath}/web-extension-resource`; @@ -128,7 +129,7 @@ export class WebClientServer { if (pathname.startsWith(this._staticRoute) && pathname.charCodeAt(this._staticRoute.length) === CharCode.Slash) { return this._handleStatic(req, res, parsedUrl); } - if (pathname === '/') { + if (pathname === this._basePath) { return this._handleRoot(req, res, parsedUrl); } if (pathname === this._callbackRoute) { @@ -262,7 +263,7 @@ export class WebClientServer { newQuery[key] = parsedUrl.query[key]; } } - const newLocation = url.format({ pathname: '/', query: newQuery }); + const newLocation = url.format({ pathname: parsedUrl.pathname, query: newQuery }); responseHeaders['Location'] = newLocation; res.writeHead(302, responseHeaders); @@ -326,6 +327,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, + remoteBaseUrl: this._basePath, _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 50d76a2213c46..196edf88796d8 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -142,6 +142,13 @@ export interface IWorkbenchConstructionOptions { */ readonly remoteAuthority?: string; + /** + * The server base path is the path where the workbench is served from. + * The path must be absolute (start with a slash). + * Corresponds to option `server-base-path` on the server side. + */ + readonly serverBasePath?: string; + /** * The connection token to send to the server. */ diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 9061af27859af..b36400ec9f15b 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -284,11 +284,11 @@ export class BrowserMain extends Disposable { // Register them early because they are needed for the profiles initialization await this.registerIndexedDBFileSystemProviders(environmentService, fileService, logService, loggerService, logsPath); - // Remote + const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName); const remoteResourceLoader = this.configuration.remoteResourceProvider ? new BrowserRemoteResourceLoader(fileService, this.configuration.remoteResourceProvider) : undefined; const resourceUriProvider = this.configuration.resourceUriProvider ?? remoteResourceLoader?.getResourceUriProvider(); - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, resourceUriProvider, productService, logService); + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, resourceUriProvider, this.configuration.serverBasePath, productService, logService); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 0ca8625187eb9..a7ef7154baa46 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -101,7 +101,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, environmentService, TestProductService, - disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), + disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), @@ -152,7 +152,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, userDataProfilesService, fileService, - disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), + disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), new NullPolicyService())); @@ -184,7 +184,7 @@ suite('WorkspaceContextService - Folder', () => { userDataProfileService, userDataProfilesService, fileService, - disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), + disposables.add(new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, disposables.add(new RemoteAuthorityResolverService(false, undefined, undefined, undefined, TestProductService, logService)), new SignService(TestProductService), new NullLogService())), uriIdentityService, new NullLogService(), new NullPolicyService())); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 846822893134e..3ced43a5f42ae 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -250,7 +250,7 @@ suite('ExtensionService', () => { [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], - [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, testProductService, new NullLogService())] + [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, undefined, testProductService, new NullLogService())] ])); extService = instantiationService.get(IExtensionService); }); From 9854e101dd46a652155d0af52224f377f5fa5c0a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 23 Feb 2024 13:29:43 -0800 Subject: [PATCH 0604/1863] fix #204395 --- .../contrib/accessibility/browser/accessibleView.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index cfbfa70a2423c..3e90ba4b1a8bd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -13,7 +13,7 @@ import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; @@ -458,7 +458,7 @@ export class AccessibleView extends Disposable { const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; this._updateContextKeys(provider, true); - + const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus(); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { if (!model) { return; @@ -483,6 +483,11 @@ export class AccessibleView extends Disposable { } else if (actionsHint) { ariaLabel = localize('accessibility-help-hint', "Accessibility Help, {0}", actionsHint); } + if (isWindows && widgetIsFocused) { + // prevent the screen reader on windows from reading + // the aria label again when it's refocused + ariaLabel = ''; + } this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); if (this._currentProvider?.options.position) { From e6132eaeff5fbf19b01f82cda7601c337e8d17ed Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:48:39 -0800 Subject: [PATCH 0605/1863] Preview selected detected link Fixes #206126 --- .../links/browser/terminalLink.ts | 4 ++ .../browser/terminalLinkDetectorAdapter.ts | 6 +- .../links/browser/terminalLinkQuickpick.ts | 69 ++++++++++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts index 4654d703f1d9f..84d9ca4fdd2c7 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts @@ -13,6 +13,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IHoverAction } from 'vs/platform/hover/browser/hover'; +import type { URI } from 'vs/base/common/uri'; +import type { IParsedLink } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; export class TerminalLink extends DisposableStore implements ILink { decorations: ILinkDecorations; @@ -30,6 +32,8 @@ export class TerminalLink extends DisposableStore implements ILink { private readonly _xterm: Terminal, readonly range: IBufferRange, readonly text: string, + readonly uri: URI | undefined, + readonly parsedLink: IParsedLink | undefined, readonly actions: IHoverAction[] | undefined, private readonly _viewportY: number, private readonly _activateCallback: (event: MouseEvent | undefined, uri: string) => Promise, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts index 8226841a9a027..26c047ea42df3 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkDetectorAdapter.ts @@ -92,9 +92,7 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv const detectedLinks = await this._detector.detect(lines, startLine, endLine); for (const link of detectedLinks) { - links.push(this._createTerminalLink(link, async (event) => { - this._onDidActivateLink.fire({ link, event }); - })); + links.push(this._createTerminalLink(link, async (event) => this._onDidActivateLink.fire({ link, event }))); } return links; @@ -110,6 +108,8 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv this._detector.xterm, l.bufferRange, l.text, + l.uri, + l.parsedLink, l.actions, this._detector.xterm.buffer.active.viewportY, activateCallback, diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index e954be537df57..aafd2e45711cd 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -6,24 +6,37 @@ import { EventType } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; -import { QuickPickItem, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +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 type { ILink } from '@xterm/xterm'; 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 { 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 type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; export class TerminalLinkQuickpick extends DisposableStore { + private readonly _editorSequencer = new Sequencer(); + private readonly _editorViewState: EditorViewState; + private readonly _onDidRequestMoreLinks = this.add(new Emitter()); readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( + @IEditorService private readonly _editorService: IEditorService, + @IHistoryService private readonly _historyService: IHistoryService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { super(); + this._editorViewState = new EditorViewState(_editorService); } async show(links: { viewport: IDetectedLinks; all: Promise }): Promise { @@ -57,6 +70,9 @@ export class TerminalLinkQuickpick extends DisposableStore { pick.placeholder = localize('terminal.integrated.openDetectedLink', "Select the link to open, type to filter all links"); pick.sortByLabel = false; pick.show(); + if (pick.activeItems.length > 0) { + this._previewItem(pick.activeItems[0]); + } // Show all results only when filtering begins, this is done so the quick pick will show up // ASAP with only the viewport entries. @@ -93,8 +109,20 @@ export class TerminalLinkQuickpick extends DisposableStore { pick.items = picks; })); + disposables.add(pick.onDidChangeActive(async () => { + const [item] = pick.activeItems; + this._previewItem(item); + })); + return new Promise(r => { - disposables.add(pick.onDidHide(() => { + disposables.add(pick.onDidHide(({ reason }) => { + // 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 + // could mean the user clicked into the editor directly. + if (reason === QuickInputHideReason.Gesture) { + this._editorViewState.restore(true); + } disposables.dispose(); if (pick.selectedItems.length === 0) { this._accessibleViewService.showLastProvider(AccessibleViewProviderId.Terminal); @@ -132,10 +160,45 @@ export class TerminalLinkQuickpick extends DisposableStore { } return picks.length > 0 ? picks : undefined; } + + private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { + if (item && 'link' in item && item.link && 'uri' in item.link && item.link.uri) { + this._editorViewState.set(); + const link = item.link; + const uri = link.uri; + + + + const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); + let selection: ITextEditorSelection | undefined;// = link.selection; + if (!selection) { + selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; + } + + + 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: uri, + options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection } + }); + } finally { + disposable.dispose(); + } + }); + } + } } export interface ITerminalLinkQuickPickItem extends IQuickPickItem { - link: ILink; + link: ILink | TerminalLink; } type LinkQuickPickItem = ITerminalLinkQuickPickItem | QuickPickItem; From 228a35f333e8ca1f3fe78b8d5a5ca558eab870f7 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 23 Feb 2024 17:07:53 -0500 Subject: [PATCH 0606/1863] Fix accidental dedent for `in` and `when` dedent in Ruby comments --- extensions/ruby/language-configuration.json | 2 +- .../contrib/indentation/test/browser/indentation.test.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index e61f3ac410fbc..e1125e0bf2b8e 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -26,6 +26,6 @@ ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", - "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b)|((in|when)\\s)" + "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b|(in|when)\\s)" } } diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 91666a05d1920..516966c94777b 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -373,16 +373,23 @@ suite('Editor Contrib - Auto Dedent On Type', () => { ['(', ')'] ], indentationRules: { - decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b)|((in|when)\s)/, + decreaseIndentPattern: /^\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b|(in|when)\s)/, increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/, }, }); + viewModel.model.setValue(""); viewModel.type("def foo\n i"); viewModel.type("n", 'keyboard'); assert.strictEqual(model.getValue(), "def foo\n in"); viewModel.type(" ", 'keyboard'); assert.strictEqual(model.getValue(), "def foo\nin "); + + viewModel.model.setValue(""); + viewModel.type(" # in"); + assert.strictEqual(model.getValue(), " # in"); + viewModel.type(" ", 'keyboard'); + assert.strictEqual(model.getValue(), " # in "); improvedLanguageModel.dispose(); }); }); From c43cd02a59395b830e90ccaac720c0433c74371b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 23 Feb 2024 14:27:53 -0800 Subject: [PATCH 0607/1863] fix #203722 --- .../contrib/accessibility/browser/accessibleView.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index cfbfa70a2423c..1e7899de2d4ad 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -29,6 +29,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -157,7 +158,8 @@ export class AccessibleView extends Disposable { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, - @IMenuService private readonly _menuService: IMenuService + @IMenuService private readonly _menuService: IMenuService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -252,6 +254,10 @@ export class AccessibleView extends Disposable { } } + activateLink(): void { + this._commandService.executeCommand('editor.action.openLink'); + } + showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { return; @@ -509,7 +515,9 @@ export class AccessibleView extends Disposable { }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { - if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { + if (e.keyCode === KeyCode.Enter) { + this.activateLink(); + } else if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { hide(e); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { const url: string = provider.options.readMoreUrl; From db5fbe6fd930a7fe0a7fe03f432eb1aa71ca14bd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 23 Feb 2024 14:31:18 -0800 Subject: [PATCH 0608/1863] simplify --- .../contrib/accessibility/browser/accessibleView.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 1e7899de2d4ad..130efbcc20c65 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -254,9 +254,6 @@ export class AccessibleView extends Disposable { } } - activateLink(): void { - this._commandService.executeCommand('editor.action.openLink'); - } showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { @@ -516,7 +513,7 @@ export class AccessibleView extends Disposable { const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { if (e.keyCode === KeyCode.Enter) { - this.activateLink(); + this._commandService.executeCommand('editor.action.openLink'); } else if (e.keyCode === KeyCode.Escape || shouldHide(e.browserEvent, this._keybindingService, this._configurationService)) { hide(e); } else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) { From 9a07ceb9f7f1dc1803913105c816009cb4bfdec1 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 15:02:22 -0800 Subject: [PATCH 0609/1863] cli: ensure the canonical snap exe is used for the CLI (#206133) Fixes #204907 --- cli/src/commands/tunnels.rs | 7 +--- cli/src/util/machine.rs | 81 ++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index f9ae68830751b..02a697c179394 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -50,10 +50,7 @@ use crate::{ AuthRequired, Next, ServeStreamParams, ServiceContainer, ServiceManager, }, util::{ - app_lock::AppMutex, - command::new_std_command, - errors::{wrap, AnyError, CodeError}, - prereqs::PreReqChecker, + app_lock::AppMutex, command::new_std_command, errors::{wrap, AnyError, CodeError}, machine::canonical_exe, prereqs::PreReqChecker }, }; use crate::{ @@ -231,7 +228,7 @@ pub async fn service( legal::require_consent(&ctx.paths, args.accept_server_license_terms)?; let current_exe = - std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?; + canonical_exe().map_err(|e| wrap(e, "could not get current exe"))?; manager .register( diff --git a/cli/src/util/machine.rs b/cli/src/util/machine.rs index 4c7b6729e4325..a573231c2b986 100644 --- a/cli/src/util/machine.rs +++ b/cli/src/util/machine.rs @@ -3,7 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::{path::Path, time::Duration}; + use std::{ + ffi::OsString, + path::{Path, PathBuf}, + time::Duration, +}; use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; pub fn process_at_path_exists(pid: u32, name: &Path) -> bool { @@ -71,3 +75,78 @@ pub async fn wait_until_exe_deleted(current_exe: &Path, poll_ms: u64) { tokio::time::sleep(duration).await; } } + +/// Gets the canonical current exe location, referring to the "current" symlink +/// if running inside snap. +pub fn canonical_exe() -> std::io::Result { + canonical_exe_inner( + std::env::current_exe(), + std::env::var_os("SNAP"), + std::env::var_os("SNAP_REVISION"), + ) +} + +#[inline(always)] +#[allow(unused_variables)] +fn canonical_exe_inner( + exe: std::io::Result, + snap: Option, + rev: Option, +) -> std::io::Result { + let exe = exe?; + + #[cfg(target_os = "linux")] + if let (Some(snap), Some(rev)) = (snap, rev) { + if !exe.starts_with(snap) { + return Ok(exe); + } + + let mut out = PathBuf::new(); + for part in exe.iter() { + if part == rev { + out.push("current") + } else { + out.push(part) + } + } + + return Ok(out); + } + + Ok(exe) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + #[cfg(target_os = "linux")] + fn test_canonical_exe_in_snap() { + let exe = canonical_exe_inner( + Ok(PathBuf::from("/snap/my-snap/1234/some/exe")), + Some("/snap/my-snap/1234".into()), + Some("1234".into()), + ) + .unwrap(); + assert_eq!(exe, PathBuf::from("/snap/my-snap/current/some/exe")); + } + + #[test] + fn test_canonical_exe_not_in_snap() { + let exe = canonical_exe_inner( + Ok(PathBuf::from("/not-in-snap")), + Some("/snap/my-snap/1234".into()), + Some("1234".into()), + ) + .unwrap(); + assert_eq!(exe, PathBuf::from("/not-in-snap")); + } + + #[test] + fn test_canonical_exe_not_in_snap2() { + let exe = canonical_exe_inner(Ok(PathBuf::from("/not-in-snap")), None, None).unwrap(); + assert_eq!(exe, PathBuf::from("/not-in-snap")); + } +} From 47002157e96daf36dfebb911ae5724a6e7710e60 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 23 Feb 2024 15:18:47 -0800 Subject: [PATCH 0610/1863] debug: fix loading state in triggered bp widget (#206140) Fixes #204699 --- .../contrib/debug/browser/breakpointWidget.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 7289d25ba29ac..59a3a4dd4bfa1 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -265,30 +265,28 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private createTriggerBreakpointInput(container: HTMLElement) { const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp !== this.breakpoint); + const breakpointOptions: ISelectOptionItem[] = [ + { text: nls.localize('noTriggerByBreakpoint', 'None'), isDisabled: true }, + ...breakpoints.map(bp => ({ + text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}`, + description: nls.localize('triggerByLoading', 'Loading...') + })), + ]; const index = breakpoints.findIndex((bp) => this.breakpoint?.triggeredBy === bp.getId()); - let select = 0; - if (index > -1) { - select = index + 1; - } - - Promise.all(breakpoints.map(async (bp): Promise => ({ - text: `${this.labelService.getUriLabel(bp.uri, { relative: true })}: ${bp.lineNumber}`, - description: await this.textModelService.createModelReference(bp.uri).then(ref => { + for (const [i, bp] of breakpoints.entries()) { + this.textModelService.createModelReference(bp.uri).then(ref => { try { - return ref.object.textEditorModel.getLineContent(bp.lineNumber).trim(); + breakpointOptions[i + 1].description = ref.object.textEditorModel.getLineContent(bp.lineNumber).trim(); } finally { ref.dispose(); } - }, () => undefined), - }))).then(breakpoints => { - selectBreakpointBox.setOptions([ - { text: nls.localize('noTriggerByBreakpoint', 'None') }, - ...breakpoints - ], select); - }); + }).catch(() => { + breakpointOptions[i + 1].description = nls.localize('noBpSource', 'Could not load source.'); + }); + } - const selectBreakpointBox = this.selectBreakpointBox = new SelectBox([{ text: nls.localize('triggerByLoading', 'Loading...'), isDisabled: true }], 0, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); + const selectBreakpointBox = this.selectBreakpointBox = new SelectBox(breakpointOptions, index + 1, this.contextViewService, defaultSelectBoxStyles, { ariaLabel: nls.localize('selectBreakpoint', 'Select breakpoint') }); selectBreakpointBox.onDidSelect(e => { if (e.index === 0) { this.triggeredByBreakpointInput = undefined; From d63202a5382aa104f5515ea09053a2a21a2587c6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 23 Feb 2024 16:05:26 -0800 Subject: [PATCH 0611/1863] Fix sharing copy paste data across editor groups (#206142) Fix sharing copy paste data across editor groups --- .../browser/copyPasteController.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index 10654d61a52b6..adc0684cfcaa6 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -56,13 +56,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi return editor.getContribution(CopyPasteController.ID); } - private readonly _editor: ICodeEditor; - - private _currentCopyOperation?: { + /** + * Global tracking the last copy operation. + * + * This is shared across all editors so that you can copy and paste between groups. + * + * TODO: figure out how to make this work with multiple windows + */ + private static _currentCopyOperation?: { readonly handle: string; readonly dataTransferPromise: CancelablePromise; }; + private readonly _editor: ICodeEditor; + private _currentPasteOperation?: CancelablePromise; private _pasteAsActionContext?: { readonly preferredId: string | undefined }; @@ -204,8 +211,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi return dataTransfer; }); - this._currentCopyOperation?.dataTransferPromise.cancel(); - this._currentCopyOperation = { handle: handle, dataTransferPromise: promise }; + CopyPasteController._currentCopyOperation?.dataTransferPromise.cancel(); + CopyPasteController._currentCopyOperation = { handle: handle, dataTransferPromise: promise }; } private async handlePaste(e: ClipboardEvent) { @@ -436,8 +443,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } private async mergeInDataFromCopy(dataTransfer: VSDataTransfer, metadata: CopyMetadata | undefined, token: CancellationToken): Promise { - if (metadata?.id && this._currentCopyOperation?.handle === metadata.id) { - const toMergeDataTransfer = await this._currentCopyOperation.dataTransferPromise; + if (metadata?.id && CopyPasteController._currentCopyOperation?.handle === metadata.id) { + const toMergeDataTransfer = await CopyPasteController._currentCopyOperation.dataTransferPromise; if (token.isCancellationRequested) { return; } From 660264a263fcfeb5e15604ecd8eaeb71ca98cd5f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:21:20 +0100 Subject: [PATCH 0612/1863] Adopt custom hover for extension and runtime view (#206154) adopt custom hover for extension and runtime view --- .../abstractRuntimeExtensionsEditor.ts | 12 ++++++--- .../extensions/browser/extensionEditor.ts | 24 ++++++++++++----- .../extensions/browser/extensionsWidgets.ts | 26 ++++++++++++++----- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index 991a3df035cb2..cb2c01bdf751a 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -5,6 +5,8 @@ import { $, Dimension, addDisposableListener, append, clearNode } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -364,13 +366,15 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } else { title = nls.localize('extensionActivating', "Extension is activating..."); } - data.activationTime.title = title; + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), data.activationTime, title)); clearNode(data.msgContainer); if (this._getUnresponsiveProfile(element.description.identifier)) { const el = $('span', undefined, ...renderLabelWithIcons(` $(alert) Unresponsive`)); - el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + const extensionHostFreezTitle = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), el, extensionHostFreezTitle)); + data.msgContainer.appendChild(el); } @@ -416,7 +420,9 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } if (accessData?.current) { const element = $('span', undefined, nls.localize('requests count', "{0} Requests: {1} (Session)", feature.label, accessData.current.count)); - element.title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + const title = nls.localize('requests count title', "Last request was {0}. Overall Requests: {1}", fromNow(accessData.current.lastAccessed, true, true), accessData.totalCount); + data.elementDisposables.push(setupCustomHover(getDefaultHoverDelegate('mouse'), element, title)); + data.msgContainer.appendChild(element); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 21986a4b70938..097f4a3c63461 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -5,6 +5,8 @@ import { $, Dimension, addDisposableListener, append, setParentFlowTo } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; +import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction } from 'vs/base/common/actions'; @@ -188,7 +190,8 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { private readonly element: HTMLElement; constructor(container: HTMLElement) { super(); - this.element = append(container, $('code.version', { title: localize('extension version', "Extension Version") })); + this.element = append(container, $('code.version')); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); this.render(); } render(): void { @@ -268,25 +271,30 @@ export class ExtensionEditor extends EditorPane { const details = append(header, $('.details')); const title = append(details, $('.title')); - const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name"), role: 'heading', tabIndex: 0 })); + const name = append(title, $('span.name.clickable', { role: 'heading', tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), name, localize('name', "Extension name"))); const versionWidget = new VersionWidget(title); - const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); + const preview = append(title, $('span.preview')); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), preview, localize('preview', "Preview"))); preview.textContent = localize('preview', "Preview"); const builtin = append(title, $('span.builtin')); builtin.textContent = localize('builtin', "Built-in"); const subtitle = append(details, $('.subtitle')); - const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { title: localize('publisher', "Publisher"), tabIndex: 0 })); + const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), publisher, localize('publisher', "Publisher"))); publisher.setAttribute('role', 'button'); const publisherDisplayName = append(publisher, $('.publisher-name')); const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $('.verified-publisher')), false); - const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); + const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count"))); const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, false); - const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); + const rating = append(append(subtitle, $('.subtitle-entry')), $('span.rating.clickable', { tabIndex: 0 })); + this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), rating, localize('rating', "Rating"))); rating.setAttribute('role', 'link'); // #132645 const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, rating, false); @@ -914,7 +922,9 @@ export class ExtensionEditor extends EditorPane { append(extensionResourcesContainer, $('.additional-details-title', undefined, localize('resources', "Resources"))); const resourcesElement = append(extensionResourcesContainer, $('.resources')); for (const [label, uri] of resources) { - this.transientDisposables.add(onClick(append(resourcesElement, $('a.resource', { title: uri.toString(), tabindex: '0' }, label)), () => this.openerService.open(uri))); + const resource = append(resourcesElement, $('a.resource', { tabindex: '0' }, label)); + this.transientDisposables.add(onClick(resource, () => this.openerService.open(uri))); + this.transientDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), resource, uri.toString())); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index a4a887e596af6..4ad3680b7d73e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -31,7 +31,7 @@ import { URI } from 'vs/base/common/uri'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import Severity from 'vs/base/common/severity'; -import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { Color } from 'vs/base/common/color'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -41,6 +41,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -124,6 +125,8 @@ export class InstallCountWidget extends ExtensionWidget { export class RatingsWidget extends ExtensionWidget { + private readonly containerHover: ICustomHover; + constructor( private container: HTMLElement, private small: boolean @@ -135,12 +138,13 @@ export class RatingsWidget extends ExtensionWidget { container.classList.add('small'); } + this.containerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), container, '')); + this.render(); } render(): void { this.container.innerText = ''; - this.container.title = ''; if (!this.extension) { return; @@ -159,7 +163,7 @@ export class RatingsWidget extends ExtensionWidget { } const rating = Math.round(this.extension.rating * 2) / 2; - this.container.title = localize('ratedLabel', "Average rating: {0} out of 5", rating); + this.containerHover.update(localize('ratedLabel', "Average rating: {0} out of 5", rating)); if (this.small) { append(this.container, $('span' + ThemeIcon.asCSSSelector(starFullIcon))); @@ -186,6 +190,7 @@ export class RatingsWidget extends ExtensionWidget { export class VerifiedPublisherWidget extends ExtensionWidget { private disposables = this._register(new DisposableStore()); + private readonly containerHover: ICustomHover; constructor( private container: HTMLElement, @@ -193,6 +198,7 @@ export class VerifiedPublisherWidget extends ExtensionWidget { @IOpenerService private readonly openerService: IOpenerService, ) { super(); + this.containerHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), container, '')); this.render(); } @@ -209,7 +215,7 @@ export class VerifiedPublisherWidget extends ExtensionWidget { if (!this.small) { verifiedPublisher.tabIndex = 0; - verifiedPublisher.title = `Verified Domain: ${this.extension.publisherDomain.link}`; + this.containerHover.update(`Verified Domain: ${this.extension.publisherDomain.link}`); verifiedPublisher.setAttribute('role', 'link'); append(verifiedPublisher, $('span.extension-verified-publisher-domain', undefined, publisherDomainLink.authority.startsWith('www.') ? publisherDomainLink.authority.substring(4) : publisherDomainLink.authority)); @@ -239,7 +245,8 @@ export class SponsorWidget extends ExtensionWidget { return; } - const sponsor = append(this.container, $('span.sponsor.clickable', { tabIndex: 0, title: this.extension?.publisherSponsorLink })); + const sponsor = append(this.container, $('span.sponsor.clickable', { tabIndex: 0 })); + this.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), sponsor, this.extension?.publisherSponsorLink.toString() ?? '')); sponsor.setAttribute('role', 'link'); // #132645 const sponsorIconElement = renderIcon(sponsorIcon); const label = $('span', undefined, localize('sponsor', "Sponsor")); @@ -367,6 +374,7 @@ export class RemoteBadgeWidget extends ExtensionWidget { class RemoteBadge extends Disposable { readonly element: HTMLElement; + readonly elementHover: ICustomHover; constructor( private readonly tooltip: boolean, @@ -376,6 +384,7 @@ class RemoteBadge extends Disposable { ) { super(); this.element = $('div.extension-badge.extension-remote-badge'); + this.elementHover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, '')); this.render(); } @@ -397,7 +406,7 @@ class RemoteBadge extends Disposable { if (this.tooltip) { const updateTitle = () => { if (this.element && this.extensionManagementServerService.remoteExtensionManagementServer) { - this.element.title = localize('remote extension title', "Extension in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label); + this.elementHover.update(localize('remote extension title', "Extension in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label)); } }; this._register(this.labelService.onDidChangeFormatters(() => updateTitle())); @@ -435,6 +444,8 @@ export class ExtensionPackCountWidget extends ExtensionWidget { export class SyncIgnoredWidget extends ExtensionWidget { + private readonly disposables = this._register(new DisposableStore()); + constructor( private readonly container: HTMLElement, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -448,11 +459,12 @@ export class SyncIgnoredWidget extends ExtensionWidget { } render(): void { + this.disposables.clear(); this.container.innerText = ''; if (this.extension && this.extension.state === ExtensionState.Installed && this.userDataSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) { const element = append(this.container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon))); - element.title = localize('syncingore.label', "This extension is ignored during sync."); + this.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), element, localize('syncingore.label', "This extension is ignored during sync."))); element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon)); } } From f10dd6906c031e1bfacfe6b1976883947688a636 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Feb 2024 07:35:39 -0800 Subject: [PATCH 0613/1863] Only preview detected links for files and when preview is on Part of #206126 --- .../links/browser/terminalLinkQuickpick.ts | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index aafd2e45711cd..6ceb6c3db3214 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -20,6 +20,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { getLinkSuffix } from 'vs/workbench/contrib/terminalContrib/links/browser/terminalLinkParsing'; import type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; +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'; export class TerminalLinkQuickpick extends DisposableStore { @@ -30,6 +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, @IQuickInputService private readonly _quickInputService: IQuickInputService, @@ -162,38 +166,45 @@ export class TerminalLinkQuickpick extends DisposableStore { } private _previewItem(item: ITerminalLinkQuickPickItem | IQuickPickItem) { - if (item && 'link' in item && item.link && 'uri' in item.link && item.link.uri) { - this._editorViewState.set(); - const link = item.link; - const uri = link.uri; - - - - const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); - let selection: ITextEditorSelection | undefined;// = link.selection; - if (!selection) { - selection = linkSuffix?.row === undefined ? undefined : { - startLineNumber: linkSuffix.row ?? 1, - startColumn: linkSuffix.col ?? 1, - endLineNumber: linkSuffix.rowEnd, - endColumn: linkSuffix.colEnd - }; - } + if (!item || !('link' in item) || !item.link || !('uri' in item.link) || !item.link.uri) { + return; + } + const link = item.link; + if (link.type !== TerminalBuiltinLinkType.LocalFile) { + return; + } - 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: uri, - options: { preserveFocus: true, revealIfOpened: true, ignoreError: true, selection } - }); - } finally { - disposable.dispose(); - } - }); + // 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; + } + + const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); + let selection: ITextEditorSelection | undefined;// = link.selection; + if (!selection) { + selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; } + + 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(); + } + }); } } From 523dd898669fd40d2587197655c33ee066f9f62a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Feb 2024 07:38:26 -0800 Subject: [PATCH 0614/1863] Simplify setting selection --- .../links/browser/terminalLinkQuickpick.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts index 6ceb6c3db3214..0cea7b3f51494 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkQuickpick.ts @@ -19,7 +19,6 @@ 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 type { ITextEditorSelection } from 'vs/platform/editor/common/editor'; 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'; @@ -182,15 +181,12 @@ export class TerminalLinkQuickpick extends DisposableStore { } const linkSuffix = link.parsedLink ? link.parsedLink.suffix : getLinkSuffix(link.text); - let selection: ITextEditorSelection | undefined;// = link.selection; - if (!selection) { - selection = linkSuffix?.row === undefined ? undefined : { - startLineNumber: linkSuffix.row ?? 1, - startColumn: linkSuffix.col ?? 1, - endLineNumber: linkSuffix.rowEnd, - endColumn: linkSuffix.colEnd - }; - } + const selection = linkSuffix?.row === undefined ? undefined : { + startLineNumber: linkSuffix.row ?? 1, + startColumn: linkSuffix.col ?? 1, + endLineNumber: linkSuffix.rowEnd, + endColumn: linkSuffix.colEnd + }; this._editorViewState.set(); this._editorSequencer.queue(async () => { From d2f340649b56500692ae84c82b8fac41f4bf8832 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:38:20 +0100 Subject: [PATCH 0615/1863] Adopt custom hover for inline chat widget (#206170) inline chat custom hover adoption --- src/vs/platform/actions/browser/buttonbar.ts | 19 ++++++++++++++----- .../inlineChat/browser/inlineChatWidget.ts | 10 ++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 21d4c4c5fc63e..94720d5b557b0 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -4,6 +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 { 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'; @@ -29,6 +31,7 @@ export interface IWorkbenchButtonBarOptions { export class WorkbenchButtonBar extends ButtonBar { protected readonly _store = new DisposableStore(); + protected readonly _updateStore = new DisposableStore(); private readonly _actionRunner: IActionRunner; private readonly _onDidChange = new Emitter(); @@ -57,6 +60,7 @@ export class WorkbenchButtonBar extends ButtonBar { override dispose() { this._onDidChange.dispose(); + this._updateStore.dispose(); this._store.dispose(); super.dispose(); } @@ -65,8 +69,12 @@ export class WorkbenchButtonBar extends ButtonBar { const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true })); + this._updateStore.clear(); this.clear(); + // Support instamt hover between buttons + const hoverDelegate = this._updateStore.add(getDefaultHoverDelegate('element', true)); + for (let i = 0; i < actions.length; i++) { const secondary = i > 0; @@ -107,15 +115,16 @@ export class WorkbenchButtonBar extends ButtonBar { } } const kb = this._keybindingService.lookupKeybinding(action.id); + let tooltip: string; if (kb) { - btn.element.title = localize('labelWithKeybinding', "{0} ({1})", action.label, kb.getLabel()); + tooltip = localize('labelWithKeybinding', "{0} ({1})", action.label, kb.getLabel()); } else { - btn.element.title = action.label; - + tooltip = action.label; } - btn.onDidClick(async () => { + this._updateStore.add(setupCustomHover(hoverDelegate, btn.element, tooltip)); + this._updateStore.add(btn.onDidClick(async () => { this._actionRunner.run(action); - }); + })); } this._onDidChange.fire(this); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 50bba01479eb3..084e20c5898d2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -66,6 +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'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -373,12 +374,16 @@ export class InlineChatWidget { this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); this._store.add(this._slashCommandContentWidget); + // Share hover delegates between toolbars to support instant hover between both + const hoverDelegate = this._store.add(getDefaultHoverDelegate('element', true)); + // toolbars this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, _options.menuId, { telemetrySource: 'interactiveEditorWidget-toolbar', toolbarOptions: { primaryGroup: 'main' }, - hiddenItemStrategy: HiddenItemStrategy.Ignore // keep it lean when hiding items and avoid a "..." overflow menu + hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu + hoverDelegate })); this._progressBar = new ProgressBar(this._elements.progress); @@ -387,7 +392,8 @@ export class InlineChatWidget { this._store.add(this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.widgetToolbar, _options.widgetMenuId, { telemetrySource: 'interactiveEditorWidget-toolbar', - toolbarOptions: { primaryGroup: 'main' } + toolbarOptions: { primaryGroup: 'main' }, + hoverDelegate })); const workbenchMenubarOptions: IWorkbenchButtonBarOptions = { From 03f6a7894e4f3b21a2f4936eeb1dc762606a5bd4 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 26 Feb 2024 02:20:16 +0100 Subject: [PATCH 0616/1863] Updated actionViewItemProvider to include options parameter (#206188) Updated actionViewItemProvider to include options parameter in various components --- .../workbench/browser/parts/compositeBar.ts | 4 +-- .../browser/parts/globalCompositeBar.ts | 5 ++-- .../notifications/notificationsCenter.ts | 3 ++- .../parts/titlebar/commandCenterControl.ts | 11 +++++--- .../browser/parts/titlebar/titlebarPart.ts | 7 ++--- .../browser/parts/views/viewFilter.ts | 5 ++-- .../contrib/comments/browser/commentNode.ts | 26 +++++++++---------- .../extensions/browser/extensionEditor.ts | 8 +++--- .../extensions/browser/extensionsActions.ts | 10 ++++--- .../extensions/browser/extensionsList.ts | 7 ++--- .../contrib/markers/browser/markersTable.ts | 2 +- .../markers/browser/markersTreeViewer.ts | 6 ++--- .../markers/browser/markersViewActions.ts | 8 +++--- .../contrib/find/notebookFindReplaceWidget.ts | 8 +++--- .../notebook/browser/diff/diffComponents.ts | 4 +-- .../notebook/browser/diff/notebookDiffList.ts | 4 +-- .../browser/view/cellParts/cellToolbars.ts | 6 ++--- .../view/cellParts/codeCellRunToolbar.ts | 3 ++- .../viewParts/notebookTopCellToolbar.ts | 4 +-- .../preferences/browser/keybindingsEditor.ts | 5 ++-- .../preferences/browser/preferencesWidgets.ts | 4 +-- .../preferences/browser/settingsEditor2.ts | 4 +-- .../preferences/browser/settingsSearchMenu.ts | 3 +++ .../terminal/browser/terminalTabsList.ts | 4 +-- .../testing/browser/testingExplorerFilter.ts | 7 ++--- .../testing/browser/testingOutputPeek.ts | 4 +-- 26 files changed, 91 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 76d6c0c1ee4ce..27d0b3738ea2b 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -201,14 +201,14 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof CompositeOverflowActivityAction) { return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( CompositeActionViewItem, - { draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions, compact: this.options.compact }, + { ...options, draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions, compact: this.options.compact }, action as CompositeBarAction, item.pinnedAction, item.toggleBadgeAction, diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index db3471fcadc44..3490432e31268 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -72,15 +72,16 @@ export class GlobalCompositeBar extends Disposable { anchorAxisAlignment: AnchorAxisAlignment.HORIZONTAL }); this.globalActivityActionBar = this._register(new ActionBar(this.element, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions); + return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { ...options, colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions); } if (action.id === ACCOUNTS_ACTIVITY_ID) { return this.instantiationService.createInstance(AccountsActivityActionViewItem, this.contextMenuActionsProvider, { + ...options, colors: this.colors, hoverOptions: this.activityHoverOptions }, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index e4cbaff22726c..40bcde5fdb480 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -173,7 +173,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente const notificationsToolBar = this._register(new ActionBar(toolbarContainer, { ariaLabel: localize('notificationsToolbar', "Notification Center Actions"), actionRunner, - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ConfigureDoNotDisturbAction.ID) { return this._register(this.instantiationService.createInstance(DropdownMenuActionViewItem, action, { getActions() { @@ -208,6 +208,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente return actions; }, }, this.contextMenuService, { + ...options, actionRunner, classNames: action.class, keybindingProvider: action => this.keybindingService.lookupKeybinding(action.id) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 3843068e941a3..72eeea0b59070 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -5,6 +5,7 @@ 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 { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; @@ -46,11 +47,11 @@ export class CommandCenterControl { primaryGroup: () => true, }, telemetrySource: 'commandCenter', - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action instanceof SubmenuItemAction && action.item.submenu === MenuId.CommandCenterCenter) { - return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, hoverDelegate, {}); + return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, { ...options, hoverDelegate }); } else { - return createActionViewItem(instantiationService, action, { hoverDelegate }); + return createActionViewItem(instantiationService, action, { ...options, hoverDelegate }); } } }); @@ -75,16 +76,18 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { private static readonly _quickOpenCommandId = 'workbench.action.quickOpenWithModes'; + private readonly _hoverDelegate: IHoverDelegate; + constructor( private readonly _submenu: SubmenuItemAction, private readonly _windowTitle: WindowTitle, - private readonly _hoverDelegate: IHoverDelegate, options: IBaseActionViewItemOptions, @IKeybindingService private _keybindingService: IKeybindingService, @IInstantiationService private _instaService: IInstantiationService, @IEditorGroupsService private _editorGroupService: IEditorGroupsService, ) { super(undefined, _submenu.actions.find(action => action.id === 'workbench.action.quickOpenWithModes') ?? _submenu.actions[0], options); + this._hoverDelegate = options.hoverDelegate ?? getDefaultHoverDelegate('mouse'); } override render(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 05ad43933f8bd..176ac265172c8 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -532,7 +532,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // --- Editor Actions const activeEditorPane = this.editorGroupsContainer.activeGroup?.activeEditorPane; if (activeEditorPane && activeEditorPane instanceof EditorPane) { - const result = activeEditorPane.getActionViewItem(action, { hoverDelegate: this.hoverDelegate }); + const result = activeEditorPane.getActionViewItem(action, options); if (result) { return result; @@ -540,7 +540,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } // Check extensions - return createActionViewItem(this.instantiationService, action, { ...options, hoverDelegate: this.hoverDelegate, menuAsChild: false }); + return createActionViewItem(this.instantiationService, action, { ...options, menuAsChild: false }); } private getKeybinding(action: IAction): ResolvedKeybinding | undefined { @@ -565,7 +565,8 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { anchorAlignmentProvider: () => AnchorAlignment.RIGHT, telemetrySource: 'titlePart', highlightToggledItems: this.editorActionsEnabled, // Only show toggled state for editor actions (Layout actions are not shown as toggled) - actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options) + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options), + hoverDelegate: this.hoverDelegate })); if (this.editorActionsEnabled) { diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index 3e5763a2203c6..b6285e45c7199 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -25,6 +25,7 @@ import { SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntr import { Widget } from 'vs/base/browser/ui/widget'; import { Emitter } from 'vs/base/common/event'; import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const viewFilterMenu = new MenuId('menu.view.filter'); export const viewFilterSubmenu = new MenuId('submenu.view.filter'); @@ -196,9 +197,9 @@ export class FilterWidget extends Widget { return this.instantiationService.createInstance(MenuWorkbenchToolBar, container, viewFilterMenu, { hiddenItemStrategy: HiddenItemStrategy.NoHide, - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action instanceof SubmenuItemAction && action.item.submenu.id === viewFilterSubmenu.id) { - this.moreFiltersActionViewItem = this.instantiationService.createInstance(MoreFiltersActionViewItem, action, undefined); + this.moreFiltersActionViewItem = this.instantiationService.createInstance(MoreFiltersActionViewItem, action, options); this.moreFiltersActionViewItem.checked = this.isMoreFiltersChecked; return this.moreFiltersActionViewItem; } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 7085518f94ad8..58e376f95610d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -30,7 +30,7 @@ import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -301,21 +301,22 @@ export class CommentNode extends Disposable { private createToolbar() { this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, { - actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + ...options, + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action as Action, options), actionRunner: this.actionRunner, classNames: ['toolbar-toggle-pickReactions', ...ThemeIcon.asClassNameArray(Codicon.reactions)], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); }, orientation: ActionsOrientation.HORIZONTAL }); @@ -357,8 +358,7 @@ export class CommentNode extends Disposable { } } - actionViewItemProvider(action: Action) { - let options = {}; + actionViewItemProvider(action: Action, options: IActionViewItemOptions) { if (action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; } else { @@ -369,9 +369,9 @@ export class CommentNode extends Disposable { const item = new ReactionActionViewItem(action); return item; } else if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } else if (action instanceof SubmenuItemAction) { - return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action, options); } else { const item = new ActionViewItem({}, action, options); return item; @@ -413,11 +413,11 @@ export class CommentNode extends Disposable { (toggleReactionAction).menuActions, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return toggleReactionActionViewItem; } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); }, actionRunner: this.actionRunner, classNames: 'toolbar-toggle-pickReactions', @@ -431,21 +431,21 @@ export class CommentNode extends Disposable { private createReactionsContainer(commentDetailsContainer: HTMLElement): void { this._reactionActionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); this._reactionsActionBar = new ActionBar(this._reactionActionsContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === ToggleReactionsAction.ID) { return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, { - actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + actionViewItemProvider: (action, options) => this.actionViewItemProvider(action as Action, options), actionRunner: this.actionRunner, classNames: ['toolbar-toggle-pickReactions', ...ThemeIcon.asClassNameArray(Codicon.reactions)], anchorAlignmentProvider: () => AnchorAlignment.RIGHT } ); } - return this.actionViewItemProvider(action as Action); + return this.actionViewItemProvider(action as Action, options); } }); this._register(this._reactionsActionBar); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 097f4a3c63461..11ee0d9a2b9c9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -344,15 +344,15 @@ export class ExtensionEditor extends EditorPane { const actionsAndStatusContainer = append(details, $('.actions-status-container')); const extensionActionBar = this._register(new ActionBar(actionsAndStatusContainer, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options) => { if (action instanceof ExtensionDropDownAction) { - return action.createActionViewItem(); + return action.createActionViewItem(options); } if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } if (action instanceof ToggleAutoUpdateForExtensionAction) { - return new CheckboxActionViewItem(undefined, action, { icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); + return new CheckboxActionViewItem(undefined, action, { ...options, icon: true, label: true, checkboxStyles: defaultCheckboxStyles }); } return undefined; }, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 1fc58a9e0f558..6e0155f3249eb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1021,8 +1021,8 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } private _actionViewItem: DropDownMenuActionViewItem | null = null; - createActionViewItem(): DropDownMenuActionViewItem { - this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this); + createActionViewItem(options: IActionViewItemOptions): DropDownMenuActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, options); return this._actionViewItem; } @@ -1034,10 +1034,12 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { export class DropDownMenuActionViewItem extends ActionViewItem { - constructor(action: ExtensionDropDownAction, + constructor( + action: ExtensionDropDownAction, + options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { - super(null, action, { icon: true, label: true }); + super(null, action, { ...options, icon: true, label: true }); } public showMenu(menuActionGroups: IAction[][], disposeActionsOnHide: boolean): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 1bf769f4c668a..d5058c51b43ae 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -26,6 +26,7 @@ import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { verifiedPublisherIcon as verifiedPublisherThemeIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const EXTENSION_LIST_ELEMENT_HEIGHT = 72; @@ -98,12 +99,12 @@ export class Renderer implements IPagedRenderer { const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $(`.verified-publisher`)), true); const publisherDisplayName = append(publisher, $('.publisher-name.ellipsis')); const actionbar = new ActionBar(footer, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action instanceof ActionWithDropDownAction) { - return new ExtensionActionWithDropdownActionViewItem(action, { icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); + return new ExtensionActionWithDropdownActionViewItem(action, { ...options, icon: true, label: true, menuActionsOrProvider: { getActions: () => action.menuActions }, menuActionClassNames: (action.class || '').split(' ') }, this.contextMenuService); } if (action instanceof ExtensionDropDownAction) { - return action.createActionViewItem(); + return action.createActionViewItem(options); } return undefined; }, diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index dc454d054ed89..bb63d92e9d2b6 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -74,7 +74,7 @@ class MarkerSeverityColumnRenderer implements ITableRenderer action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: IAction, options) => action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action, options) : undefined }); return { actionBar, icon }; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 4b7a07b3b178b..180a378ac813d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -295,7 +295,7 @@ class MarkerWidget extends Disposable { ) { super(); this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { - actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: IAction, options) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action, options) : undefined })); // wrap the icon in a container that get the icon color as foreground color. That way, if the @@ -342,9 +342,9 @@ class MarkerWidget extends Disposable { private renderMultilineActionbar(marker: Marker, parent: HTMLElement): void { const multilineActionbar = this.disposables.add(new ActionBar(dom.append(parent, dom.$('.multiline-actions')), { - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action.id === toggleMultilineAction) { - return new ToggleMultilineActionViewItem(undefined, action, { icon: true }); + return new ToggleMultilineActionViewItem(undefined, action, { ...options, icon: true }); } return undefined; } diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 818f2f56fe629..74fa00a81ae6a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -13,7 +13,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers'; import 'vs/css!./markersViewActions'; @@ -145,10 +145,12 @@ export class QuickFixAction extends Action { export class QuickFixActionViewItem extends ActionViewItem { - constructor(action: QuickFixAction, + constructor( + action: QuickFixAction, + options: IActionViewItemOptions, @IContextMenuService private readonly contextMenuService: IContextMenuService, ) { - super(null, action, { icon: true, label: false }); + super(null, action, { ...options, icon: true, label: false }); } public override onClick(event: DOM.EventLike): void { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 480033afb33d9..fd9a432d6c388 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -40,6 +40,7 @@ import { defaultInputBoxStyles, defaultProgressBarStyles, defaultToggleStyles } import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Disposable } from 'vs/base/common/lifecycle'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -63,11 +64,12 @@ const NOTEBOOK_FIND_IN_CODE_OUTPUT = nls.localize('notebook.find.filter.findInCo const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 318; const NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING = 4; class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem { - constructor(readonly filters: NotebookFindFilters, action: IAction, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { + constructor(readonly filters: NotebookFindFilters, action: IAction, options: IActionViewItemOptions, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { super(action, { getActions: () => this.getActions() }, contextMenuService, { + ...options, actionRunner, classNames: action.class, anchorAlignmentProvider: () => AnchorAlignment.RIGHT @@ -196,9 +198,9 @@ export class NotebookFindInputFilterButton extends Disposable { private createFilters(container: HTMLElement): void { this._actionbar = this._register(new ActionBar(container, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === this._filtersAction.id) { - return this.instantiationService.createInstance(NotebookFindFilterActionViewItem, this.filters, action, new ActionRunner()); + return this.instantiationService.createInstance(NotebookFindFilterActionViewItem, this.filters, action, options, new ActionRunner()); } return undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index c1218b15ce826..43b387b968990 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -117,9 +117,9 @@ class PropertyHeader extends Disposable { const cellToolbarContainer = DOM.append(this.propertyHeaderContainer, DOM.$('div.property-toolbar')); this._toolbar = new WorkbenchToolBar(cellToolbarContainer, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); + const item = new CodiconActionViewItem(action, { hoverDelegate: options.hoverDelegate }, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 9248d1cee6ea2..4e92fa8a42f97 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -189,9 +189,9 @@ export class CellDiffSideBySideRenderer implements IListRenderer { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); + const item = new CodiconActionViewItem(action, { hoverDelegate: options.hoverDelegate }, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } 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 12b86db201eca..f10530616f189 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -46,12 +46,12 @@ export class BetweenCellToolbar extends CellOverlayPart { } const betweenCellToolbar = this._register(new ToolBar(this._bottomCellToolbarContainer, this.contextMenuService, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { if (this._notebookEditor.notebookOptions.getDisplayOptions().insertToolbarAlignment === 'center') { - return this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); + return this.instantiationService.createInstance(CodiconActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } else { - return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts index 48974ad10a5bc..de2c0e912bf84 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCellRunToolbar.ts @@ -84,7 +84,7 @@ export class RunToolbar extends CellContentPart { const executionContextKeyService = this._register(getCodeCellExecutionContextKeyService(contextKeyService)); this.toolbar = this._register(new ToolBar(container, this.contextMenuService, { getKeyBinding: keybindingProvider, - actionViewItemProvider: _action => { + actionViewItemProvider: (_action, _options) => { actionViewItemDisposables.clear(); const primary = this.getCellToolbarActions(this.primaryMenu).primary[0]; @@ -104,6 +104,7 @@ export class RunToolbar extends CellContentPart { 'notebook-cell-run-toolbar', this.contextMenuService, { + ..._options, getKeyBinding: keybindingProvider }); actionViewItemDisposables.add(item.onDidChangeDropdownVisibility(visible => { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts index 9a558d0dd288e..f606649ca030a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -96,9 +96,9 @@ export class ListTopCellToolbar extends Disposable { DOM.clearNode(this.topCellToolbar); const toolbar = this.instantiationService.createInstance(MenuWorkbenchToolBar, this.topCellToolbar, this.notebookEditor.creationOptions.menuIds.cellTopInsertToolbar, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action instanceof MenuItemAction) { - const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); + const item = this.instantiationService.createInstance(CodiconActionViewItem, action, { hoverDelegate: options.hoverDelegate }); return item; } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index b0026da674cf8..eec7a8ce974ac 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -57,6 +57,7 @@ import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; const $ = DOM.$; @@ -397,9 +398,9 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP const actions = [this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction]; const toolBar = this._register(new ToolBar(this.actionsContainer, this.contextMenuService, { - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => { if (action.id === this.sortByPrecedenceAction.id || action.id === this.recordKeysAction.id) { - return new ToggleActionViewItem(null, action, { keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles }); + return new ToggleActionViewItem(null, action, { ...options, keybinding: this.keybindingsService.lookupKeybinding(action.id)?.getLabel(), toggleStyles: defaultToggleStyles }); } return undefined; }, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 1be32db1a399f..30542844cc9ac 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; @@ -252,7 +252,7 @@ export class SettingsTargetsWidget extends Widget { orientation: ActionsOrientation.HORIZONTAL, focusOnlyEnabledItems: true, ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"), - actionViewItemProvider: (action: IAction) => action.id === 'folderSettings' ? this.folderSettings : undefined + actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => action.id === 'folderSettings' ? this.folderSettings : undefined })); this.userLocalSettings = new Action('userSettings', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index c0632e494bcf2..33c9e99303bb1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -655,9 +655,9 @@ export class SettingsEditor2 extends EditorPane { this.controlsElement = DOM.append(searchContainer, DOM.$('.settings-clear-widget')); const actionBar = this._register(new ActionBar(this.controlsElement, { - actionViewItemProvider: (action) => { + actionViewItemProvider: (action, options) => { if (action.id === filterAction.id) { - return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, this.actionRunner, this.searchWidget); + return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, options, this.actionRunner, this.searchWidget); } return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index 93e13c0234dd4..d119cd97f695a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -17,6 +18,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu constructor( action: IAction, + options: IActionViewItemOptions, actionRunner: IActionRunner | undefined, private readonly searchWidget: SuggestEnabledInput, @IContextMenuService contextMenuService: IContextMenuService @@ -25,6 +27,7 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu { getActions: () => this.getActions() }, contextMenuService, { + ...options, actionRunner, classNames: action.class, anchorAlignmentProvider: () => AnchorAlignment.RIGHT, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index e9bf671e021c6..e2fd9619a6923 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -279,9 +279,9 @@ class TerminalTabsRenderer implements IListRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this._instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this._instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined }); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index a7490c873cff9..16b2f5f1e92e9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; @@ -121,9 +121,9 @@ export class TestingExplorerFilter extends BaseActionViewItem { }))); const actionbar = this._register(new ActionBar(container, { - actionViewItemProvider: action => { + actionViewItemProvider: (action, options) => { if (action.id === this.filtersAction.id) { - return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, this.state, this.actionRunner); + return this.instantiationService.createInstance(FiltersDropdownMenuActionViewItem, action, options, this.state, this.actionRunner); } return undefined; }, @@ -176,6 +176,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { constructor( action: IAction, + options: IActionViewItemOptions, private readonly filters: ITestExplorerFilterState, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService, diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 7f5dcfb792095..901d0a5d5da0b 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -2227,9 +2227,9 @@ class TestRunElementRenderer implements ICompressibleTreeRenderer + actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) + ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, { hoverDelegate: options.hoverDelegate }) : undefined }); From 13a2ba89a6159246bdf9b9d220c30a57eecfbd79 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Sun, 25 Feb 2024 20:49:24 +0100 Subject: [PATCH 0617/1863] rename suggestions: fix width overflow --- src/vs/editor/contrib/rename/browser/renameInputField.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index cf76d531a2a77..1b3728c30c648 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -515,7 +515,6 @@ class CandidatesView { public layout({ height, width }: { height: number; width: number }): void { this._availableHeight = height; this._minimumWidth = width; - this._listContainer.style.width = `${this._minimumWidth}px`; } public setCandidates(candidates: NewSymbolName[]): void { 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 0618/1863] 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 82d9574ca0615..fea45187b43f6 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 0619/1863] 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 f7338ae7b5240..47fddf11c86b1 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 f47285746fed1..929cb32d5a827 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 2a9d829b3ff1e..bf46167248f98 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 26f77202d53e9..9ca0b4310a585 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 4e8761ffb1d0b..c4f2a2def5a92 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 0620/1863] 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 4a1f488b6c3b6..a9d087399d711 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.17.1 +18.19.0 diff --git a/.yarnrc b/.yarnrc index 19c5cb1eb8f05..8675f7ab83c1c 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 57f58763ccaab..c75100cac49c1 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 a774dffc830a4..35feee9b87610 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 9ed8af5842a6a..13aa4c7e87bfb 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 e40ed70901c62..58db0f4af5173 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 12bc3c08a64d5..9f1a068b8d7e8 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 b9a6e80d5f380..bd84fc146dcb1 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 275d88b95a880..82a4fe7698d3f 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 2673931fdb62c..4b4d49573e4f0 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 1c3d3fc095fc9..2f127741d55a4 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 cac528fd2dd2c..4e7208cdf690f 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 7dcbe6fe03049..9836847454944 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 0621/1863] 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 1b3728c30c648..6cb9f8a8c5aba 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 0622/1863] 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 48f53faea39b3..d5312097ee9ab 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 ccf5700c83b50..7200372acca8c 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 0eb478a354441..3b40a248a1de2 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 8b0318487b686..35ba3ee8c5d13 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 5394416d7172c..f0ceaf1950c32 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 0623/1863] 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 0000000000000..5297e77c08622 --- /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 2cc7ffacdc44b..bb8ab9ccca72d 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 0624/1863] 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 0000000000000..550ed81628f01 --- /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 ca21ad138646a..a60e8e59d62b7 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 68cd3a6b32822..89795c710574f 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 0625/1863] 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 9fd67d69b88ee..92b430ff16227 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 a19d88cca333e..902b335ba4fde 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 0626/1863] 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 e1ee2bc0d948b..d34a58122cd91 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 b96df67819647..aaf1452c25a4a 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 c6986bd0f57cf..9652c2dfe0067 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 7228b47ae7139..0c6e63d43ce82 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 4344d01587391..ab81ed790df52 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 f362b61aa1b6d..f5d98f3fd90e2 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 91d1b7f6da912..d6c3375262a80 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 cd571b4d9e20c..bc3a26d311b3f 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 0627/1863] 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 b508d24ff6be5..36ff7b6c4b0f8 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 0628/1863] 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 6cb9f8a8c5aba..3bb00ecdb0503 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 0629/1863] 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 3bf78d8ed2402..65b718f3f0503 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 bde1bd4aad406..258a5bf18e9ad 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 0630/1863] 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 fea45187b43f6..0a593c04ec8fc 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 0631/1863] 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 cab4f6602ff7c..08a483ad5cecf 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 0632/1863] 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 0df9dc1171c47..3b3cafe11ab7e 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 0633/1863] 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 8d981fb62d27b..5d8f803de511d 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 0cea7b3f51494..2c042949a9da5 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 0634/1863] 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 fd7424b0f72c9..698d711f4dc52 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 5f5ad35284224..05505b768a5ea 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 e7ca41dc8b375..1d2a3364d0be3 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 88dfaf2c5b14d..dfc2329510f9a 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 995b8f4081783..7533398537292 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 c119ad43779ba..9ecfcbce827db 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 8b3ea12580f32..adce009430b75 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 8e20025d75795..4dfdf549a3b79 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 6d2dfef371aed..57f6962ac0ed8 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 0000000000000..446282613335d --- /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 dc0af66ff5a13..bff397303beae 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 bdcdfa7c7da07..c3c505cef39ed 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 f0209856dc318..0000000000000 --- 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 c9d62a9bc4e56..5bf35b8ffc28f 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 ca6aaa3350556..c813532f4965d 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 536fb25608e3a..38192d39413e8 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 540bb17db2f93..53e0e2b9a3064 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 f92369cddfb94..ebb6bc264d1d0 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 c42cd0ba3368c..87e8f52fbc652 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 47fddf11c86b1..f3d6b5c9f1628 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 8b4814a92c9f3..4ec59ed09bd7c 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 7b693a1f28c56..007723f698687 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 76189e64741a2..424375e8cae80 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 3e903d5bab9af..05305694e57d8 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 94720d5b557b0..79cbe6faa3733 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 cfcc0e2c73b4f..97aac13854a54 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 c1edd287bea6f..da596a8fc6c02 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 0a593c04ec8fc..3a44ead957b66 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 96dfeee7ea8cb..73c3a23a3050f 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 de219a800bb5e..26e328906471d 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 cfa2d348aa006..6ae11c5136151 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 81a034ffa918c..f4a0bb4fe282e 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 bf46167248f98..00e7153c46f89 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 218c8808205f3..d7a82ed5c1690 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 e66dc53ad6105..246532ddd0235 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 e18622523c587..13b5fde792b5c 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 72eeea0b59070..54eded7aab37d 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 176ac265172c8..548f4d36caf7a 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 849966bbe33a4..d79f2ac7930b0 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 3b40a248a1de2..2af2a8be2e537 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 9cc1b35c3545e..06b591f477a71 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 8a311d4bb0ee8..0315e95a2d4ad 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 4d2a9e4b887f1..55ea0d4e8f900 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 585d253442c18..451bc2107893b 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 2d1fcf15b4895..16b3969d2c35f 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 90aba19a7f2bf..5a365a2cb9f55 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 5de52259df4a1..13b72018035b0 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 43b69137af3f8..139928ec9fe21 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 084e20c5898d2..04d8fe1ff4f6a 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 35007bedeb845..bcfd1900226fe 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 52e87ad808606..b5c306d0b415b 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 f10530616f189..250cb9824ec01 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 2f32b9c59328b..cf98a39a26e95 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 35ba3ee8c5d13..49a702fbbfea7 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 c7dfc7eae6099..d12c826921286 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 38a4f53640352..e22115c2a8ff5 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 4a0a156fcf613..6ea0cb4542e66 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 731653a482831..332c290246acd 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 02b833d6737b3..449b0d60787bc 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 31ed968488e9a..7bf4f427bd07a 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 7261e5e10218b..56d486699a625 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 8eb58e118a3d4..e5e8e34abfa0e 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 06476f564c8f6..87503bb990c33 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 45ea476ec9aba..987d9f119b96b 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 0635/1863] =?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 f6f451bcd0d52..103efb04a9cbd 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 0636/1863] 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 4365c20acc16a..7066412c3f83c 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 f4543f6a1c9cc..4b22ef50a82d9 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 0637/1863] 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 50a7fc56a299c..9e6c751c89c8f 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 0638/1863] 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 74d7849587ef4..dd6b31584912e 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 3df2a1f682c1b..795bf69bec597 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 0639/1863] 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 488815cf2589b..e30f5dd92d942 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 9a1bd1ab1b77c..d8d7ebeb11138 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 499e1d2f57df5..bb6907c4e7856 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 0327aa4816884..2a0269486a3e9 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 2c042949a9da5..f496f89cbfcbb 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 0640/1863] 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 e6b24921ac10e..b690ae5c7927b 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 35bc464d34aac..e7a460de7d0dc 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 2f127741d55a4..30ed28ac51672 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 9836847454944..056c1e1bce0f9 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 0641/1863] 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 2a6f3ec1bc56c..6b8a385ec4215 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 ee1084be56d2c..750e53e4b269c 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 a286082c73859..ab59f23283f9d 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 2a3f9159703b2..b23dacf87e416 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 0642/1863] 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 180d51c15499a..aa43972ef640f 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 0643/1863] 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 f496f89cbfcbb..544612bcb5319 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 0644/1863] 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 00e7153c46f89..b97793d6ddddd 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 a02e1bb20103b..1fb8e60d3dbbe 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 0645/1863] 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 bb6907c4e7856..aae4ac6f275d7 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 0646/1863] 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 a8fef891e89a1..614dcc68cf1a0 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 0647/1863] 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 c0f1827014a73..93b651276e5ed 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 ab61b6bde4f3f..b2e1680a28c10 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 8669cadcce7dd..b6d4500e448aa 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 a91512a6840de..fc1328ca80381 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 0000000000000..edf5b4ae44525 --- /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 04d8fe1ff4f6a..5af8f2acc5380 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 0648/1863] 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 d8d7ebeb11138..db2791f9bdac1 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 aae4ac6f275d7..9a2fbf8e6d7d8 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 544612bcb5319..e3bd74aec3baf 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 0649/1863] 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 8328315b9578b..94dbbd2f5cd62 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 71d641d0739fd..04e4aeb5e9bca 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 0650/1863] 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 04ff3b3a47208..878246a2e26b8 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 0651/1863] 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 aa43972ef640f..74bb3b4ee9dc8 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 0652/1863] 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 74bb3b4ee9dc8..26efec69639e9 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 0653/1863] 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 a8df823266ab0..5086c95a8024d 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 0654/1863] 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 2cc5a637a6a2a..5234d7abb3427 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 76497826d7466..22c507ca0b336 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 a800e241511d3..30789475e4258 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 0655/1863] 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 24e2e4c5506c2..51060787d4ac7 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 d23f8e91fd5c4..41ed2e74e2c17 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 f0bdfdd5bc9f8..e0761b1f1db3a 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 9652c2dfe0067..c27b43d382c08 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 8a0182342560f..17c5ab59c2680 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 7b427fe5dedbb..390b19874c8d1 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 3691e98ec1deb..c4e31c3c9038d 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 e3bd74aec3baf..bfa7bbfb68781 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 650df06217dbd..fd2f81f70e959 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 d87cda2d39c36..e42c6116bc783 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 108685831ad99..3cef7fa94163a 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 3d135ec2a7032..b5abcd2dad359 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 43838aad18979..28f269d3bee3b 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 e2b8639f9cc53..80765957797e0 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 6db63c1cc2ef3..4f297a34c83c9 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 1d09528dcf6c1..1a938d7dbd676 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 0656/1863] 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 e0761b1f1db3a..faddd4e7b066c 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 e42c6116bc783..f1e25f4a355a8 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 3cef7fa94163a..efaf3103c6cce 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 0657/1863] 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 30a95315f3363..171c69d6ed3de 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 0658/1863] 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 9a42093991cdb..33a1379d3bd69 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 00943f3262b83..d025593320959 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 a594b4f3acfb4..e1bbf0f4f8e79 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 0000000000000..d757eace7e08a --- /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 0659/1863] 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 476712eaf1f0d..68181b0b8309c 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 0660/1863] 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 6e33d7da99dd7..f8167d4dc52d5 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 0661/1863] 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 0086a5158e511..9c90588adf182 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 ae4db9698e98a..cc9dee8d2754e 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 0662/1863] 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 5cbdad174b7be..cfc5b17388700 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 c4cf0aa94fed3..095a297856490 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 `