From 51ea68dcd23eeb0235e07b854c51f03c1ce60ca5 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Mon, 11 Dec 2023 16:39:38 -0500 Subject: [PATCH 01/23] 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 7094cc9c41e..b84a3d8a8b3 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 88d546e7781010960e0496299b965cf09d7349e7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 22 Mar 2024 14:41:36 +0100 Subject: [PATCH 02/23] adding more tests --- .../test/browser/indentation.test.ts | 176 +++++++++++++++++- .../test/browser/controller/cursor.test.ts | 88 --------- 2 files changed, 174 insertions(+), 90 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 a0f43259a60..ddb3fdfc672 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -273,7 +273,7 @@ suite('Change Indentation to Tabs - TypeScript/Javascript', () => { }); }); -suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => { +suite('Auto Indent On Paste - TypeScript/JavaScript', () => { const languageId = 'ts-test'; let disposables: DisposableStore; @@ -366,6 +366,84 @@ suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => { }); }); + test('issue #29803: do not indent when pasting text with only one line', () => { + + // https://github.com/microsoft/vscode/issues/29803 + + const model = createTextModel([ + 'const linkHandler = new Class(a, b, c,', + ' d)' + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + editor.setSelection(new Selection(2, 6, 2, 6)); + const text = ', null'; + viewModel.paste(text, true, undefined, 'keyboard'); + const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); + autoIndentOnPasteController.trigger(new Range(2, 6, 2, 11)); + assert.strictEqual(model.getValue(), [ + 'const linkHandler = new Class(a, b, c,', + ' d, null)' + ].join('\n')); + }); + }); + + test('issue #29753: incorrect indentation after comment', () => { + + // https://github.com/microsoft/vscode/issues/29753 + + const model = createTextModel([ + 'class A {', + ' /**', + ' * used only for debug purposes.', + ' */', + ' private _codeInfo: KeyMapping[];', + '}', + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + editor.setSelection(new Selection(5, 24, 5, 34)); + const text = 'IMacLinuxKeyMapping'; + viewModel.paste(text, true, undefined, 'keyboard'); + const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); + autoIndentOnPasteController.trigger(new Range(5, 24, 5, 43)); + assert.strictEqual(model.getValue(), [ + 'class A {', + ' /**', + ' * used only for debug purposes.', + ' */', + ' private _codeInfo: IMacLinuxKeyMapping[];', + '}', + ].join('\n')); + }); + }); + + test('issue #29753: incorrect indentation of header comment', () => { + + // https://github.com/microsoft/vscode/issues/29753 + + const model = createTextModel('', languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => { + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + const text = [ + '/*----------------', + ' * Copyright (c) ', + ' * Licensed under ...', + ' *-----------------*/', + ].join('\n'); + viewModel.paste(text, true, undefined, 'keyboard'); + const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste); + autoIndentOnPasteController.trigger(new Range(1, 1, 4, 22)); + assert.strictEqual(model.getValue(), text); + }); + }); + // Failing tests found in issues... test.skip('issue #181065: Incorrect paste of object within comment', () => { @@ -523,7 +601,7 @@ suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => { }); }); -suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => { +suite('Auto Indent On Type - TypeScript/JavaScript', () => { const languageId = "ts-test"; let disposables: DisposableStore; @@ -606,6 +684,98 @@ suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => { }); }); + test('issue #29755: do not add indentation on enter if indentation is already valid', () => { + + //https://github.com/microsoft/vscode/issues/29755 + + const model = createTextModel([ + 'function f() {', + ' const one = 1;', + ' const two = 2;', + '}', + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => { + + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + editor.setSelection(new Selection(3, 1, 3, 1)); + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getValue(), [ + 'function f() {', + ' const one = 1;', + '', + ' const two = 2;', + '}', + ].join('\n')); + }); + }); + + test('issue #36090', () => { + + // https://github.com/microsoft/vscode/issues/36090 + + const model = createTextModel([ + 'class ItemCtrl {', + ' getPropertiesByItemId(id) {', + ' return this.fetchItem(id)', + ' .then(item => {', + ' return this.getPropertiesOfItem(item);', + ' });', + ' }', + '}', + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => { + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + editor.setSelection(new Selection(7, 6, 7, 6)); + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getValue(), + [ + 'class ItemCtrl {', + ' getPropertiesByItemId(id) {', + ' return this.fetchItem(id)', + ' .then(item => {', + ' return this.getPropertiesOfItem(item);', + ' });', + ' }', + ' ', + '}', + ].join('\n') + ); + assert.deepStrictEqual(editor.getSelection(), new Selection(8, 5, 8, 5)); + }); + }); + + test('issue #115304: indent block comment onEnter', () => { + + // https://github.com/microsoft/vscode/issues/115304 + + const model = createTextModel([ + '/** */', + 'function f() {}', + ].join('\n'), languageId, {}); + disposables.add(model); + + withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel, instantiationService) => { + registerLanguage(instantiationService, languageId, Language.TypeScript, disposables); + editor.setSelection(new Selection(1, 4, 1, 4)); + viewModel.type('\n', 'keyboard'); + assert.strictEqual(model.getValue(), + [ + '/**', + ' * ', + ' */', + 'function f() {}', + ].join('\n') + ); + assert.deepStrictEqual(editor.getSelection(), new Selection(2, 4, 2, 4)); + }); + }); + + // Failing tests... + test.skip('issue #40115: keep indentation when added', () => { // https://github.com/microsoft/vscode/issues/40115 @@ -921,6 +1091,8 @@ suite('Auto Indent On Type - Ruby', () => { test('issue #198350: in or when incorrectly match non keywords for Ruby', () => { + // https://github.com/microsoft/vscode/issues/198350 + const model = createTextModel("", languageId, {}); disposables.add(model); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 854346ddfea..a86e95c1303 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -26,7 +26,6 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModelEventDispatcher'; import { ITestCodeEditor, TestCodeEditorInstantiationOptions, createCodeEditorServices, instantiateTestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -4469,93 +4468,6 @@ suite('Editor Controller', () => { }); }); - test('issue #36090: JS: editor.autoIndent seems to be broken', () => { - const languageId = 'jsMode'; - - disposables.add(languageService.registerLanguage({ id: languageId })); - disposables.add(languageConfigurationService.register(languageId, { - brackets: [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ], - indentationRules: { - // ^(.*\*/)?\s*\}.*$ - decreaseIndentPattern: /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/, - // ^.*\{[^}"']*$ - increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/ - }, - onEnterRules: javascriptOnEnterRules - })); - - const model = createTextModel( - [ - 'class ItemCtrl {', - ' getPropertiesByItemId(id) {', - ' return this.fetchItem(id)', - ' .then(item => {', - ' return this.getPropertiesOfItem(item);', - ' });', - ' }', - '}', - ].join('\n'), - languageId - ); - - withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel) => { - moveTo(editor, viewModel, 7, 6, false); - assertCursor(viewModel, new Selection(7, 6, 7, 6)); - - viewModel.type('\n', 'keyboard'); - assert.strictEqual(model.getValue(), - [ - 'class ItemCtrl {', - ' getPropertiesByItemId(id) {', - ' return this.fetchItem(id)', - ' .then(item => {', - ' return this.getPropertiesOfItem(item);', - ' });', - ' }', - ' ', - '}', - ].join('\n') - ); - assertCursor(viewModel, new Selection(8, 5, 8, 5)); - }); - }); - - test('issue #115304: OnEnter broken for TS', () => { - const languageId = 'jsMode'; - - disposables.add(languageService.registerLanguage({ id: languageId })); - disposables.add(languageConfigurationService.register(languageId, { - onEnterRules: javascriptOnEnterRules - })); - - const model = createTextModel( - [ - '/** */', - 'function f() {}', - ].join('\n'), - languageId - ); - - withTestCodeEditor(model, { autoIndent: 'advanced' }, (editor, viewModel) => { - moveTo(editor, viewModel, 1, 4, false); - assertCursor(viewModel, new Selection(1, 4, 1, 4)); - - viewModel.type('\n', 'keyboard'); - assert.strictEqual(model.getValue(), - [ - '/**', - ' * ', - ' */', - 'function f() {}', - ].join('\n') - ); - assertCursor(viewModel, new Selection(2, 4, 2, 4)); - }); - }); test('issue #38261: TAB key results in bizarre indentation in C++ mode ', () => { const languageId = 'indentRulesMode'; From fa3b1b097fcada5ae674bbb62db150d2df95009d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:33:28 -0700 Subject: [PATCH 03/23] Include env in unsafe profiles config Fixes #204167 --- .../terminal/browser/terminalProfileQuickpick.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index b7afa342a67..116e678eacd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -6,7 +6,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService, IKeyMods, IPickOptions, IQuickPickSeparator, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; +import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix, type ITerminalExecutable } from 'vs/platform/terminal/common/terminal'; import { getUriClasses, getColorClass, createColorStyleElement } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import * as nls from 'vs/nls'; @@ -131,11 +131,14 @@ export class TerminalProfileQuickpick { if (!name) { return; } - const newConfigValue: { [key: string]: ITerminalProfileObject } = { ...configProfiles }; - newConfigValue[name] = { - path: context.item.profile.path, - args: context.item.profile.args - }; + const newConfigValue: { [key: string]: ITerminalExecutable } = { ...configProfiles }; + newConfigValue[name] = { path: context.item.profile.path }; + if (context.item.profile.args) { + newConfigValue[name].args = context.item.profile.args; + } + if (context.item.profile.env) { + newConfigValue[name].env = context.item.profile.env; + } await this._configurationService.updateValue(profilesKey, newConfigValue, ConfigurationTarget.USER); }, onKeyMods: mods => keyMods = mods From e67961480f584a2cd8f2e960194b25d2b6fc02fa Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 22 Mar 2024 07:34:29 -0700 Subject: [PATCH 04/23] Extra scrolling logic to handle long tail of scroll events (#208341) * special scrolling logic * longer comment --- .../browser/view/renderers/webviewPreloads.ts | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) 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 5781cdb6a3f..96e05009b44 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -521,13 +521,49 @@ async function webviewPreloads(ctx: PreloadContext) { } }; + let previousDelta: number | undefined; let scrollTimeout: any /* NodeJS.Timeout */ | undefined; let scrolledElement: Element | undefined; - function flagRecentlyScrolled(node: Element) { + let lastTimeScrolled: number | undefined; + function flagRecentlyScrolled(node: Element, deltaY?: number) { scrolledElement = node; - node.setAttribute('recentlyScrolled', 'true'); - clearTimeout(scrollTimeout); - scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300); + if (!deltaY) { + lastTimeScrolled = Date.now(); + previousDelta = undefined; + node.setAttribute('recentlyScrolled', 'true'); + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300); + return true; + } + + if (node.hasAttribute('recentlyScrolled')) { + if (lastTimeScrolled && Date.now() - lastTimeScrolled > 300) { + // it has been a while since we actually scrolled + // if scroll velocity increases, it's likely a new scroll event + if (!!previousDelta && deltaY < 0 && deltaY < previousDelta - 2) { + clearTimeout(scrollTimeout); + scrolledElement?.removeAttribute('recentlyScrolled'); + return false; + } else if (!!previousDelta && deltaY > 0 && deltaY > previousDelta + 2) { + clearTimeout(scrollTimeout); + scrolledElement?.removeAttribute('recentlyScrolled'); + return false; + } + + // the tail end of a smooth scrolling event (from a trackpad) can go on for a while + // so keep swallowing it, but we can shorten the timeout since the events occur rapidly + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 50); + } else { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { scrolledElement?.removeAttribute('recentlyScrolled'); }, 300); + } + + previousDelta = deltaY; + return true; + } + + return false; } function eventTargetShouldHandleScroll(event: WheelEvent) { @@ -536,11 +572,6 @@ async function webviewPreloads(ctx: PreloadContext) { return false; } - if (node.hasAttribute('recentlyScrolled') && scrolledElement === node) { - flagRecentlyScrolled(node); - return true; - } - // scroll up if (event.deltaY < 0 && node.scrollTop > 0) { // there is still some content to scroll @@ -565,6 +596,10 @@ async function webviewPreloads(ctx: PreloadContext) { flagRecentlyScrolled(node); return true; } + + if (flagRecentlyScrolled(node, event.deltaY)) { + return true; + } } return false; From eae98ba5543c41ad7b5988c6327d37e3ad4c5c14 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:34:30 -0700 Subject: [PATCH 05/23] Also include color and icon in unsafe profile config --- .../contrib/terminal/browser/terminalProfileQuickpick.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index 116e678eacd..b0bd16d4243 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -139,6 +139,12 @@ export class TerminalProfileQuickpick { if (context.item.profile.env) { newConfigValue[name].env = context.item.profile.env; } + if (context.item.profile.color) { + newConfigValue[name].color = context.item.profile.color; + } + if (context.item.profile.icon) { + newConfigValue[name].icon = context.item.profile.icon; + } await this._configurationService.updateValue(profilesKey, newConfigValue, ConfigurationTarget.USER); }, onKeyMods: mods => keyMods = mods From 128acbad208a4d63accf018b0528e80ba7678cb6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 07:48:13 -0700 Subject: [PATCH 06/23] Reduce duplicate insert into terminal menu item Fixes #208420 --- .../contrib/chat/browser/actions/chatCodeblockActions.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 2c53083e14f..67cbee7eceb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -31,7 +31,6 @@ import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeB import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; 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'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -423,11 +422,6 @@ export function registerChatCodeBlockActions() { CONTEXT_IN_CHAT_SESSION, ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) ) - }, - { - id: MenuId.ChatCodeBlock, - group: 'navigation', - when: CTX_INLINE_CHAT_VISIBLE, }], keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter, From 2c28cd71852e86cf28f4a9fd09727085d2bbbd7f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Mar 2024 16:06:19 +0100 Subject: [PATCH 07/23] tweak width of inline chat content widget (#208422) --- .../contrib/inlineChat/browser/inlineChatContentWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts index e4854325329..7b586a2bba0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts @@ -120,8 +120,8 @@ export class InlineChatContentWidget implements IContentWidget { beforeRender(): IDimension | null { const contentWidth = this._editor.getLayoutInfo().contentWidth; - const minWidth = Math.round(contentWidth * 0.33); - const maxWidth = Math.round(contentWidth * 0.66); + const minWidth = Math.round(contentWidth * 0.38); + const maxWidth = Math.round(contentWidth * 0.82); const width = clamp(220, minWidth, maxWidth); From 1a6f5b638cc39a9c4e065c3d5608779da3603a28 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 08:08:23 -0700 Subject: [PATCH 08/23] Wait for cmd detection if running command at start up Fixes #205744 --- .../terminal/browser/terminalInstance.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9941d46b3a3..99f6f8b4357 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -824,10 +824,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async runCommand(commandLine: string, shouldExecute: boolean): Promise { + let commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); + + // Await command detection if the terminal is starting up + if (!commandDetection && (this._processManager.processState === ProcessState.Uninitialized || this._processManager.processState === ProcessState.Launching)) { + const store = new DisposableStore(); + await Promise.race([ + new Promise(r => { + store.add(this.capabilities.onDidAddCapabilityType(e => { + if (e === TerminalCapability.CommandDetection) { + commandDetection = this.capabilities.get(TerminalCapability.CommandDetection); + r(); + } + })); + }), + timeout(2000), + ]); + store.dispose(); + } + // Determine whether to send ETX (ctrl+c) before running the command. This should always // happen unless command detection can reliably say that a command is being entered and // there is no content in the prompt - if (this.capabilities.get(TerminalCapability.CommandDetection)?.hasInput !== false) { + if (commandDetection?.hasInput !== false) { await this.sendText('\x03', false); // Wait a little before running the command to avoid the sequences being echoed while the ^C // is being evaluated From d848ebe222375e8a403b91cdf0b2d0a48a038006 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Mar 2024 16:20:39 +0100 Subject: [PATCH 09/23] inline chat: fix info label placement (#208424) --- .../contrib/inlineChat/browser/media/inlineChat.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 250a419af74..0223db05eee 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -62,6 +62,15 @@ font-size: 11px; align-self: baseline; padding-left: 10px; + display: inline-flex; +} + +.monaco-workbench .inline-chat .status .label.info { + margin-right: auto; + padding-left: 2px; +} + +.monaco-workbench .inline-chat .status .label.status { margin-left: auto; } From 9d19dc1c2b23e5cef3c5ff8eb47ca6c1a372d63b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:05:54 -0700 Subject: [PATCH 10/23] Don't select all when opening quick chat with a query Fixes #199734 --- .../contrib/chat/browser/actions/chatQuickInputActions.ts | 5 ++++- src/vs/workbench/contrib/chat/browser/chatQuick.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 4df9285764e..d1863ef7d6c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -178,7 +178,10 @@ export function getQuickChatActionForProvider(id: string, label: string) { override run(accessor: ServicesAccessor, query?: string): void { const quickChatService = accessor.get(IQuickChatService); - quickChatService.toggle(id, query ? { query } : undefined); + quickChatService.toggle(id, query ? { + query, + selection: new Selection(1, query.length + 1, 1, query.length + 1) + } : undefined); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 50cb0f56a70..7890d192991 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -77,10 +77,12 @@ export class QuickChatService extends Disposable implements IQuickChatService { open(providerId?: string, options?: IQuickChatOpenOptions): void { if (this._input) { if (this._currentChat && options?.query) { + this._currentChat.focus(); this._currentChat.setValue(options.query, options.selection); if (!options.isPartialQuery) { this._currentChat.acceptInput(); } + return; } return this.focus(); } From 789b9095a00c13c58efdaa4e702ff80423d0467b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:19:03 -0700 Subject: [PATCH 11/23] Simplify terminal sticky scroll refresh by deferring to microtask Fixes #207959 --- .../browser/terminalStickyScrollOverlay.ts | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts index 087576f6ca2..cc759eeb396 100644 --- a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -59,6 +59,7 @@ export class TerminalStickyScrollOverlay extends Disposable { private _refreshListeners = this._register(new MutableDisposable()); private _state: OverlayState = OverlayState.Off; + private _isRefreshQueued = false; private _rawMaxLineCount: number = 5; constructor( @@ -114,7 +115,7 @@ export class TerminalStickyScrollOverlay extends Disposable { })); this._register(this._xterm.raw.onResize(() => { this._syncOptions(); - this._throttledRefresh(); + this._refresh(); })); this._getSerializeAddonConstructor().then(SerializeAddon => { @@ -175,37 +176,15 @@ export class TerminalStickyScrollOverlay extends Disposable { this._element?.classList.toggle(CssClasses.Visible, isVisible); } - /** - * The entry point to refresh sticky scroll. This is synchronous and will call into the method - * that actually refreshes using either debouncing or throttling depending on the situation. - * - * The goal is that if the command has changed to update immediately (with throttling) and if - * the command is the same then update with debouncing as it's less likely updates will show up. - * This approach also helps with: - * - * - Cursor move only updates such as moving horizontally in pagers which without this may show - * the sticky scroll before hiding it again almost immediately due to everything not being - * parsed yet. - * - Improving performance due to deferring less important updates via debouncing. - * - Less flickering when scrolling, while still updating immediately when the command changes. - */ private _refresh(): void { - if (!this._xterm.raw.element?.parentElement || !this._stickyScrollOverlay || !this._serializeAddon) { + if (this._isRefreshQueued) { return; } - const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY); - if (command && this._currentStickyCommand !== command) { - this._throttledRefresh(); - } else { - // If it's the same command, do not throttle as the sticky scroll overlay height may - // need to be adjusted. This would cause a flicker if throttled. + this._isRefreshQueued = true; + queueMicrotask(() => { this._refreshNow(); - } - } - - @throttle(0) - private _throttledRefresh(): void { - this._refreshNow(); + this._isRefreshQueued = false; + }); } private _refreshNow(): void { From 8fc27652040d97eab02f0ce7b4320ca36b562c61 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 09:53:54 -0700 Subject: [PATCH 12/23] Add location support for newWithProfile command Fixes #208306 --- .../terminal/browser/terminalActions.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 9e6c751c89c..5c1b4ef531c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1776,6 +1776,15 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) { type: 'string', enum: profileEnum.values, markdownEnumDescriptions: profileEnum.markdownDescriptions + }, + location: { + description: localize('newWithProfile.location', "Where to create the terminal"), + type: 'string', + enum: ['view', 'editor'], + enumDescriptions: [ + localize('newWithProfile.location.view', 'Create the terminal in the terminal view'), + localize('newWithProfile.location.editor', 'Create the terminal in the editor'), + ] } } } @@ -1783,7 +1792,11 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) { }, }); } - async run(accessor: ServicesAccessor, eventOrOptionsOrProfile: MouseEvent | ICreateTerminalOptions | ITerminalProfile | { profileName: string } | undefined, profile?: ITerminalProfile) { + async run( + accessor: ServicesAccessor, + eventOrOptionsOrProfile: MouseEvent | ICreateTerminalOptions | ITerminalProfile | { profileName: string; location?: 'view' | 'editor' | unknown } | undefined, + profile?: ITerminalProfile + ) { const c = getTerminalServices(accessor); const workspaceContextService = accessor.get(IWorkspaceContextService); const commandService = accessor.get(ICommandService); @@ -1799,6 +1812,12 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) { throw new Error(`Could not find terminal profile "${eventOrOptionsOrProfile.profileName}"`); } options = { config }; + if ('location' in eventOrOptionsOrProfile) { + switch (eventOrOptionsOrProfile.location) { + case 'editor': options.location = TerminalLocation.Editor; break; + case 'view': options.location = TerminalLocation.Panel; break; + } + } } else if (isMouseEvent(eventOrOptionsOrProfile) || isPointerEvent(eventOrOptionsOrProfile) || isKeyboardEvent(eventOrOptionsOrProfile)) { event = eventOrOptionsOrProfile; options = profile ? { config: profile } : undefined; From 3a28ac2cdb6312f4add667d405cc0df2010f47dd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 22 Mar 2024 18:06:35 +0100 Subject: [PATCH 13/23] server-web: resources not loaded with serverBasePath (#208435) --- src/vs/server/node/webClientServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/server/node/webClientServer.ts b/src/vs/server/node/webClientServer.ts index feed129fc94..f0c43c66aef 100644 --- a/src/vs/server/node/webClientServer.ts +++ b/src/vs/server/node/webClientServer.ts @@ -327,7 +327,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, - remoteBaseUrl: this._basePath, + serverBasePath: 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, From 385ad93dd6b5779b2e87e7b1013bbf66166ad393 Mon Sep 17 00:00:00 2001 From: Ole Date: Fri, 22 Mar 2024 18:22:32 +0100 Subject: [PATCH 14/23] Log `resource` telemetry also for side-by-side views on browsers. (#208196) * Log `resource` telemetry also for side-by-side views on browsers. This telemetry is already logged in non-browser environments, and for the regular text editor (and other editors with a single document), but not yet for editors dealing with multiple documents, like "workbench.editors.diffEditorInput". It is useful to have this telemetry to check how much e.g. the diff editor is used to open certain file types. Fixes #208187 * Use URI.isUri instead of instanceof URI. --- .../browser/parts/editor/editorGroupView.ts | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index c9e2d6a9eed..0ab3d4e9655 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -660,17 +660,27 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return true; } + private toResourceTelemetryDescriptor(resource: URI): object | undefined { + if (!resource) { + return undefined; + } + const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined; + if (!path) { + return undefined; + } + let resourceExt = extname(resource); + // Remove query parameters from the resource extension + const queryStringLocation = resourceExt.indexOf('?'); + resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt; + return { mimeType: new TelemetryTrustedValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) }; + } + private toEditorTelemetryDescriptor(editor: EditorInput): object { const descriptor = editor.getTelemetryDescriptor(); - const resource = EditorResourceAccessor.getOriginalUri(editor); - const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined; - if (resource && path) { - let resourceExt = extname(resource); - // Remove query parameters from the resource extension - const queryStringLocation = resourceExt.indexOf('?'); - resourceExt = queryStringLocation !== -1 ? resourceExt.substr(0, queryStringLocation) : resourceExt; - descriptor['resource'] = { mimeType: new TelemetryTrustedValue(getMimeTypes(resource).join(', ')), scheme: resource.scheme, ext: resourceExt, path: hash(path) }; + const resource = EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }); + if (URI.isUri(resource)) { + descriptor['resource'] = this.toResourceTelemetryDescriptor(resource); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { @@ -678,6 +688,20 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } */ return descriptor; + } else if (resource) { + if (resource.primary) { + descriptor['resource'] = this.toResourceTelemetryDescriptor(resource.primary); + } + if (resource.secondary) { + descriptor['resourceSecondary'] = this.toResourceTelemetryDescriptor(resource.secondary); + } + /* __GDPR__FRAGMENT__ + "EditorTelemetryDescriptor" : { + "resource": { "${inline}": [ "${URIDescriptor}" ] } + "resourceSecondary": { "${inline}": [ "${URIDescriptor}" ] } + } + */ + return descriptor; } return descriptor; From b45774692c3031a089caf0d132b34e1e7e90415d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 22 Mar 2024 14:31:52 -0300 Subject: [PATCH 15/23] ListView can warn when failing to measure rows because the list was not in the DOM (#208204) After debugging this for the 100th time, I will make the 101st time easier to troubleshoot. I don't think there are any reasonable cases where the list row height would actually be 0, so I think this avoids unnecessary calls to isAncestor --- src/vs/base/browser/ui/list/listView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index fdd9df0e2ae..47a4f7a85fc 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; @@ -1517,6 +1517,9 @@ export class ListView implements IListView { if (item.row) { item.row.domNode.style.height = ''; item.size = item.row.domNode.offsetHeight; + if (item.size === 0 && !isAncestor(item.row.domNode, getWindow(item.row.domNode).document.body)) { + console.warn('Measuring item node that is not in DOM! Add ListView to the DOM before measuring row height!'); + } item.lastDynamicHeightWidth = this.renderWidth; return item.size - size; } From 50b096c57554d51622bee3ba1ad42a91f526c998 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:46:54 -0700 Subject: [PATCH 16/23] Don't rescale by default Part of #207739 --- .../workbench/contrib/terminal/common/terminalConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 4226b93ec7c..bdc76cdf957 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -563,7 +563,7 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.RescaleOverlappingGlyphs]: { markdownDescription: localize('terminal.integrated.rescaleOverlappingGlyphs', "Whether to rescale glyphs horizontally that are a single cell wide but have glyphs that would overlap following cell(s). This typically happens for ambiguous width characters (eg. the roman numeral characters U+2160+) which aren't featured in monospace fonts. Emoji glyphs are never rescaled."), type: 'boolean', - default: true + default: false }, [TerminalSettingId.AutoReplies]: { markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), From 2cb0fa3f27a50782f71c28006b81a142e4d85774 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:01:32 -0700 Subject: [PATCH 17/23] Update terminalBell to No-Action-Avail_Upd10_-6db.mp3 Fixes #194907 --- .../browser/media/terminalBell.mp3 | Bin 54446 -> 27712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 b/src/vs/platform/accessibilitySignal/browser/media/terminalBell.mp3 index f00aa6de2bbad2497a51ddc193c5b6933cebb64f..7c6b7fe832348d7c8610d31b11d8f15baeec81d5 100644 GIT binary patch literal 27712 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 From 429bc0c55dd34f8750f121242e46baf74d5b6954 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 22 Mar 2024 11:19:00 -0700 Subject: [PATCH 18/23] Fix missing comma (#208442) --- src/vs/workbench/browser/parts/editor/editorGroupView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0ab3d4e9655..16b04556d21 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -697,7 +697,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { - "resource": { "${inline}": [ "${URIDescriptor}" ] } + "resource": { "${inline}": [ "${URIDescriptor}" ] }, "resourceSecondary": { "${inline}": [ "${URIDescriptor}" ] } } */ From 34f6d642e6950f36dcf90ba719706bc1ff39b43f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 22 Mar 2024 16:13:13 -0300 Subject: [PATCH 19/23] Fix multiple agent id/name mixups (#208450) --- .../workbench/contrib/chat/browser/chat.contribution.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatFollowups.ts | 8 +++++++- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- .../chat/browser/contrib/chatInputEditorContrib.ts | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 172bf27819a..85c687b0cff 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -267,7 +267,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { const agentText = (await Promise.all(agents .filter(a => a.id !== defaultAgent?.id) .map(async a => { - const agentWithLeader = `${chatAgentLeader}${a.id}`; + const agentWithLeader = `${chatAgentLeader}${a.name}`; const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` }; const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.description}`; diff --git a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts index e38f7eccb8d..29a5ba75b7e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatFollowups.ts +++ b/src/vs/workbench/contrib/chat/browser/chatFollowups.ts @@ -45,7 +45,13 @@ export class ChatFollowups extend let tooltipPrefix = ''; if ('agentId' in followup && followup.agentId && followup.agentId !== this.chatAgentService.getDefaultAgent(this.location)?.id) { - tooltipPrefix += `${chatAgentLeader}${followup.agentId} `; + const agent = this.chatAgentService.getAgent(followup.agentId); + if (!agent) { + // Refers to agent that doesn't exist + return; + } + + tooltipPrefix += `${chatAgentLeader}${agent.name} `; if ('subCommand' in followup && followup.subCommand) { tooltipPrefix += `${chatSubcommandLeader}${followup.subCommand} `; } diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 1138c93981e..66efa700bea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -504,7 +504,7 @@ class SubmitButtonActionViewItem extends ActionViewItem { if (secondaryAgent) { const secondaryKeybinding = keybindingService.lookupKeybinding(ChatSubmitSecondaryAgentEditorAction.ID)?.getLabel(); if (secondaryKeybinding) { - tooltip += `\n${chatAgentLeader}${secondaryAgent.id} (${secondaryKeybinding})`; + tooltip += `\n${chatAgentLeader}${secondaryAgent.name} (${secondaryKeybinding})`; } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 93e0593e059..be1cef1010f 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -211,7 +211,7 @@ class InputEditorDecorations extends Disposable { const textDecorations: IDecorationOptions[] | undefined = []; if (agentPart) { - const agentHover = `(${agentPart.agent.id}) ${agentPart.agent.description}`; + const agentHover = `(${agentPart.agent.name}) ${agentPart.agent.description}`; textDecorations.push({ range: agentPart.editorRange, hoverMessage: new MarkdownString(agentHover) }); if (agentSubcommandPart) { textDecorations.push({ range: agentSubcommandPart.editorRange, hoverMessage: new MarkdownString(agentSubcommandPart.command.description) }); From 9d6c275fb719e6dba9b4b5e34af384415eb8f940 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 22 Mar 2024 12:21:02 -0700 Subject: [PATCH 20/23] Add styling to pad outlineEntry toolbar (#208342) add styling to pad outlineEntry toolbar --- .../contrib/notebook/browser/contrib/outline/notebookOutline.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index cac4053c00b..4e7b875a30c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -178,6 +178,7 @@ class NotebookOutlineRenderer implements ITreeRenderer Date: Fri, 22 Mar 2024 12:26:41 -0700 Subject: [PATCH 21/23] add a toolbar item to open cell diagnostic actions (#208358) * add a toolbar item to open cell diagnostic actions * fix updated diagnostic handling * prioritize to be next to cell result * dispose status bar items, better handling of non-code cell --- .../contrib/cellCommands/cellCommands.ts | 4 +- .../cellDiagnostics/cellDiagnostics.ts | 18 +++--- .../executionStatusBarItemController.ts | 60 +++++++++++++++++++ .../notebook/browser/notebook.contribution.ts | 4 +- .../browser/view/cellParts/cellContextKeys.ts | 12 ++-- .../browser/viewModel/codeCellViewModel.ts | 6 +- .../contrib/notebook/common/notebookCommon.ts | 2 +- 7 files changed, 86 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 2027ddaa5b7..de90619669f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -357,7 +357,7 @@ const COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.collapseAllCellOutpu const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs'; const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs'; const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling'; -const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions'; +export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions'; registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { constructor() { @@ -601,7 +601,7 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookCellAction { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { if (context.cell instanceof CodeCellViewModel) { - const error = context.cell.cellErrorDetails; + const error = context.cell.cellDiagnostics.ErrorDetails; if (error?.location) { const location = Range.lift({ startLineNumber: error.location.startLineNumber + 1, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts index 054e1fda1a4..3da0a85f2d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellDiagnostics/cellDiagnostics.ts @@ -12,10 +12,14 @@ import { Iterable } from 'vs/base/common/iterator'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { Emitter, Event } from 'vs/base/common/event'; export class CellDiagnostics extends Disposable { + private readonly _onDidDiagnosticsChange = new Emitter(); + readonly onDidDiagnosticsChange: Event = this._onDidDiagnosticsChange.event; + static ID: string = 'workbench.notebook.cellDiagnostics'; private enabled = false; @@ -48,7 +52,7 @@ export class CellDiagnostics extends Disposable { const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics); if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.inlineChatService.getAllProvider()))) { this.enabled = false; - this.clearDiagnostics(); + this.clear(); } else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.inlineChatService.getAllProvider())) { this.enabled = true; if (!this.listening) { @@ -62,7 +66,7 @@ export class CellDiagnostics extends Disposable { if (this.enabled && e.type === NotebookExecutionType.cell && e.affectsCell(this.cell.uri)) { if (!!e.changed) { // cell is running - this.clearDiagnostics(); + this.clear(); } else { this.setDiagnostics(); } @@ -71,21 +75,19 @@ export class CellDiagnostics extends Disposable { public clear() { if (this.ErrorDetails) { - this.clearDiagnostics(); + this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []); + this.errorDetails = undefined; + this._onDidDiagnosticsChange.fire(); } } - private clearDiagnostics() { - this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []); - this.errorDetails = undefined; - } - private setDiagnostics() { const metadata = this.cell.model.internalMetadata; if (!metadata.lastRunSuccess && metadata?.error?.location) { const marker = this.createMarkerData(metadata.error.message, metadata.error.location); this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, [marker]); this.errorDetails = metadata.error; + this._onDidDiagnosticsChange.fire(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index 34a10466dd1..26fb72cc285 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -19,6 +19,9 @@ import { CellStatusbarAlignment, INotebookCellStatusBarItem, NotebookCellExecuti import { INotebookCellExecution, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; +import { OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export function formatCellDuration(duration: number, showMilliseconds: boolean = true): string { if (showMilliseconds && duration < 1000) { @@ -333,3 +336,60 @@ class TimerCellStatusBarItem extends Disposable { this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); } } + +export class DiagnosticCellStatusBarContrib extends Disposable implements INotebookEditorContribution { + static id: string = 'workbench.notebook.statusBar.diagtnostic'; + + constructor( + notebookEditor: INotebookEditor, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + this._register(new NotebookStatusBarController(notebookEditor, (vm, cell) => + cell instanceof CodeCellViewModel ? + instantiationService.createInstance(DiagnosticCellStatusBarItem, vm, cell) : + Disposable.None + )); + } +} +registerNotebookContribution(DiagnosticCellStatusBarContrib.id, DiagnosticCellStatusBarContrib); + + +class DiagnosticCellStatusBarItem extends Disposable { + private _currentItemIds: string[] = []; + + constructor( + private readonly _notebookViewModel: INotebookViewModel, + private readonly cell: CodeCellViewModel, + @IKeybindingService private readonly keybindingService: IKeybindingService + ) { + super(); + this._update(); + this._register(this.cell.cellDiagnostics.onDidDiagnosticsChange(() => this._update())); + } + + private async _update() { + let item: INotebookCellStatusBarItem | undefined; + + if (!!this.cell.cellDiagnostics.ErrorDetails) { + const keybinding = this.keybindingService.lookupKeybinding(OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID)?.getLabel(); + const tooltip = localize('notebook.cell.status.diagnostic', "Quick Actions {0}", `(${keybinding})`); + + item = { + text: `$(sparkle)`, + tooltip, + alignment: CellStatusbarAlignment.Left, + command: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID, + priority: Number.MAX_SAFE_INTEGER - 1 + }; + } + + const items = item ? [item] : []; + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items }]); + } + + override dispose() { + super.dispose(); + this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items: [] }]); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 7865af57809..cb609d016d2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -1101,9 +1101,9 @@ configurationRegistry.registerConfiguration({ default: false }, [NotebookSetting.cellFailureDiagnostics]: { - markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Enable experimental diagnostics for cell failures."), + markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Show available diagnostics for cell failures."), type: 'boolean', - default: false + default: true }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index f3571422003..fc8032f7853 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -101,6 +101,7 @@ export class CellContextKeyManager extends Disposable { if (element instanceof CodeCellViewModel) { this.elementDisposables.add(element.onDidChangeOutputs(() => this.updateForOutputs())); + this.elementDisposables.add(element.cellDiagnostics.onDidDiagnosticsChange(() => this.updateForDiagnostics())); } this.elementDisposables.add(this.notebookEditor.onDidChangeActiveCell(() => this.updateForFocusState())); @@ -118,6 +119,7 @@ export class CellContextKeyManager extends Disposable { this.updateForCollapseState(); this.updateForOutputs(); this.updateForChat(); + this.updateForDiagnostics(); this.cellLineNumbers.set(this.element!.lineNumbers); this.cellResource.set(this.element!.uri.toString()); @@ -202,10 +204,6 @@ export class CellContextKeyManager extends Disposable { this.cellRunState.set('idle'); this.cellExecuting.set(false); } - - if (this.element instanceof CodeCellViewModel) { - this.cellHasErrorDiagnostics.set(!!this.element.cellErrorDetails); - } } private updateForEditState() { @@ -247,4 +245,10 @@ export class CellContextKeyManager extends Disposable { this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element)); } + + private updateForDiagnostics() { + if (this.element instanceof CodeCellViewModel) { + this.cellHasErrorDiagnostics.set(!!this.element.cellDiagnostics.ErrorDetails); + } + } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 517b47d7fe7..51dc7be583f 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -47,8 +47,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod private _outputCollection: number[] = []; private readonly _cellDiagnostics: CellDiagnostics; - get cellErrorDetails() { - return this._cellDiagnostics.ErrorDetails; + get cellDiagnostics() { + return this._cellDiagnostics; } private _outputsTop: PrefixSumComputer | null = null; @@ -436,10 +436,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod protected onDidChangeTextModelContent(): void { if (this.getEditState() !== CellEditState.Editing) { - this._cellDiagnostics.clear(); this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent'); this._onDidChangeState.fire({ contentChanged: true }); } + this._cellDiagnostics.clear(); } onDeselect() { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 12c0d4ac41e..07090093a0c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -956,7 +956,7 @@ export const NotebookSetting = { cellChat: 'notebook.experimental.cellChat', notebookVariablesView: 'notebook.experimental.variablesView', InteractiveWindowPromptToSave: 'interactiveWindow.promptToSaveOnClose', - cellFailureDiagnostics: 'notebook.experimental.cellFailureDiagnostics', + cellFailureDiagnostics: 'notebook.cellFailureDiagnostics', } as const; export const enum CellStatusbarAlignment { From 6030fca4dbac053d0dd9d516e4f029a9a529b50a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 22 Mar 2024 12:50:29 -0700 Subject: [PATCH 22/23] Add default keybindings for notebook cell chat when it is enabled. (#208454) --- .../browser/controller/chat/cellChatActions.ts | 14 +++++++++++++- 1 file changed, 13 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 252b2abc2b4..7760ac2916c 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize, localize2 } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; @@ -386,6 +386,18 @@ registerAction2(class extends NotebookAction { ] }, f1: false, + keybinding: { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), + ContextKeyExpr.not(InputFocusedContextKey), + CTX_INLINE_CHAT_HAS_PROVIDER, + ContextKeyExpr.equals(`config.${NotebookSetting.cellChat}`, true) + ), + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyI, + secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)], + }, menu: [ { id: MenuId.NotebookCellBetween, From 13c9d15fd34fdf6ce471cac8cb8702ec8673cc7e Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 22 Mar 2024 13:04:30 -0700 Subject: [PATCH 23/23] ensure that search sparkle appears when AI results available --- src/vs/workbench/contrib/search/browser/searchView.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 6d096a6fbab..379fd061360 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -125,7 +125,6 @@ export class SearchView extends ViewPane { private hasReplacePatternKey: IContextKey; private hasFilePatternKey: IContextKey; private hasSomeCollapsibleResultKey: IContextKey; - private hasAIResultProvider: IContextKey; private tree!: WorkbenchCompressibleObjectTree; private treeLabels!: ResourceLabels; @@ -221,10 +220,10 @@ export class SearchView extends ViewPane { this.hasSomeCollapsibleResultKey = Constants.SearchContext.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService); this.treeViewKey = Constants.SearchContext.InTreeViewKey.bindTo(this.contextKeyService); this.aiResultsVisibleKey = Constants.SearchContext.AIResultsVisibleKey.bindTo(this.contextKeyService); - this.hasAIResultProvider = Constants.SearchContext.hasAIResultProvider.bindTo(this.contextKeyService); this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(new Set(Constants.SearchContext.hasAIResultProvider.keys()))) { + const keys = Constants.SearchContext.hasAIResultProvider.keys(); + if (e.affectsSome(new Set(keys))) { this.refreshHasAISetting(); } })); @@ -436,6 +435,7 @@ export class SearchView extends ViewPane { this.searchWidgetsContainerElement = dom.append(this.container, $('.search-widgets-container')); this.createSearchWidget(this.searchWidgetsContainerElement); + this.refreshHasAISetting(); const history = this.searchHistoryService.load(); const filePatterns = this.viewletState['query.filePatterns'] || ''; @@ -680,7 +680,8 @@ export class SearchView extends ViewPane { } private shouldShowAIButton(): boolean { - return !!(this.configurationService.getValue('search.aiResults') && this.hasAIResultProvider.get()); + const hasProvider = Constants.SearchContext.hasAIResultProvider.getValue(this.contextKeyService); + return !!(this.configurationService.getValue('search.aiResults') && hasProvider); } private onConfigurationUpdated(event?: IConfigurationChangeEvent): void { if (event && (event.affectsConfiguration('search.decorations.colors') || event.affectsConfiguration('search.decorations.badges'))) {