From 36960dc43730305d9ccb0febfc54667464a08725 Mon Sep 17 00:00:00 2001 From: Hamir Mahal Date: Tue, 5 Sep 2023 10:22:53 -0700 Subject: [PATCH 001/133] chore: changes from formatting on save --- .../contrib/terminal/browser/xterm/decorationAddon.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 59898ee1b40..c8e998a829d 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -6,26 +6,26 @@ import * as dom from 'vs/base/browser/dom'; import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; -import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CommandInvalidationReason, ICommandDetectionCapability, IMarkProperties, ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; import { terminalDecorationError, terminalDecorationIncomplete, terminalDecorationMark, terminalDecorationSuccess } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { DecorationSelector, TerminalDecorationHoverManager, updateLayout } from 'vs/workbench/contrib/terminal/browser/xterm/decorationStyles'; import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SUCCESS_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import type { IDecoration, ITerminalAddon, Terminal } from 'xterm'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposable[]; exitCode?: number; markProperties?: IMarkProperties } From 33d24efb398d4fa571c4f9a9d4258a600fd1e611 Mon Sep 17 00:00:00 2001 From: Hamir Mahal Date: Tue, 5 Sep 2023 10:26:49 -0700 Subject: [PATCH 002/133] feat: copy command and output in integrated term --- .../contrib/terminal/browser/xterm/decorationAddon.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index c8e998a829d..af0e597ce43 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -381,6 +381,16 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { if (actions.length > 0) { actions.push(new Separator()); } + const labelCopyCommandAndOutput = localize("terminal.copyCommandAndOutput", 'Copy Command and Output'); + actions.push({ + class: undefined, tooltip: labelCopyCommandAndOutput, id: 'terminal.copyCommandAndOutput', label: labelCopyCommandAndOutput, enabled: true, + run: () => { + const text = command.getOutput(); + if (typeof text === 'string') { + this._clipboardService.writeText(`${command.command !== '' ? command.command + '\n' : ''}${text}`); + } + } + }); const labelText = localize("terminal.copyOutput", 'Copy Output'); actions.push({ class: undefined, tooltip: labelText, id: 'terminal.copyOutput', label: labelText, enabled: true, From 86652e5acb60dafa4e486a7f24a8a1ae141101e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:46:39 -0700 Subject: [PATCH 003/133] Finalize terminal/context, terminal/title/context Fixes #79034 --- .../services/actions/common/menusExtensionPoint.ts | 6 ++---- .../extensions/common/extensionsApiProposals.ts | 1 - .../vscode.proposed.terminalContextMenu.d.ts | 12 ------------ 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index e2fb27d6d8b..bb1993e9d95 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -153,14 +153,12 @@ const apiMenus: IAPIMenu[] = [ { key: 'terminal/context', id: MenuId.TerminalInstanceContext, - description: localize('menus.terminalContext', "The terminal context menu"), - proposed: 'terminalContextMenu' + description: localize('menus.terminalContext', "The terminal context menu") }, { key: 'terminal/title/context', id: MenuId.TerminalTabContext, - description: localize('menus.terminalTabContext', "The terminal tabs context menu"), - proposed: 'terminalContextMenu' + description: localize('menus.terminalTabContext', "The terminal tabs context menu") }, { key: 'view/title', diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 493a5d860a6..11add6851a7 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -84,7 +84,6 @@ export const allApiProposals = Object.freeze({ tabInputTextMerge: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts', taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts', - terminalContextMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts', terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', terminalExecuteCommandEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExecuteCommandEvent.d.ts', diff --git a/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts b/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts deleted file mode 100644 index 768600e79a9..00000000000 --- a/src/vscode-dts/vscode.proposed.terminalContextMenu.d.ts +++ /dev/null @@ -1,12 +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' { - - // https://github.com/microsoft/vscode/issues/79034 - - // This proposal doesn't have any API changes, only contributions - -} From 4bb6b40fc987db2756f3047ef2835b37157ae0a5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 12 Sep 2023 07:03:17 -0700 Subject: [PATCH 004/133] Serialize context progress --- src/vs/base/common/marshallingIds.ts | 2 +- src/vs/base/common/types.ts | 5 ++++ .../api/common/extHostTerminalService.ts | 13 ++++++++++ .../terminal/browser/terminalContextMenu.ts | 25 +++++++++++++++++-- .../terminal/browser/terminalEditor.ts | 4 +-- .../terminal/browser/terminalTabbedView.ts | 10 +++----- .../contrib/terminal/common/terminal.ts | 9 +++++++ 7 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/marshallingIds.ts b/src/vs/base/common/marshallingIds.ts index ae02b8901fc..de454cf7e73 100644 --- a/src/vs/base/common/marshallingIds.ts +++ b/src/vs/base/common/marshallingIds.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export const enum MarshalledId { Uri = 1, Regexp, @@ -19,6 +18,7 @@ export const enum MarshalledId { TimelineActionContext, NotebookCellActionContext, NotebookActionContext, + TerminalContext, TestItemContext, Date, TestMessageMenuArgs, diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index d35246667bc..529d5e4e7be 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -222,3 +222,8 @@ export type OmitOptional = { [K in keyof T as T[K] extends Required[K] ? K export type Mutable = { -readonly [P in keyof T]: T[P] }; + +/** + * A single object or an array of the objects. + */ +export type SingleOrMany = T | T[]; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 58fd62eb8fb..f61297fe50c 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -25,6 +25,8 @@ import { Promises } from 'vs/base/common/async'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { TerminalQuickFix, ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { ISerializedTerminalInstanceContext } from 'vs/workbench/contrib/terminal/common/terminal'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -416,6 +418,17 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); this._bufferer = new TerminalDataBufferer(this._proxy.$sendProcessData); this._proxy.$registerProcessSupport(supportsProcesses); + this._extHostCommands.registerArgumentProcessor({ + processArgument: arg => { + switch (arg?.$mid) { + case MarshalledId.TerminalContext: { + const cast = arg as ISerializedTerminalInstanceContext; + return cast.instanceIds.map(id => this._getTerminalById(id)); + } + default: return arg; + } + } + }); this._register({ dispose: () => { for (const [_, terminalProcess] of this._terminalProcesses) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index d3ec1a63d6b..54a28368e53 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -5,11 +5,32 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction } from 'vs/base/common/actions'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { SingleOrMany } from 'vs/base/common/types'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ISerializedTerminalInstanceContext } from 'vs/workbench/contrib/terminal/common/terminal'; -export function openContextMenu(event: MouseEvent, parent: HTMLElement, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { +class IInstanceContext { + private _instanceIds: number[]; + + constructor( + instances: SingleOrMany | undefined + ) { + this._instanceIds = (!instances ? [] : Array.isArray(instances) ? instances : [instances]).map(e => e.instanceId); + } + + toJSON(): ISerializedTerminalInstanceContext { + return { + $mid: MarshalledId.TerminalContext, + instanceIds: this._instanceIds + }; + } +} + +export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { const standardEvent = new StandardMouseEvent(event); const actions: IAction[] = []; @@ -23,6 +44,6 @@ export function openContextMenu(event: MouseEvent, parent: HTMLElement, menu: IM contextMenuService.showContextMenu({ getAnchor: () => standardEvent, getActions: () => actions, - getActionsContext: () => parent, + getActionsContext: () => new IInstanceContext(contextInstances), }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index a7bd0423aad..24e872ca88e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -138,7 +138,7 @@ export class TerminalEditor extends EditorPane { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, this._editorInstanceElement!, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); return; } @@ -176,7 +176,7 @@ export class TerminalEditor extends EditorPane { else if (!this._cancelContextMenu && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') { if (!this._cancelContextMenu) { - openContextMenu(event, this._editorInstanceElement!, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 24930c1e40b..895d7b29221 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -43,7 +43,6 @@ export class TerminalTabbedView extends Disposable { private _terminalContainer: HTMLElement; private _tabListElement: HTMLElement; - private _parentElement: HTMLElement; private _tabContainer: HTMLElement; private _tabList: TerminalTabList; @@ -83,8 +82,6 @@ export class TerminalTabbedView extends Disposable { ) { super(); - this._parentElement = parentElement; - this._tabContainer = $('.tabs-container'); const tabListContainer = $('.tabs-list-container'); this._tabListElement = $('.tabs-list'); @@ -357,7 +354,7 @@ export class TerminalTabbedView extends Disposable { else if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, this._parentElement, this._instanceMenu, this._contextMenuService); + openContextMenu(event, terminal, this._instanceMenu, this._contextMenuService); return; } @@ -389,8 +386,9 @@ export class TerminalTabbedView extends Disposable { if (rightClickBehavior === 'nothing' && !event.shiftKey) { this._cancelContextMenu = true; } + terminalContainer.focus(); if (!this._cancelContextMenu) { - openContextMenu(event, this._parentElement, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); @@ -406,7 +404,7 @@ export class TerminalTabbedView extends Disposable { if (!emptyList) { this._terminalGroupService.lastAccessedMenu = 'tab-list'; } - openContextMenu(event, this._parentElement, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); + openContextMenu(event, this._tabList.getFocusedElements(), emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index efe0d3be971..9ecb3d6d273 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -5,6 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { MarshalledId } from 'vs/base/common/marshallingIds'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -379,6 +380,14 @@ export interface ITerminalStatusHoverAction { run: () => void; } +/** + * Context for actions taken on terminal instances. + */ +export interface ISerializedTerminalInstanceContext { + $mid: MarshalledId.TerminalContext; + instanceIds: number[]; +} + export const QUICK_LAUNCH_PROFILE_CHOICE = 'workbench.action.terminal.profile.choice'; export const enum TerminalCommandId { From feee9af315cd44e820d1f2cfbe9ad7a3a5d25076 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 13 Sep 2023 15:48:10 -0700 Subject: [PATCH 005/133] option to scroll to the top of the next cell --- .../browser/controller/executeActions.ts | 18 +++++++---- .../notebook/browser/notebookBrowser.ts | 6 ++-- .../notebook/browser/notebookEditorWidget.ts | 10 ++++-- .../notebook/browser/view/notebookCellList.ts | 32 ++++++++++--------- .../test/browser/testNotebookEditor.ts | 1 - 5 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 093970cad14..d62a777a238 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -12,6 +12,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } 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 { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { EditorsOrder } from 'vs/workbench/common/editor'; @@ -20,7 +21,7 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/in import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellKind, CellUri, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -455,29 +456,34 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { } const languageService = accessor.get(ILanguageService); + const config = accessor.get(IConfigurationService); + const scrollBehavior = config.getValue('notebook.scrollOnExecute'); + const focusOptions: IFocusNotebookCellOptions = { + minimalScrolling: scrollBehavior === 'fullNextCell' ? 'fullReveal' : 'partialReveal' + }; + if (context.cell.cellKind === CellKind.Markup) { const nextCell = context.notebookEditor.cellAt(idx + 1); context.cell.updateEditState(CellEditState.Preview, EXECUTE_CELL_SELECT_BELOW); if (nextCell) { - await context.notebookEditor.focusNotebookCell(nextCell, 'container', { minimalScrolling: true }); + await context.notebookEditor.focusNotebookCell(nextCell, 'container', focusOptions); } else { const newCell = insertCell(languageService, context.notebookEditor, idx, CellKind.Markup, 'below'); if (newCell) { - await context.notebookEditor.focusNotebookCell(newCell, 'editor', { minimalScrolling: true }); + await context.notebookEditor.focusNotebookCell(newCell, 'editor', focusOptions); } } return; } else { - // Try to select below, fall back on inserting const nextCell = context.notebookEditor.cellAt(idx + 1); if (nextCell) { - await context.notebookEditor.focusNotebookCell(nextCell, 'container', { minimalScrolling: true }); + await context.notebookEditor.focusNotebookCell(nextCell, 'container', focusOptions); } else { const newCell = insertCell(languageService, context.notebookEditor, idx, CellKind.Code, 'below'); if (newCell) { - await context.notebookEditor.focusNotebookCell(newCell, 'editor', { minimalScrolling: true }); + await context.notebookEditor.focusNotebookCell(newCell, 'editor', focusOptions); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 158b98692fe..1701fbbce8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -150,7 +150,8 @@ export interface ICommonCellInfo { export interface IFocusNotebookCellOptions { readonly skipReveal?: boolean; readonly focusEditorLine?: number; - readonly minimalScrolling?: boolean; + /** Set a value to only scroll as much as needed, partialReveal will bring the element partially into the view */ + readonly minimalScrolling?: 'fullReveal' | 'partialReveal' | undefined; readonly outputId?: string; readonly altOutputId?: string; } @@ -325,7 +326,8 @@ export const enum CellRevealSyncType { Default = 1, Top = 2, Center = 3, - CenterIfOutsideViewport = 4 + CenterIfOutsideViewport = 4, + PartialIfOutsideViewport = 5 } export enum CellRevealRangeType { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 9269abcf748..be3d80cc1f2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -889,7 +889,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD NotebookCellList, 'NotebookCellList', this._body, - this._viewContext, this._listDelegate, renderers, this.scopedContextKeyService, @@ -2073,6 +2072,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._list.revealCell(cell, CellRevealSyncType.CenterIfOutsideViewport); } + revealPartialIfOutsideViewport(cell: ICellViewModel) { + this._list.revealCell(cell, CellRevealSyncType.PartialIfOutsideViewport); + } + async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { return this._list.revealCellRangeAsync(cell, new Range(line, 1, line, 1), CellRevealRangeType.Default); } @@ -2360,6 +2363,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.revealInCenterIfOutsideViewport(cell); } } else { + // focus container const itemDOM = this._list.domElementOfElement(cell); if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) { (document.activeElement as HTMLElement).blur(); @@ -2373,8 +2377,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (typeof options?.focusEditorLine === 'number') { this._cursorNavMode.set(true); this.revealInView(cell); - } else if (options?.minimalScrolling) { + } else if (options?.minimalScrolling === 'fullReveal') { this.revealInView(cell); + } else if (options?.minimalScrolling === 'partialReveal') { + this.revealPartialIfOutsideViewport(cell); } else { this.revealInCenterIfOutsideViewport(cell); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 5700dc24c05..91a6e89e793 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -25,7 +25,6 @@ import { ICellRange, cellRangesToIndexes, reduceCellRanges, cellRangesEqual } fr import { NOTEBOOK_CELL_LIST_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { clamp } from 'vs/base/common/numbers'; import { ISplice } from 'vs/base/common/sequence'; -import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; import { BaseCellRenderTemplate, INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; @@ -148,7 +147,6 @@ export class NotebookCellList extends WorkbenchList implements ID constructor( private listUser: string, container: HTMLElement, - viewContext: ViewContext, delegate: IListVirtualDelegate, renderers: IListRenderer[], contextKeyService: IContextKeyService, @@ -828,12 +826,12 @@ export class NotebookCellList extends WorkbenchList implements ID this._revealInViewWithMinimalScrolling(startIndex); } - private _revealInViewWithMinimalScrolling(viewIndex: number) { + private _revealInViewWithMinimalScrolling(viewIndex: number, partial?: boolean) { const firstIndex = this.view.firstVisibleIndex; if (viewIndex <= firstIndex) { - this._revealInternal(viewIndex, true, CellRevealPosition.Top); + this._revealInternal(viewIndex, true, CellRevealPosition.Top, partial); } else { - this._revealInternal(viewIndex, true, CellRevealPosition.Bottom); + this._revealInternal(viewIndex, true, CellRevealPosition.Bottom, partial); } } @@ -863,13 +861,16 @@ export class NotebookCellList extends WorkbenchList implements ID case CellRevealSyncType.CenterIfOutsideViewport: this._revealInternal(index, true, CellRevealPosition.Center); break; + case CellRevealSyncType.PartialIfOutsideViewport: + this._revealInViewWithMinimalScrolling(index, true); + break; case CellRevealSyncType.Default: this._revealInViewWithMinimalScrolling(index); break; } } - private _revealInternal(viewIndex: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition) { + private _revealInternal(viewIndex: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition, partial?: boolean) { if (viewIndex >= this.view.length) { return; } @@ -879,15 +880,12 @@ export class NotebookCellList extends WorkbenchList implements ID const elementTop = this.view.elementTop(viewIndex); const elementBottom = this.view.elementHeight(viewIndex) + elementTop; - if (ignoreIfInsideViewport - && elementTop >= scrollTop - && elementBottom < wrapperBottom) { - - if (revealPosition === CellRevealPosition.Center - && elementBottom > wrapperBottom - && elementTop > (scrollTop + wrapperBottom) / 2) { - // the element is partially visible and it's below the center of the viewport - } else { + if (ignoreIfInsideViewport) { + if (partial && elementBottom > scrollTop && elementTop < wrapperBottom) { + //element is already partially visible + return; + } else if (elementTop >= scrollTop && elementBottom < wrapperBottom) { + // element is already fully visible return; } } @@ -917,6 +915,10 @@ export class NotebookCellList extends WorkbenchList implements ID } break; case CellRevealPosition.Bottom: + if (partial) { + this.view.setScrollTop(elementTop - (this.view.renderHeight - 100)); + break; + } this.view.setScrollTop(this.scrollTop + (elementBottom - wrapperBottom)); this.view.setScrollTop(this.scrollTop + (this.view.elementTop(viewIndex) + this.view.elementHeight(viewIndex) - this.getViewScrollBottom())); break; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index ab178faad20..b8c0039c16c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -433,7 +433,6 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe NotebookCellList, 'NotebookCellList', DOM.$('container'), - viewContext ?? new ViewContext(disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)), disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)), delegate, [renderer], instantiationService.get(IContextKeyService), From 59842721aba7bbffa5b689f18abfb07634bec38c Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Thu, 14 Sep 2023 09:57:39 -0700 Subject: [PATCH 006/133] register setting --- .../contrib/notebook/browser/controller/executeActions.ts | 4 ++-- .../contrib/notebook/browser/notebook.contribution.ts | 6 ++++++ src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index d62a777a238..79f377a3984 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -457,9 +457,9 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { const languageService = accessor.get(ILanguageService); const config = accessor.get(IConfigurationService); - const scrollBehavior = config.getValue('notebook.scrollOnExecute'); + const scrollBehavior = config.getValue(NotebookSetting.cellExecutionScroll); const focusOptions: IFocusNotebookCellOptions = { - minimalScrolling: scrollBehavior === 'fullNextCell' ? 'fullReveal' : 'partialReveal' + minimalScrolling: scrollBehavior === 'full' ? 'fullReveal' : 'partialReveal' }; if (context.cell.cellKind === CellKind.Markup) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 6372ee6dada..c911c67c50c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -1014,6 +1014,12 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('notebook.remoteSaving', "Enables the incremental saving of notebooks in Remote environment. When enabled, only the changes to the notebook are sent to the extension host, improving performance for large notebooks and slow network connections."), type: 'boolean', default: typeof product.quality === 'string' && product.quality !== 'stable' // only enable as default in insiders + }, + [NotebookSetting.cellExecutionScroll]: { + markdownDescription: nls.localize('notebook.executeCell.scrollToRevealBehavior', "How far to scroll when revealing the next cell upon exectuting {0}.", 'notebook.cell.executeAndSelectBelow'), + type: 'string', + enum: ['full', 'partial'], + default: 'full' } } }); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index cdd7b0fc31d..269144fa599 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -963,7 +963,8 @@ export const NotebookSetting = { logging: 'notebook.logging', confirmDeleteRunningCell: 'notebook.confirmDeleteRunningCell', remoteSaving: 'notebook.experimental.remoteSave', - gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols' + gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols', + cellExecutionScroll: 'notebook.executeCell.scrollToRevealBehavior' } as const; export const enum CellStatusbarAlignment { From b519f6254684d1fe147c87247b43ba1aa4b2d89b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:09:16 -0700 Subject: [PATCH 007/133] Move copy actions into a clipboard group --- .../contrib/terminal/browser/xterm/decorationAddon.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index af0e597ce43..7755dceb67a 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -371,6 +371,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { this._onDidRequestRunCommand.fire({ command }); } }); + // The second section is the clipboard section + actions.push(new Separator()); const labelCopy = localize("terminal.copyCommand", 'Copy Command'); actions.push({ class: undefined, tooltip: labelCopy, id: 'terminal.copyCommand', label: labelCopy, enabled: true, @@ -378,16 +380,13 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { }); } if (command.hasOutput()) { - if (actions.length > 0) { - actions.push(new Separator()); - } const labelCopyCommandAndOutput = localize("terminal.copyCommandAndOutput", 'Copy Command and Output'); actions.push({ class: undefined, tooltip: labelCopyCommandAndOutput, id: 'terminal.copyCommandAndOutput', label: labelCopyCommandAndOutput, enabled: true, run: () => { - const text = command.getOutput(); - if (typeof text === 'string') { - this._clipboardService.writeText(`${command.command !== '' ? command.command + '\n' : ''}${text}`); + const output = command.getOutput(); + if (typeof output === 'string') { + this._clipboardService.writeText(`${command.command !== '' ? command.command + '\n' : ''}${output}`); } } }); From 5bd7ca8a17010f410de6e69a5757086d732207b2 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:46:04 -0700 Subject: [PATCH 008/133] Add base tfidf impl (#193048) --- src/vs/base/common/tfIdf.ts | 206 ++++++++++++++++++++++++++ src/vs/base/test/common/tfIdf.test.ts | 194 ++++++++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 src/vs/base/common/tfIdf.ts create mode 100644 src/vs/base/test/common/tfIdf.test.ts diff --git a/src/vs/base/common/tfIdf.ts b/src/vs/base/common/tfIdf.ts new file mode 100644 index 00000000000..f76a24d1ce0 --- /dev/null +++ b/src/vs/base/common/tfIdf.ts @@ -0,0 +1,206 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +type SparseEmbedding = Record; +type TermFrequencies = Map; +type DocumentOccurrences = Map; + +function countMapFrom(values: Iterable): Map { + const map = new Map(); + for (const value of values) { + map.set(value, (map.get(value) ?? 0) + 1); + } + return map; +} + +interface DocumentChunkEntry { + readonly text: string; + readonly tf: TermFrequencies; +} + +export interface TfIdfDocument { + readonly key: string; + readonly textChunks: readonly string[]; +} + +export interface TfIdfScore { + readonly key: string; + readonly score: number; +} + +/** + * Implementation of tf-idf (term frequency-inverse document frequency) for a set of + * documents where each document contains one or more chunks of text. + * Each document is identified by a key, and the score for each document is computed + * by taking the max score over all the chunks in the document. + */ +export class TfIdfCalculator { + calculateScores(query: string, token: CancellationToken): TfIdfScore[] { + const embedding = this.computeEmbedding(query); + const idfCache = new Map(); + const scores: TfIdfScore[] = []; + // For each document, generate one score + for (const [key, doc] of this.documents) { + if (token.isCancellationRequested) { + return []; + } + + for (const chunk of doc.chunks) { + const score = this.computeSimilarityScore(chunk, embedding, idfCache); + if (score > 0) { + scores.push({ key, score }); + } + } + } + + return scores; + } + + /** + * Count how many times each term (word) appears in a string. + */ + private static termFrequencies(input: string): TermFrequencies { + return countMapFrom(TfIdfCalculator.splitTerms(input)); + } + + /** + * Break a string into terms (words). + */ + private static *splitTerms(input: string): Iterable { + const normalize = (word: string) => word.toLowerCase(); + + // Only match on words that are at least 3 characters long and start with a letter + for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) { + yield normalize(word); + + // eslint-disable-next-line local/code-no-look-behind-regex + const camelParts = word.split(/(?<=[a-z])(?=[A-Z])/g); + if (camelParts.length > 1) { + for (const part of camelParts) { + // Require at least 3 letters in the parts of a camel case word + if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) { + yield normalize(part); + } + } + } + } + } + + /** + * Total number of chunks + */ + private chunkCount = 0; + + private readonly chunkOccurrences: DocumentOccurrences = new Map(); + + private readonly documents = new Map; + }>(); + + updateDocuments(documents: ReadonlyArray): this { + for (const { key } of documents) { + this.deleteDocument(key); + } + + for (const doc of documents) { + const chunks: Array<{ text: string; tf: TermFrequencies }> = []; + for (const text of doc.textChunks) { + // TODO: See if we can compute the tf lazily + // The challenge is that we need to also update the `chunkOccurrences` + // and all of those updates need to get flushed before the real TF-IDF of + // anything is computed. + const tf = TfIdfCalculator.termFrequencies(text); + + // Update occurrences list + for (const term of tf.keys()) { + this.chunkOccurrences.set(term, (this.chunkOccurrences.get(term) ?? 0) + 1); + } + + chunks.push({ text, tf }); + } + + this.chunkCount += chunks.length; + this.documents.set(doc.key, { chunks }); + } + return this; + } + + deleteDocument(key: string) { + const doc = this.documents.get(key); + if (!doc) { + return; + } + + this.documents.delete(key); + this.chunkCount -= doc.chunks.length; + + // Update term occurrences for the document + for (const chunk of doc.chunks) { + for (const term of chunk.tf.keys()) { + const currentOccurrences = this.chunkOccurrences.get(term); + if (typeof currentOccurrences === 'number') { + const newOccurrences = currentOccurrences - 1; + if (newOccurrences <= 0) { + this.chunkOccurrences.delete(term); + } else { + this.chunkOccurrences.set(term, newOccurrences); + } + } + } + } + } + + private computeSimilarityScore(chunk: DocumentChunkEntry, queryEmbedding: SparseEmbedding, idfCache: Map): number { + // Compute the dot product between the chunk's embedding and the query embedding + + // Note that the chunk embedding is computed lazily on a per-term basis. + // This lets us skip a large number of calculations because the majority + // of chunks do not share any terms with the query. + + let sum = 0; + for (const [term, termTfidf] of Object.entries(queryEmbedding)) { + const chunkTf = chunk.tf.get(term); + if (!chunkTf) { + // Term does not appear in chunk so it has no contribution + continue; + } + + let chunkIdf = idfCache.get(term); + if (typeof chunkIdf !== 'number') { + chunkIdf = this.computeIdf(term); + idfCache.set(term, chunkIdf); + } + + const chunkTfidf = chunkTf * chunkIdf; + sum += chunkTfidf * termTfidf; + } + return sum; + } + + private computeEmbedding(input: string): SparseEmbedding { + const tf = TfIdfCalculator.termFrequencies(input); + return this.computeTfidf(tf); + } + + private computeIdf(term: string): number { + const chunkOccurrences = this.chunkOccurrences.get(term) ?? 0; + return chunkOccurrences > 0 + ? Math.log((this.chunkCount + 1) / chunkOccurrences) + : 0; + } + + private computeTfidf(termFrequencies: TermFrequencies): SparseEmbedding { + const embedding = Object.create(null); + for (const [word, occurrences] of termFrequencies) { + const idf = this.computeIdf(word); + if (idf > 0) { + embedding[word] = occurrences * idf; + } + } + return embedding; + } +} diff --git a/src/vs/base/test/common/tfIdf.test.ts b/src/vs/base/test/common/tfIdf.test.ts new file mode 100644 index 00000000000..69c094759eb --- /dev/null +++ b/src/vs/base/test/common/tfIdf.test.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TfIdfCalculator, TfIdfDocument, TfIdfScore } from 'vs/base/common/tfIdf'; + +/** + * Generates all permutations of an array. + * + * This is useful for testing to make sure order does not effect the result. + */ +function permutate(arr: T[]): T[][] { + if (arr.length === 0) { + return [[]]; + } + + const result: T[][] = []; + + for (let i = 0; i < arr.length; i++) { + const rest = [...arr.slice(0, i), ...arr.slice(i + 1)]; + const permutationsRest = permutate(rest); + for (let j = 0; j < permutationsRest.length; j++) { + result.push([arr[i], ...permutationsRest[j]]); + } + } + + return result; +} + +function assertScoreOrdersEqual(actualScores: TfIdfScore[], expectedScoreKeys: string[]): void { + actualScores.sort((a, b) => (b.score - a.score) || a.key.localeCompare(b.key)); + assert.strictEqual(actualScores.length, expectedScoreKeys.length); + for (let i = 0; i < expectedScoreKeys.length; i++) { + assert.strictEqual(actualScores[i].key, expectedScoreKeys[i]); + } +} + +suite('TF-IDF Calculator', function () { + test('Should return no scores when no documents are given', () => { + const tfidf = new TfIdfCalculator(); + const scores = tfidf.calculateScores('something', CancellationToken.None); + assertScoreOrdersEqual(scores, []); + }); + + test('Should return no scores for term not in document', () => { + const tfidf = new TfIdfCalculator().updateDocuments([ + makeDocument('A', 'cat dog fish'), + ]); + const scores = tfidf.calculateScores('elepant', CancellationToken.None); + assertScoreOrdersEqual(scores, []); + }); + + test('Should return scores for document with exact match', () => { + for (const docs of permutate([ + makeDocument('A', 'cat dog cat'), + makeDocument('B', 'cat fish'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('dog', CancellationToken.None); + assertScoreOrdersEqual(scores, ['A']); + } + }); + + test('Should return document with more matches first', () => { + for (const docs of permutate([ + makeDocument('/A', 'cat dog cat'), + makeDocument('/B', 'cat fish'), + makeDocument('/C', 'frog'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B']); + } + }); + + test('Should return document with more matches first when term appears in all documents', () => { + for (const docs of permutate([ + makeDocument('/A', 'cat dog cat cat'), + makeDocument('/B', 'cat fish'), + makeDocument('/C', 'frog cat cat'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/C', '/B']); + } + }); + + test('Should weigh less common term higher', () => { + for (const docs of permutate([ + makeDocument('/A', 'cat dog cat'), + makeDocument('/B', 'fish'), + makeDocument('/C', 'cat cat cat cat'), + makeDocument('/D', 'cat fish') + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cat the dog', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/C', '/D']); + } + }); + + test('Should weigh chunks with less common terms higher', () => { + for (const docs of permutate([ + makeDocument('/A', ['cat dog cat', 'fish']), + makeDocument('/B', ['cat cat cat cat dog', 'dog']) + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/B', '/A']); + } + + for (const docs of permutate([ + makeDocument('/A', ['cat dog cat', 'fish']), + makeDocument('/B', ['cat cat cat cat dog', 'dog']) + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('dog', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B', '/B']); + } + + for (const docs of permutate([ + makeDocument('/A', ['cat dog cat', 'fish']), + makeDocument('/B', ['cat cat cat cat dog', 'dog']) + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cat the dog', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/B', '/A', '/B']); + } + + for (const docs of permutate([ + makeDocument('/A', ['cat dog cat', 'fish']), + makeDocument('/B', ['cat cat cat cat dog', 'dog']) + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('lake fish', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A']); + } + }); + + test('Should ignore case and punctuation', () => { + for (const docs of permutate([ + makeDocument('/A', 'Cat doG.cat'), + makeDocument('/B', 'cAt fiSH'), + makeDocument('/C', 'frOg'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('. ,CaT! ', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B']); + } + }); + + test('Should match on camelCase words', () => { + for (const docs of permutate([ + makeDocument('/A', 'catDog cat'), + makeDocument('/B', 'fishCatFish'), + makeDocument('/C', 'frogcat'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('catDOG', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B']); + } + }); + + test('Should not match document after delete', () => { + const docA = makeDocument('/A', 'cat dog cat'); + const docB = makeDocument('/B', 'cat fish'); + const docC = makeDocument('/C', 'frog'); + + const tfidf = new TfIdfCalculator().updateDocuments([docA, docB, docC]); + let scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B']); + + tfidf.deleteDocument(docA.key); + scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/B']); + + tfidf.deleteDocument(docC.key); + scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/B']); + + tfidf.deleteDocument(docB.key); + scores = tfidf.calculateScores('cat', CancellationToken.None); + assertScoreOrdersEqual(scores, []); + }); +}); + +function makeDocument(key: string, content: string | string[]): TfIdfDocument { + return { + key, + textChunks: Array.isArray(content) ? content : [content], + }; +} From 153335bd0611fcb46cf7d0e879eca53e11f5d960 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:07:16 -0700 Subject: [PATCH 009/133] Allow codeActionsOnSave to trigger for autoSave (#190516) * removed saveReason check for autosave * modified setting values and enabling on autosave or not * cleanup * add default to false settings * modified code action save settings * changed default setting * cleanup code and added backward compatibility * aded minor comment and checking commit signing * second check on commit signing (sorry) * fix typo * small bugfix and adding proper backward compatibility * deprecate boolean values by converting them --- .../browser/codeActionsContribution.ts | 28 ++++++--- .../codeEditor/browser/saveParticipants.ts | 63 ++++++++++--------- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index e8bfcc38559..bd0c4c4393c 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -17,11 +17,18 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { CodeActionsExtensionPoint, ContributedCodeAction } from 'vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +const createCodeActionsAutoSave = (description: string): IJSONSchema => { + return { + type: 'string', + enum: ['always', 'never', 'explicit'], + enumDescriptions: [nls.localize('alwaysSave', 'Always triggers Code Actions on save'), nls.localize('neverSave', 'Never triggers Code Actions on save'), nls.localize('explicitSave', 'Triggers Code Actions only when explicitly saved')], + default: 'explicit', + description: description + }; +}; + const codeActionsOnSaveDefaultProperties = Object.freeze({ - 'source.fixAll': { - type: 'boolean', - description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.") - } + 'source.fixAll': createCodeActionsAutoSave(nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.")), }); const codeActionsOnSaveSchema: IConfigurationPropertySchema = { @@ -30,7 +37,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { type: 'object', properties: codeActionsOnSaveDefaultProperties, additionalProperties: { - type: 'boolean' + type: 'string' }, }, { @@ -38,8 +45,12 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { items: { type: 'string' } } ], + markdownDescription: nls.localize('editor.codeActionsOnSave', 'Run CodeActions for the editor on save. CodeActions must be specified and the editor must not be shutting down. Example: `"source.organizeImports": "explicit" `'), + type: 'object', + additionalProperties: { + type: 'string' + }, default: {}, - description: nls.localize('codeActionsOnSave', "Code Action kinds to be run on save."), scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, }; @@ -77,10 +88,7 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) { const newProperties: IJSONSchemaMap = { ...codeActionsOnSaveDefaultProperties }; for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) { - newProperties[sourceAction] = { - type: 'boolean', - description: nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title) - }; + newProperties[sourceAction] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)); } codeActionsOnSaveSchema.properties = newProperties; Registry.as(Extensions.Configuration) diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 939c8c05661..fc93eb31ecd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -12,25 +13,24 @@ 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 { Selection } from 'vs/editor/common/core/selection'; +import { CodeActionProvider, CodeActionTriggerType } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeActionTriggerType, CodeActionProvider } from 'vs/editor/common/languages'; -import { applyCodeAction, ApplyCodeActionReason, getCodeActions } from 'vs/editor/contrib/codeAction/browser/codeAction'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ApplyCodeActionReason, applyCodeAction, getCodeActions } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionKind, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; -import { formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/browser/format'; +import { FormattingMode, formatDocumentRangesWithSelectedProvider, formatDocumentWithSelectedProvider } from 'vs/editor/contrib/format/browser/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressStep, IProgress, Progress } from 'vs/platform/progress/common/progress'; -import { ITextFileService, ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { SaveReason } from 'vs/workbench/common/editor'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { getModifiedRanges } from 'vs/workbench/contrib/format/browser/formatModified'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ITextFileEditorModel, ITextFileSaveParticipant, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { @@ -277,24 +277,24 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { return; } - // Do not run code actions on auto save - if (env.reason !== SaveReason.EXPLICIT) { - return undefined; - } - const textEditorModel = model.textEditorModel; + const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageId(), resource: textEditorModel.uri }; - const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageId(), resource: model.resource }; - const setting = this.configurationService.getValue<{ [kind: string]: boolean } | string[]>('editor.codeActionsOnSave', settingsOverrides); + // Convert boolean values to strings + const setting = this.configurationService.getValue<{ [kind: string]: string | boolean }>('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; } + const convertedSetting: { [kind: string]: string } = {}; + for (const key in setting) { + if (typeof setting[key] === 'boolean') { + convertedSetting[key] = setting[key] ? 'explicit' : 'never'; + } else if (typeof setting[key] === 'string') { + convertedSetting[key] = setting[key] as string; + } + } - const settingItems: string[] = Array.isArray(setting) - ? setting - : Object.keys(setting).filter(x => setting[x]); - - const codeActionsOnSave = this.createCodeActionsOnSave(settingItems); + const codeActionsOnSave = this.createCodeActionsOnSave(Object.keys(convertedSetting)); if (!Array.isArray(setting)) { codeActionsOnSave.sort((a, b) => { @@ -315,14 +315,15 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { return undefined; } - const excludedActions = Array.isArray(setting) - ? [] - : Object.keys(setting) - .filter(x => setting[x] === false) - .map(x => new CodeActionKind(x)); + const excludedActions = Object.keys(setting) + .filter(x => convertedSetting[x] === 'never' || false) + .map(x => new CodeActionKind(x)); progress.report({ message: localize('codeaction', "Quick Fixes") }); - await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token); + + const filteredSaveList = codeActionsOnSave.filter(x => convertedSetting[x.value] === 'always' || (convertedSetting[x.value] === 'explicit') && env.reason === SaveReason.EXPLICIT); + + await this.applyOnSaveActions(textEditorModel, filteredSaveList, excludedActions, progress, token); } private createCodeActionsOnSave(settingItems: readonly string[]): CodeActionKind[] { From 9c12e02b7ba9d8decabbf4ff45d3fc2891f55f04 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:22:16 -0700 Subject: [PATCH 010/133] Action Widget won't show label for edits in hover when no edits are available (#192238) * initial working version v1 * cleanup and fixed error in terminal quick fix * code cleanup, making onFocus optional, removing terminal quickfix edits * added logic for cancellation v1 and removing extra call to rerender * registered cancellable tokens and added better checks onFocus * code cleanup --- .../browser/codeActionController.ts | 8 +++++++ .../codeAction/browser/codeActionMenu.ts | 3 ++- .../actionWidget/browser/actionList.ts | 22 ++++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index 14307f031e5..a56fb161ee2 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -30,6 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; import { CodeActionModel, CodeActionsState } from './codeActionModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface IActionShowOptions { @@ -251,6 +252,13 @@ export class CodeActionController extends Disposable implements IEditorContribut }, onHide: () => { this._editor?.focus(); + }, + onFocus: async (action: CodeActionItem, token: CancellationToken) => { + await action.resolve(token); + if (token.isCancellationRequested) { + return; + } + return { canPreview: !!action.action.edit?.edits.length }; } }; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 8108d979327..7d15a54b44e 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -44,7 +44,8 @@ export function toMenuItems( item: action, group: uncategorizedCodeActionGroup, disabled: !!action.action.disabled, - label: action.action.disabled || action.action.title + label: action.action.disabled || action.action.title, + canPreview: !!action.action.edit?.edits.length, }; }); } diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index e4422c393d6..6a4c9402ca2 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -6,6 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -24,6 +25,7 @@ export const previewSelectedActionCommand = 'previewSelectedCodeAction'; export interface IActionListDelegate { onHide(didCancel?: boolean): void; onSelect(action: T, preview?: boolean): void; + onFocus?(action: T, cancellationToken: CancellationToken): Promise<{ canPreview: boolean } | void>; } export interface IActionListItem { @@ -32,8 +34,8 @@ export interface IActionListItem { readonly group?: { kind?: any; icon?: ThemeIcon; title: string }; readonly disabled?: boolean; readonly label?: string; - readonly keybinding?: ResolvedKeybinding; + canPreview?: boolean | undefined; } interface IActionMenuTemplateData { @@ -126,7 +128,7 @@ class ActionItemRenderer implements IListRenderer, IAction if (element.disabled) { data.container.title = element.label; } else if (actionTitle && previewTitle) { - if (this._supportsPreview) { + if (this._supportsPreview && element.canPreview) { data.container.title = localize({ key: 'label-preview', comment: ['placeholders are keybindings, e.g "F2 to apply, Shift+F2 to preview"'] }, "{0} to apply, {1} to preview", actionTitle, previewTitle); } else { data.container.title = localize({ key: 'label', comment: ['placeholder is a keybinding, e.g "F2 to apply"'] }, "{0} to apply", actionTitle); @@ -168,6 +170,8 @@ export class ActionList extends Disposable { private readonly _allMenuItems: readonly IActionListItem[]; + private readonly cts = this._register(new CancellationTokenSource()); + constructor( user: string, preview: boolean, @@ -230,6 +234,7 @@ export class ActionList extends Disposable { hide(didCancel?: boolean): void { this._delegate.onHide(didCancel); + this.cts.cancel(); this._contextViewService.hideContextView(); } @@ -302,7 +307,18 @@ export class ActionList extends Disposable { } } - private onListHover(e: IListMouseEvent>): void { + private async onListHover(e: IListMouseEvent>) { + const element = e.element; + if (element && element.item && this.focusCondition(element)) { + if (this._delegate.onFocus && !element.disabled && element.kind === ActionListItemKind.Action) { + const result = await this._delegate.onFocus(element.item, this.cts.token); + element.canPreview = result ? result.canPreview : undefined; + } + if (e.index) { + this._list.splice(e.index, 1, [element]); + } + } + this._list.setFocus(typeof e.index === 'number' ? [e.index] : []); } From bccfade64adb249f57c8fcf03cba41609f76ce5c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 15 Sep 2023 01:17:42 +0200 Subject: [PATCH 011/133] Improve handling of extension descriptions (#193145) --- .../api/common/extHostExtensionService.ts | 6 +-- .../workbench/api/common/extensionHostMain.ts | 2 +- .../extensions/browser/extensionService.ts | 39 ++++++-------- .../browser/webWorkerExtensionHost.ts | 14 ++--- .../common/abstractExtensionService.ts | 22 ++++---- .../common/extensionDescriptionRegistry.ts | 28 +++++++++- .../extensions/common/extensionHostManager.ts | 32 ++++++------ .../common/extensionHostProtocol.ts | 12 +++-- .../services/extensions/common/extensions.ts | 52 +++++++++++++------ .../extensions/common/remoteExtensionHost.ts | 12 ++--- .../localProcessExtensionHost.ts | 12 ++--- .../nativeExtensionService.ts | 50 ++++++++---------- 12 files changed, 155 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 99fccebaee0..404f8f7d9c0 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -157,9 +157,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._readyToStartExtensionHost = new Barrier(); this._readyToRunExtensions = new Barrier(); this._eagerExtensionsActivated = new Barrier(); - this._activationEventsReader = new SyncedActivationEventsReader(this._initData.activationEvents); - this._globalRegistry = new ExtensionDescriptionRegistry(this._activationEventsReader, this._initData.allExtensions); - const myExtensionsSet = new ExtensionIdentifierSet(this._initData.myExtensions); + this._activationEventsReader = new SyncedActivationEventsReader(this._initData.extensions.activationEvents); + this._globalRegistry = new ExtensionDescriptionRegistry(this._activationEventsReader, this._initData.extensions.allExtensions); + const myExtensionsSet = new ExtensionIdentifierSet(this._initData.extensions.myExtensions); this._myRegistry = new ExtensionDescriptionRegistry( this._activationEventsReader, filterExtensions(this._globalRegistry, myExtensionsSet) diff --git a/src/vs/workbench/api/common/extensionHostMain.ts b/src/vs/workbench/api/common/extensionHostMain.ts index 40f1cdaf146..2bd275cbc21 100644 --- a/src/vs/workbench/api/common/extensionHostMain.ts +++ b/src/vs/workbench/api/common/extensionHostMain.ts @@ -194,7 +194,7 @@ export class ExtensionHostMain { } private static _transform(initData: IExtensionHostInitData, rpcProtocol: RPCProtocol): IExtensionHostInitData { - initData.allExtensions.forEach((ext) => { + initData.extensions.allExtensions.forEach((ext) => { (>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)); }); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 0317de83928..25885c7c4f9 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -26,14 +26,15 @@ import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWo import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker'; -import { ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi'; import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; -import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; +import { IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; @@ -70,7 +71,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const extensionHostFactory = new BrowserExtensionHostFactory( extensionsProposedApi, () => this._scanWebExtensions(), - () => this._getExtensions(), + () => this._getExtensionRegistrySnapshotWhenReady(), instantiationService, remoteAgentService, remoteAuthorityResolverService, @@ -216,7 +217,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { constructor( private readonly _extensionsProposedApi: ExtensionsProposedApi, private readonly _scanWebExtensions: () => Promise, - private readonly _getExtensions: () => Promise, + private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -255,18 +256,14 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true); const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); - return { - allExtensions: localExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } else { // restart case - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } } }; @@ -275,28 +272,26 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider { return { remoteAuthority: remoteAuthority, - getInitData: async () => { - const allExtensions = await this._getExtensions(); + getInitData: async (): Promise => { + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); const remoteEnv = await this._remoteAgentService.getEnvironment(); if (!remoteEnv) { throw new Error('Cannot provide init data for remote extension host!'); } - const myExtensions = runningLocations.filterByExtensionHostKind(allExtensions, ExtensionHostKind.Remote); + const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); - const initData = { + return { connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority), pid: remoteEnv.pid, appRoot: remoteEnv.appRoot, extensionHostLogsPath: remoteEnv.extensionHostLogsPath, globalStorageHome: remoteEnv.globalStorageHome, workspaceStorageHome: remoteEnv.workspaceStorageHome, - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier), + extensions, }; - - return initData; } }; } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 5ec7f0690c6..a7ce017ab9f 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { Barrier } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; @@ -15,7 +16,6 @@ import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; @@ -25,15 +25,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { parentOriginHash } from 'vs/base/browser/iframe'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { ExtensionHostExitCode, IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions'; export interface IWebWorkerExtensionHostInitData { - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface IWebWorkerExtensionHostDataProvider { @@ -43,7 +41,7 @@ export interface IWebWorkerExtensionHostDataProvider { export class WebWorkerExtensionHost extends Disposable implements IExtensionHost { public readonly remoteAuthority = null; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private readonly _onDidExit = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onDidExit.event; @@ -267,8 +265,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost private async _createExtHostInitData(): Promise { const initData = await this._initDataProvider.getInitData(); + this.extensions = initData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(initData.allExtensions, initData.myExtensions); const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; let nlsUrlWithDetails: URI | undefined = undefined; // Only use the nlsBaseUrl if we are using a language other than the default, English. @@ -304,9 +302,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost includeStack: false, logNative: this._environmentService.debugRenderer }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), nlsBaseUrl: nlsUrlWithDetails, telemetryInfo: { sessionId: this._telemetryService.sessionId, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index ccb729ce206..e1308458a40 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -31,7 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionDescriptionRegistryLock, IActivationEventsReader, LockableExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ExtensionDescriptionRegistryLock, ExtensionDescriptionRegistrySnapshot, IActivationEventsReader, LockableExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionHostManager, createExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -298,22 +298,22 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._doHandleExtensionPoints(([]).concat(toAdd).concat(toRemove)); // Update the extension host - await this._updateExtensionsOnExtHosts(toAdd, toRemove.map(e => e.identifier)); + await this._updateExtensionsOnExtHosts(result.versionId, toAdd, toRemove.map(e => e.identifier)); for (let i = 0; i < toAdd.length; i++) { this._activateAddedExtensionIfNeeded(toAdd[i]); } } - private async _updateExtensionsOnExtHosts(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { + private async _updateExtensionsOnExtHosts(versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { const removedRunningLocation = this._runningLocations.deltaExtensions(toAdd, toRemove); const promises = this._extensionHostManagers.map( - extHostManager => this._updateExtensionsOnExtHost(extHostManager, toAdd, toRemove, removedRunningLocation) + extHostManager => this._updateExtensionsOnExtHost(extHostManager, versionId, toAdd, toRemove, removedRunningLocation) ); await Promise.all(promises); } - private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap): Promise { + private async _updateExtensionsOnExtHost(extensionHostManager: IExtensionHostManager, versionId: number, toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[], removedRunningLocation: ExtensionIdentifierMap): Promise { const myToAdd = this._runningLocations.filterByExtensionHostManager(toAdd, extensionHostManager); const myToRemove = filterExtensionIdentifiers(toRemove, removedRunningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation)); const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd); @@ -322,7 +322,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const printIds = (extensions: ExtensionIdentifier[]) => extensions.map(e => e.value).join(','); this._logService.info(`AbstractExtensionService: Calling deltaExtensions: toRemove: [${printIds(toRemove)}], toAdd: [${printExtIds(toAdd)}], myToRemove: [${printIds(myToRemove)}], myToAdd: [${printExtIds(myToAdd)}],`); } - await extensionHostManager.deltaExtensions({ toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) }); + await extensionHostManager.deltaExtensions({ versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) }); } public canAddExtension(extension: IExtensionDescription): boolean { @@ -440,11 +440,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._processExtensions(lock, resolvedExtensions); // Start extension hosts which are not automatically started - const allExtensions = this._registry.getAllExtensionDescriptions(); + const snapshot = this._registry.getSnapshot(); for (const extHostManager of this._extensionHostManagers) { if (extHostManager.startup !== ExtensionHostStartup.EagerAutoStart) { - const extensions = this._runningLocations.filterByExtensionHostManager(allExtensions, extHostManager); - extHostManager.start(allExtensions, extensions.map(extension => extension.identifier)); + const extensions = this._runningLocations.filterByExtensionHostManager(snapshot.extensions, extHostManager); + extHostManager.start(snapshot.versionId, snapshot.extensions, extensions.map(extension => extension.identifier)); } } } finally { @@ -934,8 +934,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return this._registry.getAllExtensionDescriptions(); } - protected _getExtensions(): Promise { - return this._installedExtensionsReady.wait().then(() => this.extensions); + protected _getExtensionRegistrySnapshotWhenReady(): Promise { + return this._installedExtensionsReady.wait().then(() => this._registry.getSnapshot()); } public getExtension(id: string): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 16365d94990..96b940c9ef0 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -10,6 +10,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' export class DeltaExtensionsResult { constructor( + public readonly versionId: number, public readonly removedDueToLooping: IExtensionDescription[] ) { } } @@ -45,6 +46,7 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti private readonly _onDidChange = new Emitter(); public readonly onDidChange = this._onDidChange.event; + private _versionId: number = 0; private _extensionDescriptions: IExtensionDescription[]; private _extensionsMap!: ExtensionIdentifierMap; private _extensionsArr!: IExtensionDescription[]; @@ -93,10 +95,14 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti } } - public set(extensionDescriptions: IExtensionDescription[]): void { + public set(extensionDescriptions: IExtensionDescription[]): { versionId: number } { this._extensionDescriptions = extensionDescriptions; this._initialize(); + this._versionId++; this._onDidChange.fire(undefined); + return { + versionId: this._versionId + }; } public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult { @@ -112,8 +118,9 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier)); this._initialize(); + this._versionId++; this._onDidChange.fire(undefined); - return new DeltaExtensionsResult(looping); + return new DeltaExtensionsResult(this._versionId, looping); } private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] { @@ -217,6 +224,13 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti return this._extensionsArr.slice(0); } + public getSnapshot(): ExtensionDescriptionRegistrySnapshot { + return new ExtensionDescriptionRegistrySnapshot( + this._versionId, + this.getAllExtensionDescriptions() + ); + } + public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { const extension = this._extensionsMap.get(extensionId); return extension ? extension : undefined; @@ -239,6 +253,13 @@ export class ExtensionDescriptionRegistry implements IReadOnlyExtensionDescripti } } +export class ExtensionDescriptionRegistrySnapshot { + constructor( + public readonly versionId: number, + public readonly extensions: readonly IExtensionDescription[] + ) { } +} + export interface IActivationEventsReader { readActivationEvents(extensionDescription: IExtensionDescription): string[] | undefined; } @@ -282,6 +303,9 @@ export class LockableExtensionDescriptionRegistry implements IReadOnlyExtensionD public getAllExtensionDescriptions(): IExtensionDescription[] { return this._actual.getAllExtensionDescriptions(); } + public getSnapshot(): ExtensionDescriptionRegistrySnapshot { + return this._actual.getSnapshot(); + } public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { return this._actual.getExtensionDescription(extensionId); } diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 59c293fa8b1..0980a41d10c 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -53,7 +53,7 @@ export interface IExtensionHostManager { * Returns `null` if no resolver for `remoteAuthority` is found. */ getCanonicalURI(remoteAuthority: string, uri: URI): Promise; - start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise; + start(extensionRegistryVersionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise; extensionTestsExecute(): Promise; setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } @@ -436,12 +436,12 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return proxy.getCanonicalURI(remoteAuthority, uri); } - public async start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { + public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { const proxy = await this._proxy; if (!proxy) { return; } - const deltaExtensions = this._extensionHost.extensions.set(allExtensions, myExtensions); + const deltaExtensions = this._extensionHost.extensions!.set(extensionRegistryVersionId, allExtensions, myExtensions); return proxy.startExtensionHost(deltaExtensions); } @@ -457,17 +457,21 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { return this._extensionHost.runningLocation.equals(runningLocation); } - public async deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise { + public async deltaExtensions(incomingExtensionsDelta: IExtensionDescriptionDelta): Promise { const proxy = await this._proxy; if (!proxy) { return; } - this._extensionHost.extensions.delta(extensionsDelta); - return proxy.deltaExtensions(extensionsDelta); + const outgoingExtensionsDelta = this._extensionHost.extensions!.delta(incomingExtensionsDelta); + if (!outgoingExtensionsDelta) { + // The extension host already has this version of the extensions. + return; + } + return proxy.deltaExtensions(outgoingExtensionsDelta); } public containsExtension(extensionId: ExtensionIdentifier): boolean { - return this._extensionHost.extensions.containsExtension(extensionId); + return this._extensionHost.extensions?.containsExtension(extensionId) ?? false; } public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { @@ -529,7 +533,7 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos return this._actual; } const actual = this._createActual(reason); - await actual.start([], []); + await actual.start(this._lazyStartExtensions!.versionId, this._lazyStartExtensions!.allExtensions, this._lazyStartExtensions!.myExtensions); return actual; } @@ -550,13 +554,12 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos this._lazyStartExtensions!.delta(extensionsDelta); if (extensionsDelta.myToAdd.length > 0) { const actual = this._createActual(`contains ${extensionsDelta.myToAdd.length} new extension(s) (installed or enabled): ${extensionsDelta.myToAdd.map(extId => extId.value)}`); - const { toAdd, myToAdd } = this._lazyStartExtensions!.toDelta(); - actual.start(toAdd, myToAdd); + await actual.start(this._lazyStartExtensions!.versionId, this._lazyStartExtensions!.allExtensions, this._lazyStartExtensions!.myExtensions); return; } } public containsExtension(extensionId: ExtensionIdentifier): boolean { - return this._extensionHost.extensions.containsExtension(extensionId); + return this._extensionHost.extensions?.containsExtension(extensionId) ?? false; } public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { await this._startCalled.wait(); @@ -615,17 +618,16 @@ class LazyCreateExtensionHostManager extends Disposable implements IExtensionHos } throw new Error(`Cannot resolve canonical URI`); } - public async start(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { + public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise { if (myExtensions.length > 0) { // there are actual extensions, so let's launch the extension host const actual = this._createActual(`contains ${myExtensions.length} extension(s): ${myExtensions.map(extId => extId.value)}.`); - const result = actual.start(allExtensions, myExtensions); + const result = actual.start(extensionRegistryVersionId, allExtensions, myExtensions); this._startCalled.open(); return result; } // there are no actual extensions running, store extensions in `this._lazyStartExtensions` - this._lazyStartExtensions = new ExtensionHostExtensions(); - this._lazyStartExtensions.set(allExtensions, myExtensions); + this._lazyStartExtensions = new ExtensionHostExtensions(extensionRegistryVersionId, allExtensions, myExtensions); this._startCalled.open(); } public async extensionTestsExecute(): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 4472c9b7bdb..87b031a1c56 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -9,7 +9,15 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; +export interface IExtensionDescriptionSnapshot { + readonly versionId: number; + readonly allExtensions: IExtensionDescription[]; + readonly activationEvents: { [extensionId: string]: string[] }; + readonly myExtensions: ExtensionIdentifier[]; +} + export interface IExtensionDescriptionDelta { + readonly versionId: number; readonly toRemove: ExtensionIdentifier[]; readonly toAdd: IExtensionDescription[]; readonly addActivationEvents: { [extensionId: string]: string[] }; @@ -27,9 +35,7 @@ export interface IExtensionHostInitData { parentPid: number | 0; environment: IEnvironment; workspace?: IStaticWorkspaceData | null; - activationEvents: { [extensionId: string]: string[] }; - allExtensions: IExtensionDescription[]; - myExtensions: ExtensionIdentifier[]; + extensions: IExtensionDescriptionSnapshot; nlsBaseUrl?: URI; telemetryInfo: { readonly sessionId: string; diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 436f780bd00..65d59f165b5 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -13,7 +13,7 @@ import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, Ex import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind'; -import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IExtensionDescriptionDelta, IExtensionDescriptionSnapshot } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ApiProposalName } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -120,7 +120,7 @@ export interface IExtensionHost { * extension will execute or is executing on this extension host. * **NOTE**: this will reflect extensions correctly only after `start()` resolves. */ - readonly extensions: ExtensionHostExtensions; + readonly extensions: ExtensionHostExtensions | null; readonly onExit: Event<[number, string | null]>; start(): Promise; @@ -130,26 +130,41 @@ export interface IExtensionHost { } export class ExtensionHostExtensions { - + private _versionId: number; private _allExtensions: IExtensionDescription[]; private _myExtensions: ExtensionIdentifier[]; - constructor() { - this._allExtensions = []; - this._myExtensions = []; + public get versionId(): number { + return this._versionId; } - public toDelta(): IExtensionDescriptionDelta { + public get allExtensions(): IExtensionDescription[] { + return this._allExtensions; + } + + public get myExtensions(): ExtensionIdentifier[] { + return this._myExtensions; + } + + constructor(versionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]) { + this._versionId = versionId; + this._allExtensions = allExtensions.slice(0); + this._myExtensions = myExtensions.slice(0); + } + + toSnapshot(): IExtensionDescriptionSnapshot { return { - toRemove: [], - toAdd: this._allExtensions, - addActivationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions), - myToRemove: [], - myToAdd: this._myExtensions + versionId: this._versionId, + allExtensions: this._allExtensions, + myExtensions: this._myExtensions, + activationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions) }; } - public set(allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta { + public set(versionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta { + if (this._versionId > versionId) { + throw new Error(`ExtensionHostExtensions: invalid versionId ${versionId} (current: ${this._versionId})`); + } const toRemove: ExtensionIdentifier[] = []; const toAdd: IExtensionDescription[] = []; const myToRemove: ExtensionIdentifier[] = []; @@ -210,12 +225,17 @@ export class ExtensionHostExtensions { } const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd); - const delta = { toRemove, toAdd, addActivationEvents, myToRemove, myToAdd }; + const delta = { versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd }; this.delta(delta); return delta; } - public delta(extensionsDelta: IExtensionDescriptionDelta): void { + public delta(extensionsDelta: IExtensionDescriptionDelta): IExtensionDescriptionDelta | null { + if (this._versionId >= extensionsDelta.versionId) { + // ignore older deltas + return null; + } + const { toRemove, toAdd, myToRemove, myToAdd } = extensionsDelta; // First handle removals const toRemoveSet = new ExtensionIdentifierSet(toRemove); @@ -239,6 +259,8 @@ export class ExtensionHostExtensions { for (const extensionId of myToAdd) { this._myExtensions.push(extensionId); } + + return extensionsDelta; } public containsExtension(extensionId: ExtensionIdentifier): boolean { diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 7ac17418689..a4036f94bd2 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -36,8 +35,7 @@ export interface IRemoteExtensionHostInitData { readonly extensionHostLogsPath: URI; readonly globalStorageHome: URI; readonly workspaceStorageHome: URI; - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface IRemoteExtensionHostDataProvider { @@ -49,7 +47,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { public readonly remoteAuthority: string; public readonly startup = ExtensionHostStartup.EagerAutoStart; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); public readonly onExit: Event<[number, string | null]> = this._onExit.event; @@ -203,8 +201,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { private async _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise { const remoteInitData = await this._initDataProvider.getInitData(); + this.extensions = remoteInitData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(remoteInitData.allExtensions, remoteInitData.myExtensions); return { commit: this._productService.commit, version: this._productService.version, @@ -240,9 +238,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { includeStack: false, logNative: Boolean(this._environmentService.debugExtensionHost.debugId) }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), telemetryInfo: { sessionId: this._telemetryService.sessionId, machineId: this._telemetryService.machineId, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index b72e19b9ec6..c635003cea3 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -21,7 +21,6 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, ILoggerService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/common/native'; @@ -42,8 +41,7 @@ import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/life import { parseExtensionDevOptions } from '../common/extensionDevOptions'; export interface ILocalProcessExtensionHostInitData { - readonly allExtensions: IExtensionDescription[]; - readonly myExtensions: ExtensionIdentifier[]; + readonly extensions: ExtensionHostExtensions; } export interface ILocalProcessExtensionHostDataProvider { @@ -93,7 +91,7 @@ export class ExtensionHostProcess { export class NativeLocalProcessExtensionHost implements IExtensionHost { public readonly remoteAuthority = null; - public readonly extensions = new ExtensionHostExtensions(); + public extensions: ExtensionHostExtensions | null = null; private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); public readonly onExit: Event<[number, string]> = this._onExit.event; @@ -416,8 +414,8 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { private async _createExtHostInitData(): Promise { const initData = await this._initDataProvider.getInitData(); + this.extensions = initData.extensions; const workspace = this._contextService.getWorkspace(); - const deltaExtensions = this.extensions.set(initData.allExtensions, initData.myExtensions); return { commit: this._productService.commit, version: this._productService.version, @@ -454,9 +452,7 @@ export class NativeLocalProcessExtensionHost implements IExtensionHost { includeStack: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose), logNative: !this._isExtensionDevTestFromCli && this._isExtensionDevHost }, - allExtensions: deltaExtensions.toAdd, - activationEvents: deltaExtensions.addActivationEvents, - myExtensions: deltaExtensions.myToAdd, + extensions: this.extensions.toSnapshot(), telemetryInfo: { sessionId: this._telemetryService.sessionId, machineId: this._telemetryService.machineId, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index d63228db9d2..c5adae5f0d2 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -40,6 +40,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { EnablementState, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { AbstractExtensionService, ExtensionHostCrashTracker, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI, extensionIsEnabled } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionDescriptionRegistrySnapshot } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -47,9 +48,9 @@ import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/e import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker'; -import { ExtensionHostStartup, IExtensionHost, IExtensionService, WebWorkerExtHostConfigValue, toExtension, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, WebWorkerExtHostConfigValue, toExtension, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi'; -import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; +import { IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost'; import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner'; import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -91,7 +92,7 @@ export class NativeExtensionService extends AbstractExtensionService implements const extensionHostFactory = new NativeExtensionHostFactory( extensionsProposedApi, extensionScanner, - () => this._getExtensions(), + () => this._getExtensionRegistrySnapshotWhenReady(), instantiationService, environmentService, extensionEnablementService, @@ -521,7 +522,7 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { constructor( private readonly _extensionsProposedApi: ExtensionsProposedApi, private readonly _extensionScanner: CachedExtensionScanner, - private readonly _getExtensions: () => Promise, + private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @@ -581,22 +582,17 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); + const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier)); if (isCI) { this._logService.info(`NativeExtensionHostFactory._createLocalProcessExtensionHostDataProvider.myExtensions: ${myExtensions.map(ext => ext.identifier.value).join(',')}`); } - - return { - allExtensions: localExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + return { extensions }; } else { // restart case - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } } }; @@ -605,12 +601,10 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { private _createWebWorkerExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: LocalWebWorkerRunningLocation): IWebWorkerExtensionHostDataProvider { return { getInitData: async (): Promise => { - const allExtensions = await this._getExtensions(); - const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation); - return { - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier) - }; + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); + const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); + return { extensions }; } }; } @@ -618,28 +612,26 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider { return { remoteAuthority: remoteAuthority, - getInitData: async () => { - const allExtensions = await this._getExtensions(); + getInitData: async (): Promise => { + const snapshot = await this._getExtensionRegistrySnapshotWhenReady(); const remoteEnv = await this._remoteAgentService.getEnvironment(); if (!remoteEnv) { throw new Error('Cannot provide init data for remote extension host!'); } - const myExtensions = runningLocations.filterByExtensionHostKind(allExtensions, ExtensionHostKind.Remote); + const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote); + const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier)); - const initData = { + return { connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority), pid: remoteEnv.pid, appRoot: remoteEnv.appRoot, extensionHostLogsPath: remoteEnv.extensionHostLogsPath, globalStorageHome: remoteEnv.globalStorageHome, workspaceStorageHome: remoteEnv.workspaceStorageHome, - allExtensions: allExtensions, - myExtensions: myExtensions.map(extension => extension.identifier), + extensions, }; - - return initData; } }; } From 6006a390791931cdfe486b34738250232c6c330e Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Thu, 14 Sep 2023 15:58:46 -0700 Subject: [PATCH 012/133] Add support to de-emphasize debuggers in run list This PR supports hiding debuggers in VS Code similar to the when clause. If this field is set to true, it will hide the debuggers in the list. --- .../contrib/debug/browser/debugAdapterManager.ts | 1 + src/vs/workbench/contrib/debug/common/debug.ts | 1 + .../workbench/contrib/debug/common/debugSchemas.ts | 5 +++++ src/vs/workbench/contrib/debug/common/debugger.ts | 13 +++++++++++++ 4 files changed, 20 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 1084c3707d6..55314e58a47 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -364,6 +364,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { this.initExtensionActivationsIfNeeded(); candidates.sort((first, second) => first.label.localeCompare(second.label)); + candidates = candidates.filter(a => !a.isHiddenFromDropdown); const suggestedCandidates: Debugger[] = []; const otherCandidates: Debugger[] = []; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 3620976fcbc..8dda275ca10 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -839,6 +839,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut configurationSnippets?: IJSONSchemaSnippet[]; variables?: { [key: string]: string }; when?: string; + whenDeemphasize?: string; deprecated?: string; strings?: { [key in DebuggerString]: string }; } diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 7363af0723c..d5d045054bb 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -72,6 +72,11 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE type: 'string', default: '' }, + whenDeemphasize: { + description: nls.localize('vscode.extension.contributes.debuggers.whenDeemphasized', "Condition which must be true to hide this type of debugger. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension-defined context key as appropriate for this."), + type: 'string', + default: '' + }, deprecated: { description: nls.localize('vscode.extension.contributes.debuggers.deprecated', "Optional message to mark this debug type as being deprecated."), type: 'string', diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 1f8faf2fae0..7d137df185c 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -29,6 +29,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { private mainExtensionDescription: IExtensionDescription | undefined; private debuggerWhen: ContextKeyExpression | undefined; + private debuggerWhenDeemphasize: ContextKeyExpression | undefined; constructor( private adapterManager: IAdapterManager, @@ -45,6 +46,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { this.merge(dbgContribution, extensionDescription); this.debuggerWhen = typeof this.debuggerContribution.when === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.when) : undefined; + this.debuggerWhenDeemphasize = typeof this.debuggerContribution.whenDeemphasize === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.whenDeemphasize) : undefined; } merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void { @@ -147,10 +149,21 @@ export class Debugger implements IDebugger, IDebuggerMetadata { return this.debuggerWhen; } + get whenDemphasis(): ContextKeyExpression | undefined { + return this.debuggerWhenDeemphasize; + } + get enabled() { return !this.debuggerWhen || this.contextKeyService.contextMatchesRules(this.debuggerWhen); } + get isHiddenFromDropdown() { + if (!this.debuggerWhenDeemphasize) { + return false; + } + return this.contextKeyService.contextMatchesRules(this.debuggerWhenDeemphasize); + } + get strings() { return this.debuggerContribution.strings ?? (this.debuggerContribution as any).uiMessages; } From ea27cc09a8ce40585261dcd7622cdd5a838c2bdc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Sep 2023 10:05:34 +0200 Subject: [PATCH 013/133] voice - implement super simple vad --- .../sharedProcess/contrib/voiceTranscriber.ts | 11 +---- .../voiceTranscriptionWorklet.ts | 40 +++++++++++++++++-- .../workbenchVoiceRecognitionService.ts | 5 ++- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts index 11299d0848e..bc5657a7136 100644 --- a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts +++ b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts @@ -69,18 +69,11 @@ class VoiceTranscriber extends Disposable { } private async handleRequest(e: MessageEvent, cancellation: CancellationToken): Promise { - if (!(Array.isArray(e.data))) { + if (!(e.data instanceof Float32Array)) { return; } - const newData: Float32Array[] = []; - for (const channelData of e.data) { - if (channelData instanceof Float32Array) { - newData.push(channelData); - } - } - - const dataCandidate = this.joinFloat32Arrays(this.data ? [this.data, ...newData] : newData); + const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, e.data]) : e.data; if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) { this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); diff --git a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts b/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts index fc3acd9eb6a..bd125dd0262 100644 --- a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts +++ b/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts @@ -13,6 +13,7 @@ declare class AudioWorkletProcessor { interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions { processorOptions: { readonly bufferTimespan: number; + readonly vadThreshold: number; }; } @@ -22,6 +23,7 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { private stopped: boolean = false; private buffer: Float32Array[] = []; + private text = ''; private sharedProcessConnection: MessagePort | undefined = undefined; @@ -43,7 +45,7 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { } if (typeof event.data === 'string') { - this.port.postMessage(event.data); + this.processText(event.data); } }; @@ -63,6 +65,11 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { }; } + private processText(text: string = this.text): void { + this.text = text; + this.port.postMessage(this.text); + } + override process(inputs: [Float32Array[]]): boolean { if (this.startTime === undefined) { this.startTime = Date.now(); @@ -76,16 +83,43 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { this.buffer.push(inputChannelData.slice(0)); if (Date.now() - this.startTime > this.options.processorOptions.bufferTimespan && this.sharedProcessConnection) { - const buffer = this.buffer; + const buffer = this.joinFloat32Arrays(this.buffer); this.buffer = []; - this.sharedProcessConnection.postMessage(buffer); + if (!this.appearsToBeSilence(buffer)) { + this.sharedProcessConnection.postMessage(buffer); + } else { + this.processText(); + } this.startTime = Date.now(); } return !this.stopped; } + + private appearsToBeSilence(data: Float32Array): boolean { + let sum = 0; + for (let i = 0; i < data.length; i++) { + sum += data[i] * data[i]; + } + + const rms = Math.sqrt(sum / data.length); + + return rms < this.options.processorOptions.vadThreshold; + } + + private joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array { + const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0)); + + let offset = 0; + for (const float32Array of float32Arrays) { + result.set(float32Array, offset); + offset += float32Array.length; + } + + return result; + } } // @ts-ignore diff --git a/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts b/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts index 11aa0dae3e1..0f279ac26ea 100644 --- a/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts +++ b/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts @@ -41,6 +41,7 @@ export interface IWorkbenchVoiceRecognitionService { interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions { processorOptions: { readonly bufferTimespan: number; + readonly vadThreshold: number; }; } @@ -93,6 +94,7 @@ export class WorkbenchVoiceRecognitionService implements IWorkbenchVoiceRecognit private static readonly AUDIO_CHANNELS = 1; private static readonly BUFFER_TIMESPAN = 1000; + private static readonly VAD_THRESHOLD = 0.02; constructor( @IProgressService private readonly progressService: IProgressService, @@ -172,7 +174,8 @@ export class WorkbenchVoiceRecognitionService implements IWorkbenchVoiceRecognit channelCount: WorkbenchVoiceRecognitionService.AUDIO_CHANNELS, channelCountMode: 'explicit', processorOptions: { - bufferTimespan: WorkbenchVoiceRecognitionService.BUFFER_TIMESPAN + bufferTimespan: WorkbenchVoiceRecognitionService.BUFFER_TIMESPAN, + vadThreshold: WorkbenchVoiceRecognitionService.VAD_THRESHOLD } }, onDidTranscribe, this.sharedProcessService); await voiceTranscriptionTarget.start(cts.token); From 6d8c8453fb1a7b04e12400ffc57a2e3e09d2beec Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 15 Sep 2023 10:24:30 +0200 Subject: [PATCH 014/133] Add keyboard shortcut for Add Comment (#193118) Part of #192377 --- .../comments/browser/commentsController.ts | 20 ++++++++++++++++++- .../browser/commentsEditorContribution.ts | 16 +++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 636de0344f6..fddb689229c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -240,6 +240,21 @@ class CommentingRangeDecorator { } } + private areRangesIntersectingOrTouchingByLine(a: Range, b: Range) { + // Check if `a` is before `b` + if (a.endLineNumber < b.startLineNumber) { + return false; + } + + // Check if `b` is before `a` + if (b.endLineNumber < a.startLineNumber) { + return false; + } + + // These ranges must intersect + return true; + } + public getMatchedCommentAction(commentRange: Range | undefined): CommentRangeAction[] { if (commentRange === undefined) { const foundInfos = this._infos?.filter(info => info.commentingRanges.fileComments); @@ -260,7 +275,7 @@ class CommentingRangeDecorator { const foundHoverActions = new Map(); for (const decoration of this.commentingRangeDecorations) { const range = decoration.getActiveRange(); - if (range && ((range.startLineNumber <= commentRange.startLineNumber) || (commentRange.endLineNumber <= range.endLineNumber))) { + if (range && this.areRangesIntersectingOrTouchingByLine(range, commentRange)) { // We can have several commenting ranges that match from the same owner because of how // the line hover and selection decoration is done. // The ranges must be merged so that we can see if the new commentRange fits within them. @@ -863,6 +878,9 @@ export class CommentController implements IEditorContribution { const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(range); if (!newCommentInfos.length || !this.editor?.hasModel()) { this._addInProgress = false; + if (!newCommentInfos.length) { + throw new Error('There are no commenting ranges at the current position.'); + } return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 116782e4364..b12521cf40b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -19,6 +19,7 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ActiveCursorHasCommentingRange, CommentController, ID } from 'vs/workbench/contrib/comments/browser/commentsController'; import { IRange, Range } from 'vs/editor/common/core/range'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class NextCommentThreadAction extends EditorAction { constructor() { @@ -87,9 +88,9 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }); const ADD_COMMENT_COMMAND = 'workbench.action.addComment'; -CommandsRegistry.registerCommand({ +KeybindingsRegistry.registerCommandAndKeybindingRule({ id: ADD_COMMENT_COMMAND, - handler: (accessor, args?: { range: IRange; fileComment: boolean }) => { + handler: async (accessor, args?: { range: IRange; fileComment: boolean }) => { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return Promise.resolve(); @@ -102,8 +103,15 @@ CommandsRegistry.registerCommand({ const position = args?.range ? new Range(args.range.startLineNumber, args.range.startLineNumber, args.range.endLineNumber, args.range.endColumn) : (args?.fileComment ? undefined : activeEditor.getSelection()); - return controller.addOrToggleCommentAtLine(position, undefined); - } + const notificationService = accessor.get(INotificationService); + try { + await controller.addOrToggleCommentAtLine(position, undefined); + } catch (e) { + notificationService.error(nls.localize('comments.addCommand.error', "The cursor must be within a commenting range to add a comment")); // TODO: Once we have commands to go to next commenting range they should be included as buttons in the error. + } + }, + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC, }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { From bc6b75eb2c93aa375f71ec75bd6b71159b25772a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 15 Sep 2023 01:32:30 -0700 Subject: [PATCH 015/133] Basic 'chat agent' API (#193152) * Very basic chat agent API/UX * Add custom name/avatar, and restore them in persisted sessions * Show agent subcommands on the top level * Show editor decorations for subcommands * Fix unit tests * Implement unregister * Revert slash command content widget change, still used by inline editor * Remove content widget reference * Fix leaked disposable --- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadChatAgents.ts | 65 +++++ .../workbench/api/common/extHost.api.impl.ts | 7 + .../workbench/api/common/extHost.protocol.ts | 13 + .../workbench/api/common/extHostChatAgents.ts | 91 +++++++ .../contrib/chat/browser/chat.contribution.ts | 2 + .../browser/contrib/chatInputEditorContrib.ts | 252 +++++++++++++++--- .../contrib/chat/common/chatAgents.ts | 209 +++++++++++++++ .../contrib/chat/common/chatModel.ts | 36 ++- .../contrib/chat/common/chatServiceImpl.ts | 40 ++- .../contrib/chat/common/chatViewModel.ts | 4 +- .../chat/test/common/chatModel.test.ts | 6 +- .../chat/test/common/chatService.test.ts | 2 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.chatAgents.d.ts | 41 +++ 15 files changed, 717 insertions(+), 53 deletions(-) create mode 100644 src/vs/workbench/api/browser/mainThreadChatAgents.ts create mode 100644 src/vs/workbench/api/common/extHostChatAgents.ts create mode 100644 src/vs/workbench/contrib/chat/common/chatAgents.ts create mode 100644 src/vscode-dts/vscode.proposed.chatAgents.d.ts diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index abb8b45a8ad..1abc2a81ab3 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -21,6 +21,7 @@ import './mainThreadLocalization'; import './mainThreadBulkEdits'; import './mainThreadChatProvider'; import './mainThreadChatSlashCommands'; +import './mainThreadChatAgents'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents.ts b/src/vs/workbench/api/browser/mainThreadChatAgents.ts new file mode 100644 index 00000000000..52b8106cba8 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadChatAgents.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableMap } from 'vs/base/common/lifecycle'; +import { revive } from 'vs/base/common/marshalling'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatAgentsShape, ExtHostContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + + +@extHostNamedCustomer(MainContext.MainThreadChatAgents) +export class MainThreadChatAgents implements MainThreadChatAgentsShape { + + private readonly _agents = new DisposableMap; + private readonly _pendingProgress = new Map>(); + private readonly _proxy: ExtHostChatAgentsShape; + + constructor( + extHostContext: IExtHostContext, + @IChatAgentService private readonly _chatAgentService: IChatAgentService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents); + } + + $unregisterAgent(handle: number): void { + this._agents.deleteAndDispose(handle); + } + + dispose(): void { + this._agents.clearAndDisposeAll(); + } + + $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void { + if (!this._chatAgentService.hasAgent(name)) { + // dynamic! + this._chatAgentService.registerAgentData({ + id: name, + metadata: revive(metadata) + }); + } + + const d = this._chatAgentService.registerAgentCallback(name, async (prompt, progress, history, token) => { + const requestId = Math.random(); + this._pendingProgress.set(requestId, progress); + try { + return await this._proxy.$invokeAgent(handle, requestId, prompt, { history }, token); + } finally { + this._pendingProgress.delete(requestId); + } + }); + this._agents.set(handle, d); + } + + async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { + this._pendingProgress.get(requestId)?.report(revive(chunk)); + } + + $unregisterCommand(handle: number): void { + this._agents.deleteAndDispose(handle); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7e68b4409db..eaa75c565f9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -108,6 +108,7 @@ import { ExtHostChatSlashCommands } from 'vs/workbench/api/common/extHostChatSla import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; +import { ExtHostChatAgents } from 'vs/workbench/api/common/extHostChatAgents'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -209,6 +210,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); const extHostChatSlashCommands = rpcProtocol.set(ExtHostContext.ExtHostChatSlashCommands, new ExtHostChatSlashCommands(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents = rpcProtocol.set(ExtHostContext.ExtHostChatAgents, new ExtHostChatAgents(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); @@ -1360,7 +1362,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); + }, + registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) { + checkProposedApiEnabled(extension, 'chatAgents'); + return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata); } + }; return { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index eaf7745216b..b6789136034 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -77,6 +77,7 @@ import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } fr import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { RelatedInformationResult, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; +import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; @@ -1154,6 +1155,16 @@ export interface ExtHostChatSlashCommandsShape { $executeCommand(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; } +export interface MainThreadChatAgentsShape extends IDisposable { + $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void; + $unregisterAgent(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; +} + +export interface ExtHostChatAgentsShape { + $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; +} + export interface MainThreadChatVariablesShape extends IDisposable { $registerVariable(handle: number, data: IChatVariableData): void; $unregisterVariable(handle: number): void; @@ -2605,6 +2616,7 @@ export const MainContext = { MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), MainThreadChatSlashCommands: createProxyIdentifier('MainThreadChatSlashCommands'), + MainThreadChatAgents: createProxyIdentifier('MainThreadChatAgents'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), @@ -2725,6 +2737,7 @@ export const ExtHostContext = { ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), ExtHostChatSlashCommands: createProxyIdentifier('ExtHostChatSlashCommands'), + ExtHostChatAgents: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), ExtHostAiRelatedInformation: createProxyIdentifier('ExtHostAiRelatedInformation'), diff --git a/src/vs/workbench/api/common/extHostChatAgents.ts b/src/vs/workbench/api/common/extHostChatAgents.ts new file mode 100644 index 00000000000..1fc42c5a393 --- /dev/null +++ b/src/vs/workbench/api/common/extHostChatAgents.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatAgentsShape, IMainContext, MainContext, MainThreadChatAgentsShape } 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 { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import type * as vscode from 'vscode'; + +export class ExtHostChatAgents implements ExtHostChatAgentsShape { + + private static _idPool = 0; + + private readonly _agents = new Map(); + private readonly _proxy: MainThreadChatAgentsShape; + + constructor( + mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents); + } + + registerAgent(extension: ExtensionIdentifier, name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata): IDisposable { + const handle = ExtHostChatAgents._idPool++; + this._agents.set(handle, { extension, agent }); + this._proxy.$registerAgent(handle, name, metadata); + + return toDisposable(() => { + this._proxy.$unregisterAgent(handle); + this._agents.delete(handle); + }); + } + + async $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + const data = this._agents.get(handle); + if (!data) { + this._logService.warn(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); + return; + } + + let done = false; + function throwIfDone() { + if (done) { + throw new Error('Only valid while executing the command'); + } + } + + const commandExecution = new DeferredPromise(); + token.onCancellationRequested(() => commandExecution.complete()); + setTimeout(() => commandExecution.complete(), 3 * 1000); + this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p); + + const task = data.agent( + { role: ChatMessageRole.User, content: prompt }, + { history: context.history.map(typeConvert.ChatMessage.to) }, + new Progress(p => { + throwIfDone(); + this._proxy.$handleProgressChunk(requestId, { content: isInteractiveProgressFileTree(p.message) ? p.message : p.message.value }); + }), + token + ); + + try { + return await raceCancellation(Promise.resolve(task).then((v) => { + if (v && 'followUp' in v) { + const convertedFollowup = v?.followUp?.map(f => typeConvert.ChatFollowup.from(f)); + return { followUp: convertedFollowup }; + } + return undefined; + }), token); + } finally { + done = true; + commandExecution.complete(); + } + } +} + +function isInteractiveProgressFileTree(thing: unknown): thing is vscode.InteractiveProgressFileTree { + return !!thing && typeof thing === 'object' && 'treeData' in thing; +} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 9e6ad4404f1..facf315c8c8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -54,6 +54,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ChatVariablesService, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; +import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -253,4 +254,5 @@ registerSingleton(IChatAccessibilityService, ChatAccessibilityService, Instantia registerSingleton(IChatWidgetHistoryService, ChatWidgetHistoryService, InstantiationType.Delayed); registerSingleton(IChatProviderService, ChatProviderService, 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/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index e110d6ad3cc..f9c312fa4ce 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -23,8 +23,8 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { SubmitAction } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; -import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -32,13 +32,12 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; const decorationDescription = 'chat'; -const slashCommandPlaceholderDecorationType = 'chat-session-detail'; +const placeholderDecorationType = 'chat-session-detail'; const slashCommandTextDecorationType = 'chat-session-text'; const variableTextDecorationType = 'chat-variable-text'; class InputEditorDecorations extends Disposable { - private _slashCommandContentWidget: SlashCommandContentWidget | undefined; private _previouslyUsedSlashCommands = new Set(); constructor( @@ -47,10 +46,11 @@ class InputEditorDecorations extends Disposable { @IThemeService private readonly themeService: IThemeService, @IChatService private readonly chatService: IChatService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); - this.codeEditorService.registerDecorationType(decorationDescription, slashCommandPlaceholderDecorationType, {}); + this.codeEditorService.registerDecorationType(decorationDescription, placeholderDecorationType, {}); this._register(this.themeService.onDidColorThemeChange(() => this.updateRegisteredDecorationTypes())); this.updateRegisteredDecorationTypes(); @@ -71,14 +71,12 @@ class InputEditorDecorations extends Disposable { private updateRegisteredDecorationTypes() { this.codeEditorService.removeDecorationType(variableTextDecorationType); this.codeEditorService.removeDecorationType(slashCommandTextDecorationType); - this._slashCommandContentWidget?.hide(); - this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, { - opacity: '0', - after: { - contentText: ' ', - } - }); + const theme = this.themeService.getColorTheme(); + this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, { + color: theme.getColor(chatSlashCommandForeground)?.toString(), + backgroundColor: theme.getColor(chatSlashCommandBackground)?.toString() + }); this.codeEditorService.registerDecorationType(decorationDescription, variableTextDecorationType, { color: theme.getColor(chatSlashCommandForeground)?.toString(), backgroundColor: theme.getColor(chatSlashCommandBackground)?.toString(), @@ -96,11 +94,12 @@ class InputEditorDecorations extends Disposable { private async updateInputEditorDecorations() { const inputValue = this.widget.inputEditor.getValue(); const slashCommands = await this.widget.getSlashCommands(); // TODO this async call can lead to a flicker of the placeholder text when switching editor tabs + const agents = this.chatAgentService.getAgents(); if (!inputValue) { const extensionPlaceholder = this.widget.viewModel?.inputPlaceholder; const defaultPlaceholder = slashCommands?.length ? - localize('interactive.input.placeholderWithCommands', "Ask a question or type '/' for topics") : + localize('interactive.input.placeholderWithCommands', "Ask a question or type '@' or '/'") : localize('interactive.input.placeholderNoCommands', "Ask a question"); const placeholder = extensionPlaceholder ?? defaultPlaceholder; const decoration: IDecorationOptions[] = [ @@ -119,18 +118,57 @@ class InputEditorDecorations extends Disposable { } } ]; - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, decoration); - this._slashCommandContentWidget?.hide(); + this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, decoration); return; } - let slashCommandPlaceholderDecoration: IDecorationOptions[] | undefined; - const command = inputValue && slashCommands?.find(c => inputValue.startsWith(`/${c.command} `)); + // TODO@roblourens need some kind of parser for queries + + let placeholderDecoration: IDecorationOptions[] | undefined; + const usedAgent = inputValue && agents.find(a => inputValue.startsWith(`@${a.id} `)); + + let usedSubcommand: string | undefined; + let subCommandPosition: number | undefined; + if (usedAgent) { + const subCommandReg = /\/(\w+)(\s|$)/g; + let subCommandMatch: RegExpExecArray | null; + while (subCommandMatch = subCommandReg.exec(inputValue)) { + const maybeCommand = subCommandMatch[1]; + usedSubcommand = usedAgent.metadata.subCommands.find(agentCommand => maybeCommand === agentCommand.name)?.name; + if (usedSubcommand) { + subCommandPosition = subCommandMatch.index; + break; + } + } + } + + if (usedAgent && inputValue === `@${usedAgent.id} `) { + // Agent reference with no other text - show the placeholder + if (usedAgent.metadata.description) { + placeholderDecoration = [{ + range: { + startLineNumber: 1, + endLineNumber: 1, + startColumn: usedAgent.id.length, + endColumn: 1000 + }, + renderOptions: { + after: { + contentText: usedAgent.metadata.description, + color: this.getPlaceholderColor(), + } + } + }]; + } + } + + const command = !usedAgent && inputValue && slashCommands?.find(c => inputValue.startsWith(`/${c.command} `)); if (command && inputValue === `/${command.command} `) { + // Command reference with no other text - show the placeholder const isFollowupSlashCommand = this._previouslyUsedSlashCommands.has(command.command); const shouldRenderFollowupPlaceholder = command.followupPlaceholder && isFollowupSlashCommand; if (shouldRenderFollowupPlaceholder || command.detail) { - slashCommandPlaceholderDecoration = [{ + placeholderDecoration = [{ range: { startLineNumber: 1, endLineNumber: 1, @@ -144,26 +182,40 @@ class InputEditorDecorations extends Disposable { } } }]; - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, slashCommandPlaceholderDecoration); } } - if (!slashCommandPlaceholderDecoration) { - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, []); - } - if (command && inputValue.startsWith(`/${command.command} `)) { - if (!this._slashCommandContentWidget) { - this._slashCommandContentWidget = new SlashCommandContentWidget(this.widget.inputEditor); - this._store.add(this._slashCommandContentWidget); + this.widget.inputEditor.setDecorationsByType(decorationDescription, placeholderDecorationType, placeholderDecoration ?? []); + + // TODO@roblourens The way these numbers are computed aren't totally correct... + const textDecorations: IDecorationOptions[] | undefined = []; + if (usedAgent) { + textDecorations.push( + { + range: { + startLineNumber: 1, + endLineNumber: 1, + startColumn: 1, + endColumn: usedAgent.id.length + 2 + } + } + ); + if (usedSubcommand) { + textDecorations.push( + { + range: { + startLineNumber: 1, + endLineNumber: 1, + startColumn: subCommandPosition! + 1, + endColumn: subCommandPosition! + usedSubcommand.length + 2 + } + } + ); } - this._slashCommandContentWidget.setCommandText(command.command); - this._slashCommandContentWidget.show(); - } else { - this._slashCommandContentWidget?.hide(); } - if (command && command.detail) { - const textDecoration: IDecorationOptions[] = [ + if (command) { + textDecorations.push( { range: { startLineNumber: 1, @@ -172,12 +224,11 @@ class InputEditorDecorations extends Disposable { endColumn: command.command.length + 2 } } - ]; - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, textDecoration); - } else { - this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, []); + ); } + this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, textDecorations); + const variables = this.chatVariablesService.getVariables(); const variableReg = /(^|\s)@(\w+)(:\d+)?(?=(\s|$))/ig; let match: RegExpMatchArray | null; @@ -235,6 +286,7 @@ class SlashCommandCompletions extends Disposable { constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -247,6 +299,15 @@ class SlashCommandCompletions extends Disposable { return null; } + const firstLine = model.getLineContent(1).trim(); + + const agents = this.chatAgentService.getAgents(); + const usedAgent = firstLine.startsWith('@') && agents.find(a => firstLine.startsWith(`@${a.id}`)); + if (usedAgent) { + // No (classic) global slash commands when an agent is used + return; + } + if (model.getValueInRange(new Range(1, 1, 1, 2)) !== '/' && model.getValueLength() > 0) { return null; } @@ -275,6 +336,125 @@ class SlashCommandCompletions extends Disposable { } } +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SlashCommandCompletions, LifecyclePhase.Eventually); + +class AgentCompletions extends Disposable { + constructor( + @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IChatAgentService private readonly chatAgentService: IChatAgentService + ) { + super(); + + this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + _debugDisplayName: 'chatAgent', + triggerCharacters: ['@'], + provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => { + const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + return null; + } + + if (model.getValueInRange(new Range(1, 1, 1, 2)) !== '@' && model.getValueLength() > 0) { + return null; + } + + const agents = this.chatAgentService.getAgents(); + return { + suggestions: agents.map((c, i) => { + const withAt = `@${c.id}`; + return { + label: withAt, + insertText: `${withAt} `, + detail: c.metadata.description, + range: new Range(1, 1, 1, 1), + // sortText: 'a'.repeat(i + 1), + kind: CompletionItemKind.Text, // The icons are disabled here anyway + // command: c.executeImmediately ? { id: SubmitAction.ID, title: withAt, arguments: [{ widget, inputValue: `${withAt} ` }] } : undefined, + }; + }) + }; + } + })); + + this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + _debugDisplayName: 'chatAgentSubcommand', + triggerCharacters: ['/'], + provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { + const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + return; + } + + const firstLine = model.getLineContent(1).trim(); + + if (!firstLine.startsWith('@')) { + return; + } + + const agents = this.chatAgentService.getAgents(); + const usedAgent = agents.find(a => firstLine.startsWith(`@${a.id}`)); + if (!usedAgent) { + return; + } + + const maybeCommands = model.getValue().split(/\s+/).filter(w => w.startsWith('/')); + const usedSubcommand = usedAgent.metadata.subCommands.find(agentCommand => maybeCommands.some(c => c === `/${agentCommand.name}`)); + if (usedSubcommand) { + // Only one allowed + return; + } + + return { + suggestions: usedAgent.metadata.subCommands.map((c, i) => { + const withSlash = `/${c.name}`; + return { + label: withSlash, + insertText: `${withSlash} `, + detail: c.description, + range: new Range(1, position.column - 1, 1, position.column - 1), + // sortText: 'a'.repeat(i + 1), + kind: CompletionItemKind.Text, // The icons are disabled here anyway + // command: c.executeImmediately ? { id: SubmitAction.ID, title: withAt, arguments: [{ widget, inputValue: `${withAt} ` }] } : undefined, + }; + }) + }; + } + })); + + // list subcommands when the query is empty, insert agent+subcommand + this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { + _debugDisplayName: 'chatAgentAndSubcommand', + triggerCharacters: ['/'], + provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { + const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); + if (!widget) { + return; + } + + if (model.getValue().trim() !== '/') { + // Only when the input only contains a slash + return; + } + + const agents = this.chatAgentService.getAgents(); + return { + suggestions: agents.flatMap(a => a.metadata.subCommands.map((c, i) => { + const withSlash = `/${c.name}`; + return { + label: withSlash, + insertText: `@${a.id} ${withSlash} `, + detail: `(@${a.id}) ${c.description}`, + range: new Range(1, 1, 1, 1), + kind: CompletionItemKind.Text, // The icons are disabled here anyway + }; + })) + }; + } + })); + } +} + interface SlashCommandYieldTo { command: string; } @@ -348,7 +528,7 @@ function sortSlashCommandsByYieldTo(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SlashCommandCompletions, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually); class VariableCompletions extends Disposable { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts new file mode 100644 index 00000000000..e7e4c4cfa83 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Event, Emitter } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Disposable, DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatFollowup, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +//#region extension point + +const agentItem: IJSONSchema = { + type: 'object', + required: ['agent', 'detail'], + properties: { + agent: { + type: 'string', + markdownDescription: localize('agent', "The name of the agent which will be used as prefix.") + }, + detail: { + type: 'string', + markdownDescription: localize('details', "The details of the agent.") + }, + } +}; + +const agentItems: IJSONSchema = { + description: localize('vscode.extension.contributes.slashes', "Contributes agents to chat"), + oneOf: [ + agentItem, + { + type: 'array', + items: agentItem + } + ] +}; + +export const agentsExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'agents', + jsonSchema: agentItems +}); + +//#region agent service, commands etc + +export interface IChatAgentData { + id: string; + metadata: IChatAgentMetadata; +} + +function isAgentData(data: any): data is IChatAgentData { + return typeof data === 'object' && data && + typeof data.id === 'string' && + typeof data.detail === 'string'; + // (typeof data.sortText === 'undefined' || typeof data.sortText === 'string') && + // (typeof data.executeImmediately === 'undefined' || typeof data.executeImmediately === 'boolean'); +} + +export interface IChatAgentFragment { + content: string | { treeData: IChatResponseProgressFileTreeData }; +} + +export interface IChatAgentCommand { + name: string; + description: string; +} + +export interface IChatAgentMetadata { + description: string; + subCommands: IChatAgentCommand[]; + requireCommand?: boolean; // Do some agents not have a default action? + isImplicit?: boolean; // Only @workspace. slash commands get promoted to the top-level and this agent is invoked when those are used + fullName?: string; + icon?: URI; +} + +export type IChatAgentCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; + +export const IChatAgentService = createDecorator('chatAgentService'); + +export interface IChatAgentService { + _serviceBrand: undefined; + readonly onDidChangeAgents: Event; + registerAgentData(data: IChatAgentData): IDisposable; + registerAgentCallback(id: string, callback: IChatAgentCallback): IDisposable; + registerAgent(data: IChatAgentData, callback: IChatAgentCallback): IDisposable; + invokeAgent(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; + getAgents(): Array; + hasAgent(id: string): boolean; +} + +type Tuple = { data: IChatAgentData; callback?: IChatAgentCallback }; + +export class ChatAgentService extends Disposable implements IChatAgentService { + + public static readonly AGENT_LEADER = '@'; + + declare _serviceBrand: undefined; + + private readonly _agents = new Map(); + + private readonly _onDidChangeAgents = this._register(new Emitter()); + readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + + constructor(@IExtensionService private readonly _extensionService: IExtensionService) { + super(); + } + + override dispose(): void { + super.dispose(); + this._agents.clear(); + } + + registerAgentData(data: IChatAgentData): IDisposable { + if (this._agents.has(data.id)) { + throw new Error(`Already registered an agent with id ${data.id}}`); + } + this._agents.set(data.id, { data }); + this._onDidChangeAgents.fire(); + + return toDisposable(() => { + if (this._agents.delete(data.id)) { + this._onDidChangeAgents.fire(); + } + }); + } + + registerAgentCallback(id: string, agentCallback: IChatAgentCallback): IDisposable { + const data = this._agents.get(id); + if (!data) { + throw new Error(`No agent with id ${id} registered`); + } + data.callback = agentCallback; + return toDisposable(() => data.callback = undefined); + } + + registerAgent(data: IChatAgentData, callback: IChatAgentCallback): IDisposable { + return combinedDisposable( + this.registerAgentData(data), + this.registerAgentCallback(data.id, callback) + ); + } + + getAgents(): Array { + return Array.from(this._agents.values(), v => v.data); + } + + hasAgent(id: string): boolean { + return this._agents.has(id); + } + + async invokeAgent(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { + const data = this._agents.get(id); + if (!data) { + throw new Error('No agent with id ${id} NOT registered'); + } + if (!data.callback) { + await this._extensionService.activateByEvent(`onChatAgent:${id}`); + } + if (!data.callback) { + throw new Error(`No agent with id ${id} NOT resolved`); + } + + return await data.callback(prompt, progress, history, token); + } +} + +class ChatAgentContribution implements IWorkbenchContribution { + constructor(@IChatAgentService chatAgentService: IChatAgentService) { + const contributions = new DisposableStore(); + + agentsExtPoint.setHandler(extensions => { + contributions.clear(); + + for (const entry of extensions) { + if (!isProposedApiEnabled(entry.description, 'chatAgents')) { + entry.collector.error(`The ${agentsExtPoint.name} is proposed API`); + continue; + } + + const { value } = entry; + + for (const candidate of Iterable.wrap(value)) { + + if (!isAgentData(candidate)) { + entry.collector.error(localize('invalid', "Invalid {0}: {1}", agentsExtPoint.name, JSON.stringify(candidate))); + continue; + } + + contributions.add(chatAgentService.registerAgentData({ ...candidate })); + } + } + }); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ChatAgentContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 5f51abedb9a..26c82c87dd2 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -10,6 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { ILogService } from 'vs/platform/log/common/log'; +import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatRequestModel { @@ -232,16 +233,17 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel } public get username(): string { - return this.session.responderUsername; + return this.agent?.metadata.fullName ?? this.session.responderUsername; } public get avatarIconUri(): URI | undefined { - return this.session.responderAvatarIconUri; + return this.agent?.metadata.icon ?? this.session.responderAvatarIconUri; } constructor( _response: IMarkdownString | (IMarkdownString | IChatResponseProgressFileTreeData)[], public readonly session: ChatModel, + public readonly agent: IChatAgentData | undefined, private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, @@ -309,10 +311,18 @@ export interface ISerializableChatsData { [sessionId: string]: ISerializableChatData; } +export interface ISerializableChatAgentData { + id: string; + description: string; + fullName?: string; + icon?: UriComponents; +} + export interface ISerializableChatRequestData { providerRequestId: string | undefined; message: string; response: (IMarkdownString | IChatResponseProgressFileTreeData)[] | undefined; + agent?: ISerializableChatAgentData; responseErrorDetails: IChatResponseErrorDetails | undefined; followups: IChatFollowup[] | undefined; isCanceled: boolean | undefined; @@ -455,7 +465,8 @@ export class ChatModel extends Disposable implements IChatModel { constructor( public readonly providerId: string, private readonly initialData: ISerializableChatData | IExportableChatData | undefined, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -484,7 +495,8 @@ export class ChatModel extends Disposable implements IChatModel { return requests.map((raw: ISerializableChatRequestData) => { const request = new ChatRequestModel(this, raw.message, raw.providerRequestId); if (raw.response || raw.responseErrorDetails) { - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); + const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); } return request; }); @@ -531,13 +543,13 @@ export class ChatModel extends Disposable implements IChatModel { return this._requests; } - addRequest(message: string | IChatReplyFollowup): ChatRequestModel { + addRequest(message: string | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel { if (!this._session) { throw new Error('addRequest: No session'); } const request = new ChatRequestModel(this, message); - request.response = new ChatResponseModel(new MarkdownString(''), this); + request.response = new ChatResponseModel(new MarkdownString(''), this, chatAgent); this._requests.push(request); this._onDidChange.fire({ kind: 'addRequest', request }); @@ -550,7 +562,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel(new MarkdownString(''), this); + request.response = new ChatResponseModel(new MarkdownString(''), this, undefined); } if (request.response.isComplete) { @@ -593,7 +605,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel(new MarkdownString(''), this); + request.response = new ChatResponseModel(new MarkdownString(''), this, undefined); } request.response.setErrorDetails(rawResponse.errorDetails); @@ -642,7 +654,13 @@ export class ChatModel extends Disposable implements IChatModel { responseErrorDetails: r.response?.errorDetails, followups: r.response?.followups, isCanceled: r.response?.isCanceled, - vote: r.response?.vote + vote: r.response?.vote, + agent: r.response?.agent ? { + id: r.response.agent.id, + description: r.response.agent.metadata.description, + fullName: r.response.agent.metadata.fullName, + icon: r.response.agent.metadata.icon + } : undefined, }; }), providerId: this.providerId, diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 361d4d08407..54dc2b94444 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -21,6 +21,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 { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; @@ -152,7 +153,8 @@ export class ChatService extends Disposable implements IChatService { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, - @IChatVariablesService private readonly chatVariablesService: IChatVariablesService + @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @IChatAgentService private readonly chatAgentService: IChatAgentService ) { super(); @@ -433,10 +435,12 @@ export class ChatService extends Disposable implements IChatService { } private async _sendRequestAsync(model: ChatModel, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise { - const request = model.addRequest(message); + const resolvedAgent = typeof message === 'string' ? this.resolveAgent(message) : undefined; + const request = model.addRequest(message, resolvedAgent); const resolvedCommand = typeof message === 'string' && message.startsWith('/') ? await this.handleSlashCommand(model.sessionId, message) : message; + let gotProgress = false; const requestType = typeof message === 'string' ? (message.startsWith('/') ? 'slashCommand' : 'string') : @@ -487,7 +491,25 @@ export class ChatService extends Disposable implements IChatService { let rawResponse: IChatResponse | null | undefined; let slashCommandFollowups: IChatFollowup[] | void = []; - if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) { + if (typeof message === 'string' && resolvedAgent) { + const history: IChatMessage[] = []; + for (const request of model.getRequests()) { + if (typeof request.message !== 'string' || !request.response) { + continue; + } + if (isMarkdownString(request.response.response.value)) { + history.push({ role: ChatMessageRole.User, content: request.message }); + history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value }); + } + } + const agentResult = await this.chatAgentService.invokeAgent(resolvedAgent.id, message.substring(resolvedAgent.id.length + 1).trimStart(), new Progress(p => { + const { content } = p; + const data = isCompleteInteractiveProgressTreeData(content) ? content : { content }; + progressCallback(data); + }), history, token); + slashCommandFollowups = agentResult?.followUp; + rawResponse = { session: model.session! }; + } else if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) { // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -598,6 +620,16 @@ export class ChatService extends Disposable implements IChatService { return command; } + private resolveAgent(prompt: string): IChatAgentData | undefined { + prompt = prompt.trim(); + const agents = this.chatAgentService.getAgents(); + if (!prompt.startsWith('@')) { + return; + } + + return agents.find(a => prompt.match(new RegExp(`@${a.id}($|\\s)`))); + } + async getSlashCommands(sessionId: string, token: CancellationToken): Promise { const model = this._sessionModels.get(sessionId); if (!model) { @@ -681,7 +713,7 @@ export class ChatService extends Disposable implements IChatService { } await model.waitForInitialization(); - const request = model.addRequest(message); + const request = model.addRequest(message, undefined); if (typeof response.message === 'string') { model.acceptResponseProgress(request, { content: response.message }); } else { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index fadb4edc028..7e891ea7be6 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -10,8 +10,8 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatRequestModel, IChatResponseModel, IChatModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatResponseErrorDetails, IChatReplyFollowup, IChatResponseCommandFollowup, InteractiveSessionVoteDirection, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; export function isRequestVM(item: unknown): item is IChatRequestViewModel { 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 8cb6844d4f2..3a6bbd8843f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -9,6 +9,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -23,10 +24,11 @@ suite('ChatModel', () => { instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); }); test('Waits for initialization', async () => { - const model = testDisposables.add(new ChatModel('provider', undefined, new NullLogService())); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); let hasInitialized = false; model.waitForInitialization().then(() => { @@ -42,7 +44,7 @@ suite('ChatModel', () => { }); test('Initialization fails when model is disposed', async () => { - const model = testDisposables.add(new ChatModel('provider', undefined, new NullLogService())); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); model.dispose(); await assert.rejects(() => model.waitForInitialization()); 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 de602fdfa98..b3ccd1a70a6 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -20,6 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsService } from 'vs/workbench/common/views'; +import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChat, IChatProgress, IChatProvider, IChatRequest, IChatResponse, IPersistedChatState, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; @@ -81,6 +82,7 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); + instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); }); test('retrieveSession', async () => { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ad2039a68bf..c3cd4a04db7 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,6 +11,7 @@ export const allApiProposals = Object.freeze({ 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', + chatAgents: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents.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', chatSlashCommands: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSlashCommands.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatAgents.d.ts b/src/vscode-dts/vscode.proposed.chatAgents.d.ts new file mode 100644 index 00000000000..e668dbbdd2c --- /dev/null +++ b/src/vscode-dts/vscode.proposed.chatAgents.d.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. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface ChatAgentContext { + history: ChatMessage[]; + } + + export interface ChatAgentResponse { + message: MarkdownString | InteractiveProgressFileTree; + } + + export interface ChatAgentResult { + followUp?: InteractiveSessionFollowup[]; + } + + export interface ChatAgentCommand { + name: string; + description: string; + } + + export interface ChatAgentMetadata { + description: string; + fullName?: string; + icon?: Uri; + subCommands: ChatAgentCommand[]; + requireCommand?: boolean; // Do some agents not have a default action? + isImplicit?: boolean; // Only @workspace. slash commands get promoted to the top-level and this agent is invoked when those are used + } + + export interface ChatAgent { + (prompt: ChatMessage, context: ChatAgentContext, progress: Progress, token: CancellationToken): Thenable; + } + + export namespace chat { + export function registerAgent(id: string, agent: ChatAgent, metadata: ChatAgentMetadata): Disposable; + } +} From 17c535565298adcabfdee866d7914955e8c63d2b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Sep 2023 11:14:35 +0200 Subject: [PATCH 016/133] voice - still handle silence on the transcriber --- .../sharedProcess/contrib/voiceTranscriber.ts | 18 +++++++++++-- .../voiceTranscriptionWorklet.ts | 25 ++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts index bc5657a7136..c9cd0b411eb 100644 --- a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts +++ b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts @@ -38,6 +38,9 @@ class VoiceTranscriber extends Disposable { private data: Float32Array | undefined = undefined; + private transcribedDataLength = 0; + private transcribedResult = ''; + constructor( private readonly port: MessagePortMain, private readonly voiceRecognitionService: IVoiceRecognitionService, @@ -74,7 +77,6 @@ class VoiceTranscriber extends Disposable { } const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, e.data]) : e.data; - if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) { this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); return; @@ -95,7 +97,19 @@ class VoiceTranscriber extends Disposable { return; } - const result = await this.voiceRecognitionService.transcribe(data, cancellation); + let result: string; + if (data.length === this.transcribedDataLength) { + // Optimization: if the data is the same as the last time + // we transcribed, don't transcribe again, just return the + // same result as we had last time. + this.logService.info(`[voice] transcriber: silence detected, reusing previous transcription result`); + result = this.transcribedResult; + } else { + result = await this.voiceRecognitionService.transcribe(data, cancellation); + } + + this.transcribedResult = result; + this.transcribedDataLength = data.length; if (cancellation.isCancellationRequested) { return; diff --git a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts b/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts index bd125dd0262..cb0083c81eb 100644 --- a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts +++ b/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts @@ -23,7 +23,6 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { private stopped: boolean = false; private buffer: Float32Array[] = []; - private text = ''; private sharedProcessConnection: MessagePort | undefined = undefined; @@ -45,7 +44,7 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { } if (typeof event.data === 'string') { - this.processText(event.data); + this.port.postMessage(event.data); } }; @@ -65,11 +64,6 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { }; } - private processText(text: string = this.text): void { - this.text = text; - this.port.postMessage(this.text); - } - override process(inputs: [Float32Array[]]): boolean { if (this.startTime === undefined) { this.startTime = Date.now(); @@ -86,11 +80,12 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { const buffer = this.joinFloat32Arrays(this.buffer); this.buffer = []; - if (!this.appearsToBeSilence(buffer)) { - this.sharedProcessConnection.postMessage(buffer); - } else { - this.processText(); - } + // Send buffer to shared process for transcription. + // Send an empty buffer if it appears to be silence + // so that we can still trigger the transcription + // service and let it know about this. + + this.sharedProcessConnection.postMessage(this.appearsToBeSilence(buffer) ? new Float32Array(0) : buffer); this.startTime = Date.now(); } @@ -99,6 +94,12 @@ class VoiceTranscriptionWorklet extends AudioWorkletProcessor { } private appearsToBeSilence(data: Float32Array): boolean { + + // This is the most simple Voice Activity Detection (VAD) + // and it is based on the Root Mean Square (RMS) of the signal + // with a certain threshold. Good for testing but probably + // not suitable for shipping to stable (TODO@bpasero). + let sum = 0; for (let i = 0; i < data.length; i++) { sum += data[i] * data[i]; From e8ea1ab5842c1cff60486fe1a73bbf03189bed55 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Sep 2023 11:32:17 +0200 Subject: [PATCH 017/133] voice - drop the need for `suppressNonSpeechTokens` --- .../platform/voiceRecognition/node/voiceRecognitionService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts b/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts index a63b58f6224..9213c0798b5 100644 --- a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts +++ b/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts @@ -55,7 +55,6 @@ export class VoiceRecognitionService implements IVoiceRecognitionService { audioBuffer: { channelCount: 1; samplingRate: 16000; bitDepth: 16; channelData: Float32Array }, options: { language: string | 'auto'; - suppressNonSpeechTokens: boolean; signal: AbortSignal; } ) => Promise; @@ -71,7 +70,6 @@ export class VoiceRecognitionService implements IVoiceRecognitionService { channelData }, { language: 'en', - suppressNonSpeechTokens: true, signal: abortController.signal }); From ec45a64ce24e956211c82d0f098c3295693d202a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 15 Sep 2023 11:49:52 +0200 Subject: [PATCH 018/133] Aria status when editor has commenting ranges (#193187) Part of #192377 --- .../comments/browser/commentsController.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index fddb689229c..d39fca2b63a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -38,6 +38,7 @@ import { Position } from 'vs/editor/common/core/position'; import { CommentThreadRangeDecorator } from 'vs/workbench/contrib/comments/browser/commentThreadRangeDecorator'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; +import { status } from 'vs/base/browser/ui/aria/aria'; export const ID = 'editor.contrib.review'; @@ -330,6 +331,7 @@ export class CommentController implements IEditorContribution { private _pendingEditsCache: { [key: string]: { [key: string]: { [key: number]: string } } }; // owner -> threadId -> uniqueIdInThread -> pending comment private _editorDisposables: IDisposable[] = []; private _activeCursorHasCommentingRange: IContextKey; + private _hasProvidedAriaStatus: boolean = false; constructor( editor: ICodeEditor, @@ -377,8 +379,8 @@ export class CommentController implements IEditorContribution { } this.beginCompute(); })); - this.globalToDispose.add(this.commentService.onDidSetDataProvider(_ => this.beginCompute())); - this.globalToDispose.add(this.commentService.onDidUpdateCommentingRanges(_ => this.beginCompute())); + this.globalToDispose.add(this.commentService.onDidSetDataProvider(_ => this.beginComputeAndProvideStatus())); + this.globalToDispose.add(this.commentService.onDidUpdateCommentingRanges(_ => this.beginComputeAndProvideStatus())); this.globalToDispose.add(this.commentService.onDidSetResourceCommentInfos(e => { const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; @@ -686,6 +688,8 @@ export class CommentController implements IEditorContribution { return; } + this._hasProvidedAriaStatus = false; + this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); if (this._editorDisposables.length) { @@ -775,7 +779,16 @@ export class CommentController implements IEditorContribution { this._commentThreadRangeDecorator.update(this.editor, commentInfo); })); - this.beginCompute(); + this.beginComputeAndProvideStatus(); + } + + private beginComputeAndProvideStatus(): void { + this.beginCompute().then(() => { + if (!this._hasProvidedAriaStatus && this._commentInfos.some(commentInfo => commentInfo.commentingRanges.ranges.length > 0 || commentInfo.commentingRanges.fileComments)) { + this._hasProvidedAriaStatus = true; + status(nls.localize('hasCommentRanges', "Editor has commenting ranges.")); + } + }); } private async openCommentsView(thread: languages.CommentThread) { From f0a61fd8f6238bddddf055df332a4a6dbd67fa53 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Sep 2023 12:40:07 +0200 Subject: [PATCH 019/133] voice - add a basic sliding window transcription approach based on silence detection --- .../sharedProcess/contrib/voiceTranscriber.ts | 125 +++++++++++++----- .../node/voiceRecognitionService.ts | 8 +- 2 files changed, 95 insertions(+), 38 deletions(-) diff --git a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts index c9cd0b411eb..23a790e6109 100644 --- a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts +++ b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts @@ -9,10 +9,12 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IVoiceRecognitionService } from 'vs/platform/voiceRecognition/node/voiceRecognitionService'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { LimitedQueue } from 'vs/base/common/async'; +import { LimitedQueue, Queue } from 'vs/base/common/async'; export class VoiceTranscriptionManager extends Disposable { + private static USE_SLIDING_WINDOW = !!process.env.VSCODE_VOICE_USE_SLIDING_WINDOW; + constructor( private readonly onDidWindowConnectRaw: Event, @IVoiceRecognitionService private readonly voiceRecognitionService: IVoiceRecognitionService, @@ -25,26 +27,25 @@ export class VoiceTranscriptionManager extends Disposable { private registerListeners(): void { this._register(this.onDidWindowConnectRaw(port => { - this._register(new VoiceTranscriber(port, this.voiceRecognitionService, this.logService)); + this.logService.info(`[voice] transcriber: new connection (sliding window: ${VoiceTranscriptionManager.USE_SLIDING_WINDOW})`); + + if (VoiceTranscriptionManager.USE_SLIDING_WINDOW) { + this._register(new SlidingWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService)); + } else { + this._register(new FullWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService)); + } })); } } -class VoiceTranscriber extends Disposable { +abstract class VoiceTranscriber extends Disposable { - private static MAX_DATA_LENGTH = 30 /* seconds */ * 16000 /* sampling rate */ * 16 /* bith depth */ * 1 /* channels */ / 8; - - private readonly transcriptionQueue = new LimitedQueue(); - - private data: Float32Array | undefined = undefined; - - private transcribedDataLength = 0; - private transcribedResult = ''; + protected static MAX_DATA_LENGTH = 30 /* seconds */ * 16000 /* sampling rate */ * 16 /* bith depth */ * 1 /* channels */ / 8; constructor( - private readonly port: MessagePortMain, - private readonly voiceRecognitionService: IVoiceRecognitionService, - private readonly logService: ILogService + protected readonly port: MessagePortMain, + protected readonly voiceRecognitionService: IVoiceRecognitionService, + protected readonly logService: ILogService ) { super(); @@ -52,12 +53,16 @@ class VoiceTranscriber extends Disposable { } private registerListeners(): void { - this.logService.info(`[voice] transcriber: new connection`); - const cts = new CancellationTokenSource(); this._register(toDisposable(() => cts.dispose(true))); - const requestHandler = (e: MessageEvent) => this.handleRequest(e, cts.token); + const requestHandler = (e: MessageEvent) => { + if (!(e.data instanceof Float32Array)) { + return; + } + + this.handleRequest(e.data, cts.token); + }; this.port.on('message', requestHandler); this._register(toDisposable(() => this.port.off('message', requestHandler))); @@ -71,12 +76,79 @@ class VoiceTranscriber extends Disposable { }); } - private async handleRequest(e: MessageEvent, cancellation: CancellationToken): Promise { - if (!(e.data instanceof Float32Array)) { + protected abstract handleRequest(data: Float32Array, cancellation: CancellationToken): Promise; + + protected joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array { + const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0)); + + let offset = 0; + for (const float32Array of float32Arrays) { + result.set(float32Array, offset); + offset += float32Array.length; + } + + return result; + } +} + +class SlidingWindowVoiceTranscriber extends VoiceTranscriber { + + private readonly transcriptionQueue = new Queue(); + + private transcribedResults: string[] = []; + private data: Float32Array = new Float32Array(0); + + protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise { + if (data.length > 0) { + this.logService.info(`[voice] transcriber: voice detected, storing in buffer`); + + this.data = this.data ? this.joinFloat32Arrays([this.data, data]) : data; + } else if (this.data) { + this.logService.info(`[voice] transcriber: silence detected, transcribing window...`); + + const data = this.data.slice(0); + this.data = new Float32Array(0); + + this.transcriptionQueue.queue(() => this.transcribe(data, cancellation)); + } + } + + private async transcribe(data: Float32Array, cancellation: CancellationToken): Promise { + if (cancellation.isCancellationRequested) { return; } - const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, e.data]) : e.data; + if (data.length > VoiceTranscriber.MAX_DATA_LENGTH) { + this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); + return; + } + + if (data.length !== 0) { + const result = await this.voiceRecognitionService.transcribe(data, cancellation); + if (result) { + this.transcribedResults.push(result); + } + } + + if (cancellation.isCancellationRequested) { + return; + } + + this.port.postMessage(this.transcribedResults.join(' ')); + } +} + +class FullWindowVoiceTranscriber extends VoiceTranscriber { + + private readonly transcriptionQueue = new LimitedQueue(); + + private data: Float32Array | undefined = undefined; + + private transcribedDataLength = 0; + private transcribedResult = ''; + + protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise { + const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, data]) : data; if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) { this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); return; @@ -105,6 +177,7 @@ class VoiceTranscriber extends Disposable { this.logService.info(`[voice] transcriber: silence detected, reusing previous transcription result`); result = this.transcribedResult; } else { + this.logService.info(`[voice] transcriber: voice detected, transcribing everything...`); result = await this.voiceRecognitionService.transcribe(data, cancellation); } @@ -117,16 +190,4 @@ class VoiceTranscriber extends Disposable { this.port.postMessage(result); } - - private joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array { - const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0)); - - let offset = 0; - for (const float32Array of float32Arrays) { - result.set(float32Array, offset); - offset += float32Array.length; - } - - return result; - } } diff --git a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts b/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts index 9213c0798b5..03f7a16ca95 100644 --- a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts +++ b/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts @@ -37,8 +37,6 @@ export class VoiceRecognitionService implements IVoiceRecognitionService { ) { } async transcribe(channelData: Float32Array, cancellation: CancellationToken): Promise { - this.logService.info(`[voice] transcribe(${channelData.length}): Begin`); - const modulePath = process.env.VSCODE_VOICE_MODULE_PATH; // TODO@bpasero package if (!modulePath || this.productService.quality === 'stable') { this.logService.error(`[voice] transcribe(${channelData.length}): Voice recognition not yet supported`); @@ -47,8 +45,6 @@ export class VoiceRecognitionService implements IVoiceRecognitionService { const now = Date.now(); - this.logService.info(`[voice] transcribe(${channelData.length}): Getting module from ${modulePath}`); - try { const voiceModule: { transcribe: ( @@ -73,11 +69,11 @@ export class VoiceRecognitionService implements IVoiceRecognitionService { signal: abortController.signal }); - this.logService.info(`[voice] transcribe(${channelData.length}): End (text: "${text}", took: ${Date.now() - now}ms)`); + this.logService.info(`[voice] transcribe(${channelData.length}): Text "${text}", took ${Date.now() - now}ms)`); return text; } catch (error) { - this.logService.error(`[voice] transcribe(${channelData.length}): Failed (error: "${error}", took: ${Date.now() - now}ms)`); + this.logService.error(`[voice] transcribe(${channelData.length}): Failed width "${error}", took ${Date.now() - now}ms)`); throw error; } From 4dad617e422b557f3a3cb62e122e62a8afe16447 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 15 Sep 2023 12:54:06 +0200 Subject: [PATCH 020/133] Finalize `TextEditorOptions.indentSize` API proposal (#193193) Finalize TextEditorOptions.indentSize API proposal --- .../workbench/api/common/extHostTextEditor.ts | 4 +-- .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 11 ++++++- .../vscode.proposed.indentSize.d.ts | 32 ------------------- 4 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.indentSize.d.ts diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index 8ab93b18b21..60d75433f9c 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -165,10 +165,10 @@ export class ExtHostTextEditorOptions { set tabSize(value: number | string) { that._setTabSize(value); }, - get indentSize(): number | 'tabSize' { + get indentSize(): number | string { return that._indentSize; }, - set indentSize(value: number | 'tabSize') { + set indentSize(value: number | string) { that._setIndentSize(value); }, get insertSpaces(): boolean | string { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index c3cd4a04db7..a5e42e1d5d6 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -50,7 +50,6 @@ export const allApiProposals = Object.freeze({ 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', idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', - indentSize: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.indentSize.d.ts', inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', interactive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactive.d.ts', interactiveUserActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index b5c09fe1617..a8ab72cab99 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -668,13 +668,22 @@ declare module 'vscode' { /** * The size in spaces a tab takes. This is used for two purposes: * - the rendering width of a tab character; - * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true. + * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true + * and `indentSize` is set to `"tabSize"`. * * When getting a text editor's options, this property will always be a number (resolved). * When setting a text editor's options, this property is optional and it can be a number or `"auto"`. */ tabSize?: number | string; + /** + * The number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true. + * + * When getting a text editor's options, this property will always be a number (resolved). + * When setting a text editor's options, this property is optional and it can be a number or `"tabSize"`. + */ + indentSize?: number | string; + /** * When pressing Tab insert {@link TextEditorOptions.tabSize n} spaces. * When getting a text editor's options, this property will always be a boolean (resolved). diff --git a/src/vscode-dts/vscode.proposed.indentSize.d.ts b/src/vscode-dts/vscode.proposed.indentSize.d.ts deleted file mode 100644 index 2a490dd2742..00000000000 --- a/src/vscode-dts/vscode.proposed.indentSize.d.ts +++ /dev/null @@ -1,32 +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' { - - // https://github.com/microsoft/vscode/issues/193077 @alexdima - - /** - * Represents a {@link TextEditor text editor}'s {@link TextEditor.options options}. - */ - export interface TextEditorOptions { - /** - * The size in spaces a tab takes. This is used for two purposes: - * - the rendering width of a tab character; - * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true - * and `indentSize` is set to `"tabSize"`. - * - * When getting a text editor's options, this property will always be a number (resolved). - * When setting a text editor's options, this property is optional and it can be a number or `"auto"`. - */ - tabSize?: number | string; - /** - * The number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true. - * - * When getting a text editor's options, this property will always be a number (resolved). - * When setting a text editor's options, this property is optional and it can be a number or `"tabSize"`. - */ - indentSize?: number | 'tabSize'; - } -} From 9a9ef69e1f407743da8caf0c8bc6e4ec535c6f23 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Sep 2023 14:05:57 +0200 Subject: [PATCH 021/133] voice - cleanup disposables (#193199) --- .../sharedProcess/contrib/voiceTranscriber.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts index 23a790e6109..6f29b8d3a23 100644 --- a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts +++ b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts @@ -67,13 +67,20 @@ abstract class VoiceTranscriber extends Disposable { this._register(toDisposable(() => this.port.off('message', requestHandler))); this.port.start(); - this._register(toDisposable(() => this.port.close())); + let closed = false; this.port.on('close', () => { this.logService.info(`[voice] transcriber: closed connection`); - cts.dispose(true); + closed = true; + this.dispose(); }); + + this._register(toDisposable(() => { + if (!closed) { + this.port.close(); + } + })); } protected abstract handleRequest(data: Float32Array, cancellation: CancellationToken): Promise; @@ -93,7 +100,7 @@ abstract class VoiceTranscriber extends Disposable { class SlidingWindowVoiceTranscriber extends VoiceTranscriber { - private readonly transcriptionQueue = new Queue(); + private readonly transcriptionQueue = this._register(new Queue()); private transcribedResults: string[] = []; private data: Float32Array = new Float32Array(0); @@ -103,7 +110,7 @@ class SlidingWindowVoiceTranscriber extends VoiceTranscriber { this.logService.info(`[voice] transcriber: voice detected, storing in buffer`); this.data = this.data ? this.joinFloat32Arrays([this.data, data]) : data; - } else if (this.data) { + } else { this.logService.info(`[voice] transcriber: silence detected, transcribing window...`); const data = this.data.slice(0); @@ -136,6 +143,12 @@ class SlidingWindowVoiceTranscriber extends VoiceTranscriber { this.port.postMessage(this.transcribedResults.join(' ')); } + + override dispose(): void { + super.dispose(); + + this.data = new Float32Array(0); + } } class FullWindowVoiceTranscriber extends VoiceTranscriber { @@ -190,4 +203,10 @@ class FullWindowVoiceTranscriber extends VoiceTranscriber { this.port.postMessage(result); } + + override dispose(): void { + super.dispose(); + + this.data = undefined; + } } From b2388cebc348cc2ccd74a10e0f7f1d60d3bb356d Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 15 Sep 2023 14:55:21 +0200 Subject: [PATCH 022/133] Avoid using global document, global window (#193189) * Avoid using global document, global window * Fix problem with fake dom element * Fix problem with tracking focus on the window --- src/vs/base/browser/dom.ts | 91 ++++++++++++++----- src/vs/base/browser/mouseEvent.ts | 4 +- .../editor/browser/controller/mouseHandler.ts | 2 +- .../editor/browser/controller/mouseTarget.ts | 10 +- .../browser/controller/textAreaHandler.ts | 4 +- .../browser/controller/textAreaInput.ts | 12 ++- src/vs/editor/browser/coreCommands.ts | 23 ++--- src/vs/editor/browser/editorDom.ts | 2 +- src/vs/editor/browser/editorExtensions.ts | 3 +- src/vs/editor/browser/view/viewPart.ts | 2 +- .../contentWidgets/contentWidgets.ts | 16 +++- .../diffEditor/diffEditor.contribution.ts | 6 +- .../contrib/clipboard/browser/clipboard.ts | 11 ++- .../colorPicker/browser/colorPickerWidget.ts | 4 +- .../browser/copyPasteController.ts | 4 +- .../contrib/hover/browser/contentHover.ts | 2 +- .../hover/browser/resizableContentWidget.ts | 2 +- .../message/browser/messageController.ts | 2 +- .../rename/browser/renameInputField.ts | 2 +- .../contrib/suggest/browser/suggestWidget.ts | 2 +- .../suggest/browser/suggestWidgetDetails.ts | 2 +- .../browser/controller/textAreaInput.test.ts | 2 + src/vs/editor/test/browser/testCodeEditor.ts | 1 + .../keybinding/browser/keybindingService.ts | 57 +++++++----- 24 files changed, 170 insertions(+), 96 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index a3430a44354..143a15e1c6c 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -17,6 +17,29 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; +export const { registerWindow, getWindows, onDidCreateWindow } = (function () { + const windows: Window[] = []; + const onDidCreateWindow = new event.Emitter<{ window: Window; disposableStore: DisposableStore }>(); + return { + onDidCreateWindow: onDidCreateWindow.event, + registerWindow(window: Window): IDisposable { + windows.push(window); + const disposableStore = new DisposableStore(); + disposableStore.add(toDisposable(() => { + const index = windows.indexOf(window); + if (index !== -1) { + windows.splice(index, 1); + } + })); + onDidCreateWindow.fire({ window, disposableStore }); + return disposableStore; + }, + getWindows(): Window[] { + return windows; + } + }; +})(); + export function clearNode(node: HTMLElement): void { while (node.firstChild) { node.firstChild.remove(); @@ -282,34 +305,37 @@ export function addDisposableThrottledListener(node: } export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration { - return document.defaultView!.getComputedStyle(el, null); + return el.ownerDocument.defaultView!.getComputedStyle(el, null); } export function getClientArea(element: HTMLElement): Dimension { + const elDocument = element.ownerDocument; + const elWindow = elDocument.defaultView?.window; + // Try with DOM clientWidth / clientHeight - if (element !== document.body) { + if (element !== elDocument.body) { return new Dimension(element.clientWidth, element.clientHeight); } // If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight - if (platform.isIOS && window.visualViewport) { - return new Dimension(window.visualViewport.width, window.visualViewport.height); + if (platform.isIOS && elWindow?.visualViewport) { + return new Dimension(elWindow.visualViewport.width, elWindow.visualViewport.height); } // Try innerWidth / innerHeight - if (window.innerWidth && window.innerHeight) { - return new Dimension(window.innerWidth, window.innerHeight); + if (elWindow?.innerWidth && elWindow.innerHeight) { + return new Dimension(elWindow.innerWidth, elWindow.innerHeight); } // Try with document.body.clientWidth / document.body.clientHeight - if (document.body && document.body.clientWidth && document.body.clientHeight) { - return new Dimension(document.body.clientWidth, document.body.clientHeight); + if (elDocument.body && elDocument.body.clientWidth && elDocument.body.clientHeight) { + return new Dimension(elDocument.body.clientWidth, elDocument.body.clientHeight); } // Try with document.documentElement.clientWidth / document.documentElement.clientHeight - if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientHeight) { - return new Dimension(document.documentElement.clientWidth, document.documentElement.clientHeight); + if (elDocument.documentElement && elDocument.documentElement.clientWidth && elDocument.documentElement.clientHeight) { + return new Dimension(elDocument.documentElement.clientWidth, elDocument.documentElement.clientHeight); } throw new Error('Unable to figure out browser width and height'); @@ -431,8 +457,8 @@ export function getTopLeftOffset(element: HTMLElement): IDomPosition { while ( (element = element.parentNode) !== null - && element !== document.body - && element !== document.documentElement + && element !== element.ownerDocument.body + && element !== element.ownerDocument.documentElement ) { top -= element.scrollTop; const c = isShadowRoot(element) ? null : getComputedStyle(element); @@ -498,8 +524,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition { const bb = domNode.getBoundingClientRect(); return { - left: bb.left + window.scrollX, - top: bb.top + window.scrollY, + left: bb.left + (domNode.ownerDocument.defaultView?.scrollX ?? 0), + top: bb.top + (domNode.ownerDocument.defaultView?.scrollY ?? 0), width: bb.width, height: bb.height }; @@ -518,7 +544,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { } testElement = testElement.parentElement; - } while (testElement !== null && testElement !== document.documentElement); + } while (testElement !== null && testElement !== testElement.ownerDocument.documentElement); return zoom; } @@ -602,7 +628,7 @@ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: function getParentFlowToElement(node: HTMLElement): HTMLElement | null { const flowToParentId = node.dataset[parentFlowToDataKey]; if (typeof flowToParentId === 'string') { - return document.getElementById(flowToParentId); + return node.ownerDocument.getElementById(flowToParentId); } return null; } @@ -671,7 +697,7 @@ export function isInShadowDOM(domNode: Node): boolean { export function getShadowRoot(domNode: Node): ShadowRoot | null { while (domNode.parentNode) { - if (domNode === document.body) { + if (domNode === domNode.ownerDocument?.body) { // reached the body return null; } @@ -680,8 +706,12 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null { return isShadowRoot(domNode) ? domNode : null; } +/** + * Returns the active element across all child windows. + * Use this instead of `document.activeElement` to handle multiple windows. + */ export function getActiveElement(): Element | null { - let result = document.activeElement; + let result = getActiveDocument().activeElement; while (result?.shadowRoot) { result = result.shadowRoot.activeElement; @@ -690,6 +720,15 @@ export function getActiveElement(): Element | null { return result; } +/** + * Returns the active document across all child windows. + * Use this instead of `document` when reacting to dom events to handle multiple windows. + */ +export function getActiveDocument(): Document { + const documents = [document, ...getWindows().map(w => w.document)]; + return documents.find(doc => doc.hasFocus()) ?? document; +} + export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement { const style = document.createElement('style'); style.type = 'text/css'; @@ -875,15 +914,19 @@ class FocusTracker extends Disposable implements IFocusTracker { private _refreshStateHandler: () => void; - private static hasFocusWithin(element: HTMLElement): boolean { - const shadowRoot = getShadowRoot(element); - const activeElement = (shadowRoot ? shadowRoot.activeElement : document.activeElement); - return isAncestor(activeElement, element); + private static hasFocusWithin(element: HTMLElement | Window): boolean { + if (isHTMLElement(element)) { + const shadowRoot = getShadowRoot(element); + const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement); + return isAncestor(activeElement, element); + } else { + return isAncestor(window.document.activeElement, window.document); + } } constructor(element: HTMLElement | Window) { super(); - let hasFocus = FocusTracker.hasFocusWithin(element); + let hasFocus = FocusTracker.hasFocusWithin(element); let loosingFocus = false; const onFocus = () => { @@ -1092,7 +1135,7 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void { // standard DOM behavior is to move focus to the element. We // typically never want that, rather put focus to the closest element // in the hierarchy of the parent DOM nodes. - if (document.activeElement === node) { + if (node.ownerDocument.activeElement === node) { const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex'); parentFocusable?.focus(); } diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index db8ba364a86..bdfbf2ed661 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -69,8 +69,8 @@ export class StandardMouseEvent implements IMouseEvent { this.posy = e.pageY; } else { // Probably hit by MSGestureEvent - this.posx = e.clientX + document.body.scrollLeft + document.documentElement!.scrollLeft; - this.posy = e.clientY + document.body.scrollTop + document.documentElement!.scrollTop; + this.posx = e.clientX + this.target.ownerDocument.body.scrollLeft + this.target.ownerDocument.documentElement.scrollLeft; + this.posy = e.clientY + this.target.ownerDocument.body.scrollTop + this.target.ownerDocument.documentElement.scrollTop; } // Find the position of the iframe this code is executing in relative to the iframe where the event was captured. diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 5bfec5deba4..c2cc7511079 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -98,7 +98,7 @@ export class MouseHandler extends ViewEventHandler { // remove this listener if (!this._mouseLeaveMonitor) { - this._mouseLeaveMonitor = dom.addDisposableListener(document, 'mousemove', (e) => { + this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => { if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) { // went outside the editor! this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index b39b8f95ba6..4c57874ba42 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -336,7 +336,7 @@ export class HitTestContext { } private static _findAttribute(element: Element, attr: string, stopAt: Element): string | null { - while (element && element !== document.body) { + while (element && element !== element.ownerDocument.body) { if (element.hasAttribute && element.hasAttribute(attr)) { return element.getAttribute(attr); } @@ -917,7 +917,7 @@ export class MouseTargetFactory { range = (shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY); } } else { - range = (document).caretRangeFromPoint(coords.clientX, coords.clientY); + range = (ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY); } if (!range || !range.startContainer) { @@ -959,7 +959,7 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { - const hitResult: { offsetNode: Node; offset: number } = (document).caretPositionFromPoint(coords.clientX, coords.clientY); + const hitResult: { offsetNode: Node; offset: number } = (ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { // offsetNode is expected to be the token text @@ -1011,9 +1011,9 @@ export class MouseTargetFactory { private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult { let result: HitTestResult = new UnknownHitTestResult(); - if (typeof (document).caretRangeFromPoint === 'function') { + if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); - } else if ((document).caretPositionFromPoint) { + } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); } if (result.type === HitTestResultType.Content) { diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index d1db497e636..8be2c4e4baa 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -391,7 +391,7 @@ export class TextAreaHandler extends ViewPart { const distanceToModelLineStart = startModelPosition.column - 1 - visibleBeforeCharCount; const hiddenLineTextBefore = lineTextBeforeSelection.substring(0, lineTextBeforeSelection.length - visibleBeforeCharCount); const { tabSize } = this._context.viewModel.model.getOptions(); - const widthOfHiddenTextBefore = measureText(hiddenLineTextBefore, this._fontInfo, tabSize); + const widthOfHiddenTextBefore = measureText(this.textArea.domNode.ownerDocument, hiddenLineTextBefore, this._fontInfo, tabSize); return { distanceToModelLineStart, widthOfHiddenTextBefore }; })(); @@ -927,7 +927,7 @@ interface IRenderData { strikethrough?: boolean; } -function measureText(text: string, fontInfo: FontInfo, tabSize: number): number { +function measureText(document: Document, text: string, fontInfo: FontInfo, tabSize: number): number { if (text.length === 0) { return 0; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 36da11ef183..929df3c5d84 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -110,6 +110,8 @@ export interface ICompleteTextAreaWrapper extends ITextAreaWrapper { readonly onBlur: Event; readonly onSyntheticTap: Event; + readonly ownerDocument: Document; + setIgnoreSelectionChangeTime(reason: string): void; getIgnoreSelectionChangeTime(): number; resetSelectionChangeTime(): void; @@ -494,7 +496,7 @@ export class TextAreaInput extends Disposable { // `selectionchange` events often come multiple times for a single logical change // so throttle multiple `selectionchange` events that burst in a short period of time. let previousSelectionChangeEventTime = 0; - return dom.addDisposableListener(document, 'selectionchange', (e) => { + return dom.addDisposableListener(this._textArea.ownerDocument, 'selectionchange', (e) => {//todo inputLatency.onSelectionChange(); if (!this._hasFocus) { @@ -701,6 +703,10 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap public readonly onFocus = this._register(new DomEmitter(this._actual, 'focus')).event; public readonly onBlur = this._register(new DomEmitter(this._actual, 'blur')).event; + public get ownerDocument(): Document { + return this._actual.ownerDocument; + } + private _onSyntheticTap = this._register(new Emitter()); public readonly onSyntheticTap: Event = this._onSyntheticTap.event; @@ -725,7 +731,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap if (shadowRoot) { return shadowRoot.activeElement === this._actual; } else if (dom.isInDOM(this._actual)) { - return document.activeElement === this._actual; + return this._actual.ownerDocument.activeElement === this._actual; } else { return false; } @@ -775,7 +781,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap if (shadowRoot) { activeElement = shadowRoot.activeElement; } else { - activeElement = document.activeElement; + activeElement = textArea.ownerDocument.activeElement; } const currentIsFocused = (activeElement === textArea); diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index 7a3452d8461..44fc8eb9ab9 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -29,6 +29,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewModel } from 'vs/editor/common/viewModel'; import { ISelection } from 'vs/editor/common/core/selection'; +import { getActiveElement } from 'vs/base/browser/dom'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -315,9 +316,9 @@ abstract class EditorOrNativeTextInputCommand { // 2. handle case when focus is in some other `input` / `textarea`. target.addImplementation(1000, 'generic-dom-input-textarea', (accessor: ServicesAccessor, args: unknown) => { // Only if focused on an element that allows for entering text - const activeElement = document.activeElement; + const activeElement = getActiveElement(); if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - this.runDOMCommand(); + this.runDOMCommand(activeElement); return true; } return false; @@ -343,7 +344,7 @@ abstract class EditorOrNativeTextInputCommand { return true; } - public abstract runDOMCommand(): void; + public abstract runDOMCommand(activeElement: Element): void; public abstract runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise; } @@ -1875,13 +1876,13 @@ export namespace CoreNavigationCommands { constructor() { super(SelectAllCommand); } - public runDOMCommand(): void { + public runDOMCommand(activeElement: Element): void { if (isFirefox) { - (document.activeElement).focus(); - (document.activeElement).select(); + (activeElement).focus(); + (activeElement).select(); } - document.execCommand('selectAll'); + activeElement.ownerDocument.execCommand('selectAll'); } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const viewModel = editor._getViewModel(); @@ -2090,8 +2091,8 @@ export namespace CoreEditingCommands { constructor() { super(UndoCommand); } - public runDOMCommand(): void { - document.execCommand('undo'); + public runDOMCommand(activeElement: Element): void { + activeElement.ownerDocument.execCommand('undo'); } public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise { if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { @@ -2105,8 +2106,8 @@ export namespace CoreEditingCommands { constructor() { super(RedoCommand); } - public runDOMCommand(): void { - document.execCommand('redo'); + public runDOMCommand(activeElement: Element): void { + activeElement.ownerDocument.execCommand('redo'); } public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise { if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 4af00e67b13..60379467426 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -241,7 +241,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable { // Add a <> keydown event listener that will cancel the monitoring // if something other than a modifier key is pressed - this._keydownListener = dom.addStandardDisposableListener(document, 'keydown', (e) => { + this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { const chord = e.toKeyCodeChord(); if (chord.isModifierKey()) { // Allow modifier keys diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 75562c53821..f84a017b141 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -24,6 +24,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { IDisposable } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ILogService } from 'vs/platform/log/common/log'; +import { getActiveElement } from 'vs/base/browser/dom'; export type ServicesAccessor = InstantiationServicesAccessor; export type EditorContributionCtor = IConstructorSignature; @@ -219,7 +220,7 @@ export class MultiCommand extends Command { logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`); for (const impl of this._implementations) { if (impl.when) { - const context = contextKeyService.getContext(document.activeElement); + const context = contextKeyService.getContext(getActiveElement()); const value = impl.when.evaluate(context); if (!value) { continue; diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index 14a46ce44fe..ad88b9f9551 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -57,7 +57,7 @@ export class PartFingerprints { const result: PartFingerprint[] = []; let resultLen = 0; - while (child && child !== document.body) { + while (child && child !== child.ownerDocument.body) { if (child === stopAt) { break; } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index b494ba60cc2..3112c19c324 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -266,9 +266,11 @@ class Widget { } private _getMaxWidth(): number { + const elDocument = this.domNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; return ( this.allowEditorOverflow - ? window.innerWidth || document.documentElement!.offsetWidth || document.body.offsetWidth + ? elWindow?.innerWidth || elDocument.documentElement.offsetWidth || elDocument.body.offsetWidth : this._contentWidth ); } @@ -326,7 +328,9 @@ class Widget { const MIN_LIMIT = Math.max(LEFT_PADDING, domNodePosition.left - width); const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width - RIGHT_PADDING); - let absoluteLeft = domNodePosition.left + left - window.scrollX; + const elDocument = this._viewDomNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; + let absoluteLeft = domNodePosition.left + left - (elWindow?.scrollX ?? 0); if (absoluteLeft + width > MAX_LIMIT) { const delta = absoluteLeft - (MAX_LIMIT - width); @@ -348,10 +352,12 @@ class Widget { const belowTop = anchor.top + anchor.height; const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode); - const absoluteAboveTop = domNodePosition.top + aboveTop - window.scrollY; - const absoluteBelowTop = domNodePosition.top + belowTop - window.scrollY; + const elDocument = this._viewDomNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; + const absoluteAboveTop = domNodePosition.top + aboveTop - (elWindow?.scrollY ?? 0); + const absoluteBelowTop = domNodePosition.top + belowTop - (elWindow?.scrollY ?? 0); - const windowSize = dom.getClientArea(document.body); + const windowSize = dom.getClientArea(elDocument.body); const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width); // Leave some clearance to the top/bottom diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 9ccc307b2cd..f17051a0364 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -3,6 +3,7 @@ * 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'; @@ -290,10 +291,11 @@ export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | } } - if (document.activeElement) { + const activeElement = getActiveElement(); + if (activeElement) { for (const d of diffEditors) { const container = d.getContainerDomNode(); - if (isElementOrParentOf(container, document.activeElement)) { + if (isElementOrParentOf(container, activeElement)) { return d; } } diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 420abfa16a4..1aa2c9f3611 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from 'vs/base/browser/browser'; +import { getActiveDocument } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/textAreaInput'; @@ -179,7 +180,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { CopyOptions.forceCopyWithSyntaxHighlighting = true; editor.focus(); - document.execCommand('copy'); + editor.getContainerDomNode().ownerDocument.execCommand('copy'); CopyOptions.forceCopyWithSyntaxHighlighting = false; } } @@ -200,7 +201,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman if (selection && selection.isEmpty() && !emptySelectionClipboard) { return true; } - document.execCommand(browserCommand); + focusedEditor.getContainerDomNode().ownerDocument.execCommand(browserCommand); return true; } return false; @@ -208,7 +209,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman // 2. (default) handle case when focus is somewhere else. target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { - document.execCommand(browserCommand); + getActiveDocument().execCommand(browserCommand); return true; }); } @@ -225,7 +226,7 @@ if (PasteAction) { // Only if editor text focus (i.e. not if editor has widget focus). const focusedEditor = codeEditorService.getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasTextFocus()) { - const result = document.execCommand('paste'); + const result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste'); // Use the clipboard service if document.execCommand('paste') was not successful if (!result && platform.isWeb) { return (async () => { @@ -256,7 +257,7 @@ if (PasteAction) { // 2. Paste: (default) handle case when focus is somewhere else. PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { - document.execCommand('paste'); + getActiveDocument().execCommand('paste'); return true; }); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 2f25409976f..5e245dfb4a2 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -264,7 +264,7 @@ class SaturationBox extends Disposable { this.monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangePosition(event.pageX - origin.left, event.pageY - origin.top), () => null); - const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => { + const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => { this._onColorFlushed.fire(); pointerUpListener.dispose(); if (this.monitor) { @@ -387,7 +387,7 @@ abstract class Strip extends Disposable { monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangeTop(event.pageY - origin.top), () => null); - const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => { + const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => { this._onColorFlushed.fire(); pointerUpListener.dispose(); monitor.stopMonitoring(true); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index f85e595c4ff..08ad5897812 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener } from 'vs/base/browser/dom'; +import { addDisposableListener, getActiveDocument } from 'vs/base/browser/dom'; import { coalesce } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -99,7 +99,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._editor.focus(); try { this._pasteAsActionContext = { preferredId }; - document.execCommand('paste'); + getActiveDocument().execCommand('paste'); } finally { this._pasteAsActionContext = undefined; } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index c06e6bac4a7..00b575b5c89 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -675,7 +675,7 @@ export class ContentHoverWidget extends ResizableContentWidget { ); if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) { - const bodyBoxWidth = dom.getClientArea(document.body).width; + const bodyBoxWidth = dom.getClientArea(this._hover.containerDomNode.ownerDocument.body).width; const horizontalPadding = 14; return bodyBoxWidth - horizontalPadding; } else { diff --git a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts index 0243d9e88bf..72736b8ab6e 100644 --- a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts +++ b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts @@ -78,7 +78,7 @@ export abstract class ResizableContentWidget extends Disposable implements ICont return; } const editorBox = dom.getDomNodePagePosition(editorDomNode); - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(editorDomNode.ownerDocument.body); const mouseBottom = editorBox.top + mouseBox.top + mouseBox.height; return bodyBox.height - mouseBottom - BOTTOM_HEIGHT; } diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index e4466162dc4..6e13775d651 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -82,7 +82,7 @@ export class MessageController implements IEditorContribution { return; // override when mouse over message } - if (this._messageWidget.value && dom.isAncestor(document.activeElement, this._messageWidget.value.getDomNode())) { + if (this._messageWidget.value && dom.isAncestor(dom.getActiveElement(), this._messageWidget.value.getDomNode())) { return; // override when focus is inside the message } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 2ea6da1d7f8..e00d68cd368 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -192,7 +192,7 @@ export class RenameInputField implements IContentWidget { }; disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!document.hasFocus()))); + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); this._show(); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 6a03536715e..b86e2fd94ef 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -787,7 +787,7 @@ export class SuggestWidget implements IDisposable { return; } - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(this.element.domNode.ownerDocument.body); const info = this.getLayoutInfo(); if (!size) { diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index d6e95dd5a71..7f4646be053 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -372,7 +372,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } _placeAtAnchor(anchorBox: dom.IDomNodePagePosition, size: dom.Dimension, preferAlignAtTop: boolean) { - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(this.getDomNode().ownerDocument.body); const info = this.widget.getLayoutInfo(); diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index f66d1bbaa65..6874020e739 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -91,6 +91,8 @@ suite('TextAreaInput', () => { private _state: IRecordedTextareaState; private _currDispatchingEvent: IRecordedEvent | null; + public ownerDocument = document; + constructor() { super(); this._state = { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 28a17729768..418f0b990c9 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -100,6 +100,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { class TestEditorDomElement { parentElement: IContextKeyServiceTarget | null = null; + ownerDocument = document; setAttribute(attr: string, value: string): void { } removeAttribute(attr: string): void { } hasAttribute(attr: string): boolean { return false; } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index e6112182d25..10e58f90efe 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -18,7 +18,7 @@ import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from 'vs/base/common/keybindings'; import { IMMUTABLE_CODE_TO_KEY_CODE, KeyCode, KeyCodeUtils, KeyMod, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import { isMacintosh, OperatingSystem, OS } from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; @@ -237,28 +237,9 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateKeybindingsJsonSchema(); this._register(extensionService.onDidRegisterExtensions(() => this.updateKeybindingsJsonSchema())); - // for standard keybindings - this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - this.isComposingGlobalContextKey.set(e.isComposing); - const keyEvent = new StandardKeyboardEvent(e); - this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`); - this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`); - const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); - if (shouldPreventDefault) { - keyEvent.preventDefault(); - } - this.isComposingGlobalContextKey.set(false); - })); - - // for single modifier chord keybindings (e.g. shift shift) - this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - this.isComposingGlobalContextKey.set(e.isComposing); - const keyEvent = new StandardKeyboardEvent(e); - const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); - if (shouldPreventDefault) { - keyEvent.preventDefault(); - } - this.isComposingGlobalContextKey.set(false); + this._register(this._registerKeyListeners(window)); + this._register(dom.onDidCreateWindow(({ window, disposableStore }) => { + disposableStore.add(this._registerKeyListeners(window)); })); this._register(browser.onDidChangeFullscreen(() => { @@ -280,6 +261,36 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); } + private _registerKeyListeners(window: Window): IDisposable { + const disposables = new DisposableStore(); + + // for standard keybindings + disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this.isComposingGlobalContextKey.set(e.isComposing); + const keyEvent = new StandardKeyboardEvent(e); + this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`); + this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`); + const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); + if (shouldPreventDefault) { + keyEvent.preventDefault(); + } + this.isComposingGlobalContextKey.set(false); + })); + + // for single modifier chord keybindings (e.g. shift shift) + disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + this.isComposingGlobalContextKey.set(e.isComposing); + const keyEvent = new StandardKeyboardEvent(e); + const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); + if (shouldPreventDefault) { + keyEvent.preventDefault(); + } + this.isComposingGlobalContextKey.set(false); + })); + + return disposables; + } + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { this._contributions.push(contribution); if (contribution.onDidChange) { From 0a45b2737a14b6326eaf08df58984271e5d4bffd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 15 Sep 2023 15:13:50 +0200 Subject: [PATCH 023/133] typos --- .../src/languageFeatures/diagnostics.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index 1f6c21a5d88..450ffcb886c 100644 --- a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -206,7 +206,7 @@ class DiagnosticsTelemetryManager extends Disposable { this._diagnosticCodesMap.clear(); /* __GDPR__ "typescript.diagnostics" : { - "owner": "@aiday-mar", + "owner": "aiday-mar", "diagnosticCodes" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" @@ -214,7 +214,7 @@ class DiagnosticsTelemetryManager extends Disposable { } */ this._telemetryReporter.logTelemetry('typescript.diagnostics', { - diagnoticCodes: diagnosticCodes + diagnosticCodes: diagnosticCodes }); } }, 5 * 60 * 1000); // 5 minutes From f7c6e1a7a136084217a0180b980f56952ab65c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 15 Sep 2023 14:46:24 +0100 Subject: [PATCH 024/133] SCM: Adopt `StorageScope.WORKSPACE` and `StorageTarget.USER` (#193142) * update HistoryNavigator * update ResourceMap ctor * scm: use workspace storage for input histories * prepare to migrate * wip: migration * :lipstick: * delete invalid legacy scm histories --- src/vs/base/common/history.ts | 31 ++- src/vs/base/common/iterator.ts | 6 + src/vs/base/common/map.ts | 26 +- src/vs/base/test/common/history.test.ts | 20 ++ .../contrib/scm/common/scmService.ts | 257 +++++++++++------- 5 files changed, 235 insertions(+), 105 deletions(-) diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 22c2f27a678..de578810355 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -120,14 +120,15 @@ export class HistoryNavigator2 { private head: HistoryNode; private tail: HistoryNode; private cursor: HistoryNode; - private size: number; + private _size: number; + get size(): number { return this._size; } constructor(history: readonly T[], private capacity: number = 10) { if (history.length < 1) { throw new Error('not supported'); } - this.size = 1; + this._size = 1; this.head = this.tail = this.cursor = { value: history[0], previous: undefined, @@ -150,7 +151,7 @@ export class HistoryNavigator2 { this.tail.next = node; this.tail = node; this.cursor = this.tail; - this.size++; + this._size++; if (this.valueSet.has(value)) { this._deleteFromList(value); @@ -158,12 +159,12 @@ export class HistoryNavigator2 { this.valueSet.add(value); } - while (this.size > this.capacity) { + while (this._size > this.capacity) { this.valueSet.delete(this.head.value); this.head = this.head.next!; this.head.previous = undefined; - this.size--; + this._size--; } } @@ -188,6 +189,24 @@ export class HistoryNavigator2 { return oldValue; } + prepend(value: T): void { + if (this._size === this.capacity || this.valueSet.has(value)) { + return; + } + + const node: HistoryNode = { + value, + previous: undefined, + next: this.head + }; + + this.head.previous = node; + this.head = node; + this._size++; + + this.valueSet.add(value); + } + isAtEnd(): boolean { return this.cursor === this.tail; } @@ -243,7 +262,7 @@ export class HistoryNavigator2 { temp.next!.previous = temp.previous; } - this.size--; + this._size--; } temp = temp.next!; diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index f06dcf7acbc..c05887f9a70 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -30,6 +30,12 @@ export namespace Iterable { return iterable || _empty; } + export function* reverse(array: Array): Iterable { + for (let i = array.length - 1; i >= 0; i--) { + yield array[i]; + } + } + export function isEmpty(iterable: Iterable | undefined | null): boolean { return !iterable || iterable[Symbol.iterator]().next().done === true; } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 6f85a3111e3..74fd108e399 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -41,6 +41,10 @@ class ResourceMapEntry { constructor(readonly uri: URI, readonly value: T) { } } +function isEntries(arg: ResourceMap | ResourceMapKeyFn | readonly (readonly [URI, T])[] | undefined): arg is readonly (readonly [URI, T])[] { + return Array.isArray(arg); +} + export class ResourceMap implements Map { private static readonly defaultToKey = (resource: URI) => resource.toString(); @@ -63,13 +67,27 @@ export class ResourceMap implements Map { */ constructor(other?: ResourceMap, toKey?: ResourceMapKeyFn); - constructor(mapOrKeyFn?: ResourceMap | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) { - if (mapOrKeyFn instanceof ResourceMap) { - this.map = new Map(mapOrKeyFn.map); + /** + * + * @param other Another resource which this maps is created from + * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util + */ + constructor(entries?: readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn); + + constructor(arg?: ResourceMap | ResourceMapKeyFn | readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn) { + if (arg instanceof ResourceMap) { + this.map = new Map(arg.map); this.toKey = toKey ?? ResourceMap.defaultToKey; + } else if (isEntries(arg)) { + this.map = new Map(); + this.toKey = toKey ?? ResourceMap.defaultToKey; + + for (const [resource, value] of arg) { + this.set(resource, value); + } } else { this.map = new Map(); - this.toKey = mapOrKeyFn ?? ResourceMap.defaultToKey; + this.toKey = arg ?? ResourceMap.defaultToKey; } } diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index c5cacb494a7..eae79bb5afa 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { HistoryNavigator, HistoryNavigator2 } from 'vs/base/common/history'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('History Navigator', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('create reduces the input to limit', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 2); @@ -269,4 +272,21 @@ suite('History Navigator 2', () => { assert.strictEqual(testObject.previous(), '1'); }); + test('prepend', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + assert.strictEqual(testObject.current(), '4'); + assert.ok(testObject.isAtEnd()); + assert.deepStrictEqual(Array.from(testObject), ['1', '2', '3', '4']); + + testObject.prepend('0'); + assert.strictEqual(testObject.current(), '4'); + assert.ok(testObject.isAtEnd()); + assert.deepStrictEqual(Array.from(testObject), ['0', '1', '2', '3', '4']); + + testObject.prepend('2'); + assert.strictEqual(testObject.current(), '4'); + assert.ok(testObject.isAtEnd()); + assert.deepStrictEqual(Array.from(testObject), ['0', '1', '2', '3', '4']); + }); + }); diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 38867b0e96a..4740f81653f 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason, InputValidationType, IInputValidation } from './scm'; import { ILogService } from 'vs/platform/log/common/log'; @@ -11,7 +11,10 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { HistoryNavigator2 } from 'vs/base/common/history'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { ResourceMap } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; import { Iterable } from 'vs/base/common/iterator'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; class SCMInput implements ISCMInput { @@ -94,108 +97,31 @@ class SCMInput implements ISCMInput { private readonly _onDidChangeValidateInput = new Emitter(); readonly onDidChangeValidateInput: Event = this._onDidChangeValidateInput.event; - private historyNavigator: HistoryNavigator2; - private didChangeHistory: boolean; - - private static didGarbageCollect = false; - private static migrateAndGarbageCollectStorage(storageService: IStorageService): void { - if (SCMInput.didGarbageCollect) { - return; - } - - // Migrate from old format // TODO@joao: remove this migration code a few releases - const userKeys = Iterable.filter(storageService.keys(StorageScope.APPLICATION, StorageTarget.USER), key => key.startsWith('scm/input:')); - - for (const key of userKeys) { - try { - const rawHistory = storageService.get(key, StorageScope.APPLICATION, ''); - const history = JSON.parse(rawHistory); - - if (Array.isArray(history)) { - if (history.length === 0 || (history.length === 1 && history[0] === '')) { - // remove empty histories - storageService.remove(key, StorageScope.APPLICATION); - } else { - // migrate existing histories to have a timestamp - storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.APPLICATION, StorageTarget.MACHINE); - } - } else { - // move to MACHINE target - storageService.store(key, rawHistory, StorageScope.APPLICATION, StorageTarget.MACHINE); - } - } catch { - // remove unparseable entries - storageService.remove(key, StorageScope.APPLICATION); - } - } - - // Garbage collect - const machineKeys = Iterable.filter(storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE), key => key.startsWith('scm/input:')); - - for (const key of machineKeys) { - try { - const history = JSON.parse(storageService.get(key, StorageScope.APPLICATION, '')); - - if (Array.isArray(history?.history) && Number.isInteger(history?.timestamp) && new Date().getTime() - history?.timestamp > 2592000000) { - // garbage collect after 30 days - storageService.remove(key, StorageScope.APPLICATION); - } - } catch { - // remove unparseable entries - storageService.remove(key, StorageScope.APPLICATION); - } - } - - SCMInput.didGarbageCollect = true; - } + private readonly historyNavigator: HistoryNavigator2; + private didChangeHistory: boolean = false; constructor( readonly repository: ISCMRepository, - @IStorageService private storageService: IStorageService + private readonly history: SCMInputHistory ) { - SCMInput.migrateAndGarbageCollectStorage(storageService); - - const key = this.repository.provider.rootUri ? `scm/input:${this.repository.provider.label}:${this.repository.provider.rootUri?.path}` : undefined; - let history: string[] | undefined; - - if (key) { - try { - history = JSON.parse(this.storageService.get(key, StorageScope.APPLICATION, '')).history; - history = history?.map(s => s ?? ''); - } catch { - // noop - } - } - - if (!Array.isArray(history) || history.length === 0) { - history = [this._value]; - } else { - this._value = history[history.length - 1]; - } - - this.historyNavigator = new HistoryNavigator2(history, 50); - this.didChangeHistory = false; - - if (key) { - this.storageService.onWillSaveState(_ => { + if (this.repository.provider.rootUri) { + this.historyNavigator = history.getHistory(this.repository.provider.label, this.repository.provider.rootUri); + this.history.onWillSaveHistory(event => { if (this.historyNavigator.isAtEnd()) { this.saveValue(); } - if (!this.didChangeHistory) { - return; + if (this.didChangeHistory) { + event.historyDidIndeedChange(); } - const history = [...this.historyNavigator].map(s => s ?? ''); - - if (history.length === 0 || (history.length === 1 && history[0] === '')) { - storageService.remove(key, StorageScope.APPLICATION); - } else { - storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.APPLICATION, StorageTarget.MACHINE); - } this.didChangeHistory = false; }); + } else { // in memory only + this.historyNavigator = new HistoryNavigator2([''], 100); } + + this._value = this.historyNavigator.current(); } setValue(value: string, transient: boolean, reason?: SCMInputChangeReason) { @@ -253,14 +179,16 @@ class SCMRepository implements ISCMRepository { private readonly _onDidChangeSelection = new Emitter(); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; - readonly input: ISCMInput = new SCMInput(this, this.storageService); + readonly input: ISCMInput; constructor( public readonly id: string, public readonly provider: ISCMProvider, private disposable: IDisposable, - @IStorageService private storageService: IStorageService - ) { } + inputHistory: SCMInputHistory + ) { + this.input = new SCMInput(this, inputHistory); + } setSelected(selected: boolean): void { if (this._selected === selected) { @@ -277,6 +205,142 @@ class SCMRepository implements ISCMRepository { } } +class WillSaveHistoryEvent { + private _didChangeHistory = false; + get didChangeHistory() { return this._didChangeHistory; } + historyDidIndeedChange() { this._didChangeHistory = true; } +} + +class SCMInputHistory { + + private disposables = new DisposableStore(); + private readonly histories = new Map>>(); + + private readonly _onWillSaveHistory = this.disposables.add(new Emitter()); + readonly onWillSaveHistory = this._onWillSaveHistory.event; + + constructor( + @IStorageService private storageService: IStorageService, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + ) { + this.histories = new Map(); + + const entries = this.storageService.getObject<[string, URI, string[]][]>('scm.history', StorageScope.WORKSPACE, []); + + for (const [providerLabel, rootUri, history] of entries) { + let providerHistories = this.histories.get(providerLabel); + + if (!providerHistories) { + providerHistories = new ResourceMap(); + this.histories.set(providerLabel, providerHistories); + } + + providerHistories.set(rootUri, new HistoryNavigator2(history, 100)); + } + + if (this.migrateStorage()) { + this.saveToStorage(); + } + + this.disposables.add(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, 'scm.history', this.disposables)(e => { + if (e.external && e.key === 'scm.history') { + const raw = this.storageService.getObject<[string, URI, string[]][]>('scm.history', StorageScope.WORKSPACE, []); + + for (const [providerLabel, uri, rawHistory] of raw) { + const history = this.getHistory(providerLabel, uri); + + for (const value of Iterable.reverse(rawHistory)) { + history.prepend(value); + } + } + } + })); + + this.disposables.add(this.storageService.onWillSaveState(_ => { + const event = new WillSaveHistoryEvent(); + this._onWillSaveHistory.fire(event); + + if (event.didChangeHistory) { + this.saveToStorage(); + } + })); + } + + private saveToStorage(): void { + const raw: [string, URI, string[]][] = []; + + for (const [providerLabel, providerHistories] of this.histories) { + for (const [rootUri, history] of providerHistories) { + if (!(history.size === 1 && history.current() === '')) { + raw.push([providerLabel, rootUri, [...history]]); + } + } + } + + this.storageService.store('scm.history', raw, StorageScope.WORKSPACE, StorageTarget.USER); + } + + getHistory(providerLabel: string, rootUri: URI): HistoryNavigator2 { + let providerHistories = this.histories.get(providerLabel); + + if (!providerHistories) { + providerHistories = new ResourceMap(); + this.histories.set(providerLabel, providerHistories); + } + + let history = providerHistories.get(rootUri); + + if (!history) { + history = new HistoryNavigator2([''], 100); + providerHistories.set(rootUri, history); + } + + return history; + } + + // Migrates from Application scope storage to Workspace scope. + // TODO@joaomoreno: Change from January 2024 onwards such that the only code is to remove all `scm/input:` storage keys + private migrateStorage(): boolean { + let didSomethingChange = false; + const machineKeys = Iterable.filter(this.storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE), key => key.startsWith('scm/input:')); + + for (const key of machineKeys) { + try { + const legacyHistory = JSON.parse(this.storageService.get(key, StorageScope.APPLICATION, '')); + const match = /^scm\/input:([^:]+):(.+)$/.exec(key); + + if (!match || !Array.isArray(legacyHistory?.history) || !Number.isInteger(legacyHistory?.timestamp)) { + this.storageService.remove(key, StorageScope.APPLICATION); + continue; + } + + const [, providerLabel, rootPath] = match; + const rootUri = URI.file(rootPath); + + if (this.workspaceContextService.getWorkspaceFolder(rootUri)) { + const history = this.getHistory(providerLabel, rootUri); + + for (const entry of Iterable.reverse(legacyHistory.history as string[])) { + history.prepend(entry); + } + + didSomethingChange = true; + this.storageService.remove(key, StorageScope.APPLICATION); + } + } catch { + this.storageService.remove(key, StorageScope.APPLICATION); + } + } + + return didSomethingChange; + } + + dispose() { + this.disposables.dispose(); + } +} + + export class SCMService implements ISCMService { declare readonly _serviceBrand: undefined; @@ -285,6 +349,7 @@ export class SCMService implements ISCMService { get repositories(): Iterable { return this._repositories.values(); } get repositoryCount(): number { return this._repositories.size; } + private inputHistory: SCMInputHistory; private providerCount: IContextKey; private readonly _onDidAddProvider = new Emitter(); @@ -295,9 +360,11 @@ export class SCMService implements ISCMService { constructor( @ILogService private readonly logService: ILogService, + @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, - @IStorageService private storageService: IStorageService + @IStorageService storageService: IStorageService ) { + this.inputHistory = new SCMInputHistory(storageService, workspaceContextService); this.providerCount = contextKeyService.createKey('scm.providerCount', 0); } @@ -314,7 +381,7 @@ export class SCMService implements ISCMService { this.providerCount.set(this._repositories.size); }); - const repository = new SCMRepository(provider.id, provider, disposable, this.storageService); + const repository = new SCMRepository(provider.id, provider, disposable, this.inputHistory); this._repositories.set(provider.id, repository); this._onDidAddProvider.fire(repository); From 4e714a51b3075bdc1650fa73ba4905b92055d02b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 15 Sep 2023 15:55:31 +0200 Subject: [PATCH 025/133] Add next/previous commenting range commands+keybindings (#193209) Part of #192377 --- .../comments/browser/commentsController.ts | 66 ++++++++++++++++++- .../browser/commentsEditorContribution.ts | 50 +++++++++++++- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index d39fca2b63a..b67f1e94080 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -243,12 +243,12 @@ class CommentingRangeDecorator { private areRangesIntersectingOrTouchingByLine(a: Range, b: Range) { // Check if `a` is before `b` - if (a.endLineNumber < b.startLineNumber) { + if (a.endLineNumber < (b.startLineNumber - 1)) { return false; } // Check if `b` is before `a` - if (b.endLineNumber < a.startLineNumber) { + if ((b.endLineNumber + 1) < a.startLineNumber) { return false; } @@ -302,6 +302,46 @@ class CommentingRangeDecorator { }).map(actions => actions.action); } + public getNearestCommentingRange(findPosition: Position, reverse?: boolean): Range | undefined { + let findPositionContainedWithin: Range | undefined; + let decorations: CommentingRangeDecoration[]; + if (reverse) { + decorations = []; + for (let i = this.commentingRangeDecorations.length - 1; i >= 0; i--) { + decorations.push(this.commentingRangeDecorations[i]); + } + } else { + decorations = this.commentingRangeDecorations; + } + for (const decoration of decorations) { + const range = decoration.getActiveRange(); + if (!range) { + continue; + } + + if (findPositionContainedWithin && this.areRangesIntersectingOrTouchingByLine(range, findPositionContainedWithin)) { + findPositionContainedWithin = Range.plusRange(findPositionContainedWithin, range); + continue; + } + + if (range.startLineNumber <= findPosition.lineNumber && findPosition.lineNumber <= range.endLineNumber) { + findPositionContainedWithin = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); + continue; + } + + if (!reverse && range.endLineNumber < findPosition.lineNumber) { + continue; + } + + if (reverse && range.startLineNumber > findPosition.lineNumber) { + continue; + } + + return range; + } + return decorations[0].getActiveRange() ?? undefined; + } + public dispose(): void { this.commentingRangeDecorations = []; } @@ -671,6 +711,28 @@ export class CommentController implements IEditorContribution { this._findNearestCommentThread(true); } + private _findNearestCommentingRange(reverse?: boolean): void { + if (!this.editor?.hasModel()) { + return; + } + + const after = this.editor.getSelection().getEndPosition(); + const range = this._commentingRangeDecorator.getNearestCommentingRange(after, reverse); + if (range) { + const position = reverse ? range.getEndPosition() : range.getStartPosition(); + this.editor.setPosition(position); + this.editor.revealLineInCenterIfOutsideViewport(position.lineNumber); + } + } + + public nextCommentingRange(): void { + this._findNearestCommentingRange(); + } + + public previousCommentingRange(): void { + this._findNearestCommentingRange(true); + } + public dispose(): void { this.globalToDispose.dispose(); this.localToDispose.dispose(); diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index b12521cf40b..a370ecbb48f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import 'vs/css!./media/review'; import { IActiveCodeEditor, ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -63,11 +63,55 @@ export class PreviousCommentThreadAction extends EditorAction { } } - registerEditorContribution(ID, CommentController, EditorContributionInstantiation.AfterFirstRender); registerEditorAction(NextCommentThreadAction); registerEditorAction(PreviousCommentThreadAction); +export class NextCommentingRangeAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.goToNextCommentingRange', + label: nls.localize('goToNextCommentingRange', "Go to Next Commenting Range"), + alias: 'Go to Next Commenting Range', + precondition: WorkspaceHasCommenting, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow), + weight: KeybindingWeight.EditorContrib + } + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const controller = CommentController.get(editor); + controller?.nextCommentingRange(); + } +} + +export class PreviousCommentingRangeAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.goToPreviousCommentingRange', + label: nls.localize('goToPreviousCommentingRange', "Go to Previous Commenting Range"), + alias: 'Go to Next Commenting Range', + precondition: WorkspaceHasCommenting, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow), + weight: KeybindingWeight.EditorContrib + } + }); + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + const controller = CommentController.get(editor); + controller?.previousCommentingRange(); + } +} + +registerEditorAction(NextCommentingRangeAction); +registerEditorAction(PreviousCommentingRangeAction); + const TOGGLE_COMMENTING_COMMAND = 'workbench.action.toggleCommenting'; CommandsRegistry.registerCommand({ id: TOGGLE_COMMENTING_COMMAND, @@ -111,7 +155,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }, weight: KeybindingWeight.EditorContrib, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC), }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { From b386cc49982defc7efa5207045bc179df6d1cd63 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 15 Sep 2023 08:28:03 -0700 Subject: [PATCH 026/133] move single chat view container to workbench contribution (#192827) * minimal change to register chat contribs at startup * refactor and use a single view container * undo this change --- .../browser/chatContributionServiceImpl.ts | 83 +++++++++++++------ .../chat/common/chatContributionService.ts | 2 + 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index aa774249863..a16591501b5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -9,10 +9,10 @@ import * as resources from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { getClearAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; @@ -21,6 +21,8 @@ import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'interactiveSession', @@ -59,23 +61,28 @@ const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi }, }); -export class ChatContributionService implements IChatContributionService { - declare _serviceBrand: undefined; +export class ChatExtensionPointHandler implements IWorkbenchContribution { + private _viewContainer: ViewContainer; private _registrationDisposables = new Map(); - private _registeredProviders = new Map(); constructor( + @IChatContributionService readonly _chatContributionService: IChatContributionService ) { + this._viewContainer = this.registerViewContainer(); + this.handleAndRegisterChatExtensions(); + } + + private handleAndRegisterChatExtensions(): void { chatExtensionPoint.setHandler((extensions, delta) => { for (const extension of delta.added) { const extensionDisposable = new DisposableStore(); for (const providerDescriptor of extension.value) { - this.registerChatProvider(extension.description, providerDescriptor); + this.registerChatProvider(providerDescriptor); const extensionIcon = extension.description.icon ? resources.joinPath(extension.description.extensionLocation, extension.description.icon) : undefined; - this._registeredProviders.set(providerDescriptor.id, { + this._chatContributionService.registerChatProvider({ ...providerDescriptor, extensionIcon }); @@ -91,26 +98,17 @@ export class ChatContributionService implements IChatContributionService { } for (const providerDescriptor of extension.value) { - this._registeredProviders.delete(providerDescriptor.id); + this._chatContributionService.deregisterChatProvider(providerDescriptor.id); } } }); } - public get registeredProviders(): IChatProviderContribution[] { - return Array.from(this._registeredProviders.values()); - } - - public getViewIdForProvider(providerId: string): string { - return ChatViewPane.ID + '.' + providerId; - } - - private registerChatProvider(extension: Readonly, providerDescriptor: IRawChatProviderContribution): IDisposable { - const icon = providerDescriptor.icon ? resources.joinPath(extension.extensionLocation, providerDescriptor.icon) : Codicon.commentDiscussion; - const title = localize('chat.viewContainer.label', "Chat"); - + private registerViewContainer(): ViewContainer { // Register View Container - const viewContainerId = CHAT_SIDEBAR_PANEL_ID + '.' + providerDescriptor.id; + const title = localize('chat.viewContainer.label', "Chat"); + const icon = Codicon.commentDiscussion; + const viewContainerId = CHAT_SIDEBAR_PANEL_ID; const viewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: viewContainerId, title: { value: title, original: 'Chat' }, @@ -121,19 +119,23 @@ export class ChatContributionService implements IChatContributionService { order: 100, }, ViewContainerLocation.Sidebar); + return viewContainer; + } + + private registerChatProvider(providerDescriptor: IRawChatProviderContribution): IDisposable { // Register View - const viewId = this.getViewIdForProvider(providerDescriptor.id); + const viewId = this._chatContributionService.getViewIdForProvider(providerDescriptor.id); const viewDescriptor: IViewDescriptor[] = [{ id: viewId, - containerIcon: icon, - containerTitle: title, + containerIcon: this._viewContainer.icon, + containerTitle: this._viewContainer.title.value, name: providerDescriptor.label, canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: providerDescriptor.id }]), when: ContextKeyExpr.deserialize(providerDescriptor.when) }]; - Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); + Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); // Per-provider actions @@ -149,10 +151,39 @@ export class ChatContributionService implements IChatContributionService { return { dispose: () => { - Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, viewContainer); - Registry.as(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(viewContainer); + Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); + Registry.as(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(this._viewContainer); disposables.dispose(); } }; } } + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(ChatExtensionPointHandler, LifecyclePhase.Starting); + + +export class ChatContributionService implements IChatContributionService { + declare _serviceBrand: undefined; + + private _registeredProviders = new Map(); + + constructor( + ) { } + + public getViewIdForProvider(providerId: string): string { + return ChatViewPane.ID + '.' + providerId; + } + + public registerChatProvider(provider: IChatProviderContribution): void { + this._registeredProviders.set(provider.id, provider); + } + + public deregisterChatProvider(providerId: string): void { + this._registeredProviders.delete(providerId); + } + + public get registeredProviders(): IChatProviderContribution[] { + return Array.from(this._registeredProviders.values()); + } +} diff --git a/src/vs/workbench/contrib/chat/common/chatContributionService.ts b/src/vs/workbench/contrib/chat/common/chatContributionService.ts index 722183a5998..24720409f21 100644 --- a/src/vs/workbench/contrib/chat/common/chatContributionService.ts +++ b/src/vs/workbench/contrib/chat/common/chatContributionService.ts @@ -18,6 +18,8 @@ export interface IChatContributionService { _serviceBrand: undefined; registeredProviders: IChatProviderContribution[]; + registerChatProvider(provider: IChatProviderContribution): void; + deregisterChatProvider(providerId: string): void; getViewIdForProvider(providerId: string): string; } From c35bb82f6ecb8cb18e7304e204fa9db5e47c7755 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 15 Sep 2023 09:01:00 -0700 Subject: [PATCH 027/133] Rename to 'hiddenWhen' --- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- .../workbench/contrib/debug/common/debugSchemas.ts | 4 ++-- src/vs/workbench/contrib/debug/common/debugger.ts | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8dda275ca10..d55bda40b26 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -839,7 +839,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut configurationSnippets?: IJSONSchemaSnippet[]; variables?: { [key: string]: string }; when?: string; - whenDeemphasize?: string; + hiddenWhen?: string; deprecated?: string; strings?: { [key in DebuggerString]: string }; } diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index d5d045054bb..835bb1df7f8 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -72,8 +72,8 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE type: 'string', default: '' }, - whenDeemphasize: { - description: nls.localize('vscode.extension.contributes.debuggers.whenDeemphasized', "Condition which must be true to hide this type of debugger. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension-defined context key as appropriate for this."), + hiddenWhen: { + description: nls.localize('vscode.extension.contributes.debuggers.hiddenWhen', "Condition which must be true to hide this type of debugger from the debugger listing but enabled. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension-defined context key as appropriate for this."), type: 'string', default: '' }, diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 7d137df185c..e18a400fe91 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -29,7 +29,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { private mainExtensionDescription: IExtensionDescription | undefined; private debuggerWhen: ContextKeyExpression | undefined; - private debuggerWhenDeemphasize: ContextKeyExpression | undefined; + private debuggerHiddenWhen: ContextKeyExpression | undefined; constructor( private adapterManager: IAdapterManager, @@ -46,7 +46,7 @@ export class Debugger implements IDebugger, IDebuggerMetadata { this.merge(dbgContribution, extensionDescription); this.debuggerWhen = typeof this.debuggerContribution.when === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.when) : undefined; - this.debuggerWhenDeemphasize = typeof this.debuggerContribution.whenDeemphasize === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.whenDeemphasize) : undefined; + this.debuggerHiddenWhen = typeof this.debuggerContribution.hiddenWhen === 'string' ? ContextKeyExpr.deserialize(this.debuggerContribution.hiddenWhen) : undefined; } merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void { @@ -149,8 +149,8 @@ export class Debugger implements IDebugger, IDebuggerMetadata { return this.debuggerWhen; } - get whenDemphasis(): ContextKeyExpression | undefined { - return this.debuggerWhenDeemphasize; + get hiddenWhen(): ContextKeyExpression | undefined { + return this.debuggerHiddenWhen; } get enabled() { @@ -158,10 +158,10 @@ export class Debugger implements IDebugger, IDebuggerMetadata { } get isHiddenFromDropdown() { - if (!this.debuggerWhenDeemphasize) { + if (!this.debuggerHiddenWhen) { return false; } - return this.contextKeyService.contextMatchesRules(this.debuggerWhenDeemphasize); + return this.contextKeyService.contextMatchesRules(this.debuggerHiddenWhen); } get strings() { From 456dd7a43b43cf07b61f335ef97b189ad8744f07 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:03:54 -0700 Subject: [PATCH 028/133] Use an ActionRunner to transform context for term ctx menu actions --- .../api/common/extHostTerminalService.ts | 19 +++++++-- .../terminal/browser/terminalContextMenu.ts | 40 ++++++++++++++----- .../terminal/browser/terminalEditor.ts | 6 ++- .../terminal/browser/terminalTabbedView.ts | 17 ++++++-- .../contrib/terminal/common/terminal.ts | 2 +- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index f61297fe50c..60d88fbbcff 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -420,12 +420,23 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._proxy.$registerProcessSupport(supportsProcesses); this._extHostCommands.registerArgumentProcessor({ processArgument: arg => { + const deserialize = (arg: any) => { + const cast = arg as ISerializedTerminalInstanceContext; + return this._getTerminalById(cast.instanceId)?.value; + }; switch (arg?.$mid) { - case MarshalledId.TerminalContext: { - const cast = arg as ISerializedTerminalInstanceContext; - return cast.instanceIds.map(id => this._getTerminalById(id)); + case MarshalledId.TerminalContext: return deserialize(arg); + default: { + if (Array.isArray(arg)) { + return arg.map(e => { + switch (e?.$mid) { + case MarshalledId.TerminalContext: return deserialize(e); + default: return e; + } + }); + } + return arg; } - default: return arg; } } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index 54a28368e53..490ad05ad0f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -4,33 +4,50 @@ *--------------------------------------------------------------------------------------------*/ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction } from 'vs/base/common/actions'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { asArray } from 'vs/base/common/arrays'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { SingleOrMany } from 'vs/base/common/types'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ISerializedTerminalInstanceContext } from 'vs/workbench/contrib/terminal/common/terminal'; -class IInstanceContext { - private _instanceIds: number[]; +class InstanceContext { + private _instanceId: number; - constructor( - instances: SingleOrMany | undefined - ) { - this._instanceIds = (!instances ? [] : Array.isArray(instances) ? instances : [instances]).map(e => e.instanceId); + constructor(instance: ITerminalInstance) { + this._instanceId = instance.instanceId; } toJSON(): ISerializedTerminalInstanceContext { return { $mid: MarshalledId.TerminalContext, - instanceIds: this._instanceIds + instanceId: this._instanceId }; } } -export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { +class TerminalContextActionRunner extends ActionRunner { + constructor( + private readonly _commandService: ICommandService + ) { + super(); + } + + override run(action: IAction, context?: InstanceContext): Promise { + if (Array.isArray(context) && context.every(e => e instanceof InstanceContext)) { + // arg1: The (first) focused instance + // arg2: All selected instances + return this._commandService.executeCommand(action.id, context?.[0], context); + } + return super.run(action, context); + } +} + +export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, commandService: ICommandService, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { const standardEvent = new StandardMouseEvent(event); const actions: IAction[] = []; @@ -41,9 +58,12 @@ export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMan actions.push(...extraActions); } + const context: InstanceContext[] = contextInstances ? asArray(contextInstances).map(e => new InstanceContext(e)) : []; + contextMenuService.showContextMenu({ + actionRunner: new TerminalContextActionRunner(commandService), getAnchor: () => standardEvent, getActions: () => actions, - getActionsContext: () => new IInstanceContext(contextInstances), + getActionsContext: () => context, }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 24e872ca88e..55903a9e497 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 { ICommandService } from 'vs/platform/commands/common/commands'; export class TerminalEditor extends EditorPane { @@ -55,6 +56,7 @@ export class TerminalEditor extends EditorPane { @IContextKeyService contextKeyService: IContextKeyService, @IMenuService menuService: IMenuService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ICommandService private readonly _commandService: ICommandService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @INotificationService private readonly _notificationService: INotificationService, @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @@ -138,7 +140,7 @@ export class TerminalEditor extends EditorPane { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._commandService, this._contextMenuService); return; } @@ -176,7 +178,7 @@ export class TerminalEditor extends EditorPane { else if (!this._cancelContextMenu && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') { if (!this._cancelContextMenu) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._commandService, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 895d7b29221..14ef8d64f8a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -25,6 +25,7 @@ import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/termin import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { ICommandService } from 'vs/platform/commands/common/commands'; const $ = dom.$; @@ -73,6 +74,7 @@ export class TerminalTabbedView extends Disposable { @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotificationService private readonly _notificationService: INotificationService, + @ICommandService private readonly _commandService: ICommandService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IMenuService menuService: IMenuService, @@ -354,7 +356,7 @@ export class TerminalTabbedView extends Disposable { else if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, terminal, this._instanceMenu, this._contextMenuService); + openContextMenu(event, terminal, this._instanceMenu, this._commandService, this._contextMenuService); return; } @@ -388,7 +390,7 @@ export class TerminalTabbedView extends Disposable { } terminalContainer.focus(); if (!this._cancelContextMenu) { - openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); + openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._commandService, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); @@ -404,7 +406,16 @@ export class TerminalTabbedView extends Disposable { if (!emptyList) { this._terminalGroupService.lastAccessedMenu = 'tab-list'; } - openContextMenu(event, this._tabList.getFocusedElements(), emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); + + // Put the focused item first as it's used as the first positional argument + const selectedInstances = this._tabList.getSelectedElements(); + const focusedInstance = this._tabList.getFocusedElements()?.[0]; + if (focusedInstance) { + selectedInstances.splice(selectedInstances.findIndex(e => e.instanceId === focusedInstance.instanceId), 1); + selectedInstances.unshift(focusedInstance); + } + + openContextMenu(event, selectedInstances, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._commandService, this._contextMenuService, emptyList ? this._getTabActions() : undefined); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 9ecb3d6d273..03979a19aa9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -385,7 +385,7 @@ export interface ITerminalStatusHoverAction { */ export interface ISerializedTerminalInstanceContext { $mid: MarshalledId.TerminalContext; - instanceIds: number[]; + instanceId: number; } export const QUICK_LAUNCH_PROFILE_CHOICE = 'workbench.action.terminal.profile.choice'; From 26558d58c0f3bd4740ddb7e8504246cc699b9f00 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:21:37 -0700 Subject: [PATCH 029/133] Refactor Settings search (#193146) --- .../preferences/browser/settingsEditor2.ts | 63 ++++++-------- .../preferences/browser/settingsTreeModels.ts | 86 +++++++++++++++---- .../preferences/common/preferencesModels.ts | 42 --------- 3 files changed, 92 insertions(+), 99 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 8c9da329807..44bf36ad369 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1103,18 +1103,19 @@ export class SettingsEditor2 extends EditorPane { const reportModifiedProps = { key, query, - searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(), - rawResults: this.searchResultModel && this.searchResultModel.getRawResults(), + searchResults: this.searchResultModel?.getUniqueResults() ?? null, + rawResults: this.searchResultModel?.getRawResults() ?? null, showConfiguredOnly: !!this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG), isReset: typeof value === 'undefined', settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget }; + this.pendingSettingUpdate = null; return this.reportModifiedSetting(reportModifiedProps); }); } - private reportModifiedSetting(props: { key: string; query: string; searchResults: ISearchResult[] | null; rawResults: ISearchResult[] | null; showConfiguredOnly: boolean; isReset: boolean; settingsTarget: SettingsTarget }): void { + private reportModifiedSetting(props: { key: string; query: string; searchResults: ISearchResult | null; rawResults: ISearchResult[] | null; showConfiguredOnly: boolean; isReset: boolean; settingsTarget: SettingsTarget }): void { type SettingsEditorModifiedSettingEvent = { key: string; groupId: string | undefined; @@ -1133,29 +1134,21 @@ export class SettingsEditor2 extends EditorPane { isReset: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Identifies whether a setting was reset to its default value.' }; target: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The scope of the setting, such as user or workspace.' }; owner: 'rzhao271'; - comment: 'Event which fires when the user modifies a setting in the settings editor'; + comment: 'Event emitted when the user modifies a setting in the Settings editor'; }; - this.pendingSettingUpdate = null; - let groupId: string | undefined = undefined; let nlpIndex: number | undefined = undefined; let displayIndex: number | undefined = undefined; if (props.searchResults) { - const remoteResult = props.searchResults[SearchResultIdx.Remote]; - const localResult = props.searchResults[SearchResultIdx.Local]; - - const localIndex = localResult!.filterMatches.findIndex(m => m.setting.key === props.key); - groupId = localIndex >= 0 ? - 'local' : - 'remote'; - - displayIndex = localIndex >= 0 ? - localIndex : - remoteResult && (remoteResult.filterMatches.findIndex(m => m.setting.key === props.key) + localResult.filterMatches.length); + displayIndex = props.searchResults.filterMatches.findIndex(m => m.setting.key === props.key); if (this.searchResultModel) { const rawResults = this.searchResultModel.getRawResults(); + if (rawResults[SearchResultIdx.Local] && displayIndex >= 0) { + const settingInLocalResults = rawResults[SearchResultIdx.Local].filterMatches.some(m => m.setting.key === props.key); + groupId = settingInLocalResults ? 'local' : 'remote'; + } if (rawResults[SearchResultIdx.Remote]) { const _nlpIndex = rawResults[SearchResultIdx.Remote].filterMatches.findIndex(m => m.setting.key === props.key); nlpIndex = _nlpIndex >= 0 ? _nlpIndex : undefined; @@ -1487,7 +1480,7 @@ export class SettingsEditor2 extends EditorPane { await this.triggerSearch(query.replace(/\u203A/g, ' ')); if (query && this.searchResultModel) { - this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(this.searchResultModel!.getUniqueResults())); + this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(this.searchResultModel)); } } @@ -1578,49 +1571,41 @@ export class SettingsEditor2 extends EditorPane { return filterModel; } - private reportFilteringUsed(results: ISearchResult[]): void { + private reportFilteringUsed(searchResultModel: SearchResultModel | null): void { + if (!searchResultModel) { + return; + } + type SettingsEditorFilterEvent = { - 'durations.nlpResult': number | undefined; 'counts.nlpResult': number | undefined; 'counts.filterResult': number | undefined; - 'requestCount': number | undefined; + 'counts.uniqueResultsCount': number | undefined; }; type SettingsEditorFilterClassification = { - 'durations.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'How long the remote search provider took, if applicable.' }; 'counts.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of matches found by the remote search provider, if applicable.' }; 'counts.filterResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of matches found by the local search provider, if applicable.' }; - 'requestCount': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of requests sent to Bing, if applicable.' }; + 'counts.uniqueResultsCount': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of unique matches over both search providers, if applicable.' }; owner: 'rzhao271'; - comment: 'Tracks the number of requests and performance of the built-in search providers'; + comment: 'Tracks the performance of the built-in search providers'; }; - - const nlpResult = results[SearchResultIdx.Remote]; - const nlpMetadata = nlpResult?.metadata; - - const duration = { - nlpResult: nlpMetadata?.duration - }; - // Count unique results const counts: { nlpResult?: number; filterResult?: number } = {}; - const filterResult = results[SearchResultIdx.Local]; + const rawResults = searchResultModel.getRawResults(); + const filterResult = rawResults[SearchResultIdx.Local]; if (filterResult) { counts['filterResult'] = filterResult.filterMatches.length; } - + const nlpResult = rawResults[SearchResultIdx.Remote]; if (nlpResult) { counts['nlpResult'] = nlpResult.filterMatches.length; } - const requestCount = nlpMetadata?.requestCount; - + const uniqueResults = searchResultModel.getUniqueResults(); const data = { - 'durations.nlpResult': duration.nlpResult, 'counts.nlpResult': counts['nlpResult'], 'counts.filterResult': counts['filterResult'], - requestCount + 'counts.uniqueResultsCount': uniqueResults?.filterMatches.length }; - this.telemetryService.publicLog2('settingsEditor.filter', data); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 180f990b5a5..7e30259a1bb 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -11,7 +11,7 @@ import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configurat import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ENABLE_EXTENSION_TOGGLE_SETTINGS, ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; -import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { IExtensionSetting, ISearchResult, ISetting, ISettingMatch, SettingMatchType, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, APPLICATION_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; @@ -831,12 +831,12 @@ function isObjectSetting({ } // Flatten anyof schemas - const flatSchemas = arrays.flatten(schemas.map((schema): IJSONSchema[] => { + const flatSchemas = schemas.map((schema): IJSONSchema[] => { if (Array.isArray(schema.anyOf)) { return schema.anyOf; } return [schema]; - })); + }).flat(); return flatSchemas.every(isObjectRenderableSchema); } @@ -855,7 +855,7 @@ export const enum SearchResultIdx { export class SearchResultModel extends SettingsTreeModel { private rawSearchResults: ISearchResult[] | null = null; - private cachedUniqueSearchResults: ISearchResult[] | null = null; + private cachedUniqueSearchResults: ISearchResult | null = null; private newExtensionSearchResults: ISearchResult | null = null; private searchResultCount: number | null = null; @@ -874,29 +874,86 @@ export class SearchResultModel extends SettingsTreeModel { this.update({ id: 'searchResultModel', label: '' }); } - getUniqueResults(): ISearchResult[] { + private compareTwoNullableNumbers(a: number | undefined, b: number | undefined): number { + const aOrMax = a ?? Number.MAX_SAFE_INTEGER; + const bOrMax = b ?? Number.MAX_SAFE_INTEGER; + if (aOrMax < bOrMax) { + return -1; + } else if (aOrMax > bOrMax) { + return 1; + } else { + return 0; + } + } + + private sortResults(filterMatches: ISettingMatch[]): ISettingMatch[] { + filterMatches.sort((a, b) => { + if (a.matchType !== b.matchType) { + // Sort by match type if the match types are not the same. + // The priority of the match type is given by the SettingMatchType enum. + return b.matchType - a.matchType; + } else if (a.matchType === SettingMatchType.RemoteMatch) { + // The match types are the same and are RemoteMatch. + // Sort by score. + return b.score - a.score; + } else { + // The match types are the same. + if (a.setting.extensionInfo && b.setting.extensionInfo + && a.setting.extensionInfo.id === b.setting.extensionInfo.id) { + // These settings belong to the same extension. + if (a.setting.categoryLabel !== b.setting.categoryLabel + && (a.setting.categoryOrder !== undefined || b.setting.categoryOrder !== undefined) + && a.setting.categoryOrder !== b.setting.categoryOrder) { + // These two settings don't belong to the same category and have different category orders. + return this.compareTwoNullableNumbers(a.setting.categoryOrder, b.setting.categoryOrder); + } else if (a.setting.categoryLabel === b.setting.categoryLabel + && (a.setting.order !== undefined || b.setting.order !== undefined) + && a.setting.order !== b.setting.order) { + // These two settings belong to the same category, but have different orders. + return this.compareTwoNullableNumbers(a.setting.order, b.setting.order); + } + } + // In the worst case, go back to lexicographical order. + return b.score - a.score; + } + }); + return filterMatches; + } + + getUniqueResults(): ISearchResult | null { if (this.cachedUniqueSearchResults) { return this.cachedUniqueSearchResults; } if (!this.rawSearchResults) { - return []; + return null; } + let combinedFilterMatches: ISettingMatch[] = []; + const localMatchKeys = new Set(); const localResult = this.rawSearchResults[SearchResultIdx.Local]; - localResult?.filterMatches.forEach(m => localMatchKeys.add(m.setting.key)); + if (localResult) { + localResult.filterMatches.forEach(m => localMatchKeys.add(m.setting.key)); + combinedFilterMatches = localResult.filterMatches; + } const remoteResult = this.rawSearchResults[SearchResultIdx.Remote]; if (remoteResult) { remoteResult.filterMatches = remoteResult.filterMatches.filter(m => !localMatchKeys.has(m.setting.key)); - } + combinedFilterMatches = combinedFilterMatches.concat(remoteResult.filterMatches); - if (remoteResult) { this.newExtensionSearchResults = this.rawSearchResults[SearchResultIdx.NewExtensions]; } - this.cachedUniqueSearchResults = [localResult, remoteResult]; + // Combine and sort results. + combinedFilterMatches = this.sortResults(combinedFilterMatches); + + this.cachedUniqueSearchResults = { + filterMatches: combinedFilterMatches, + exactMatch: localResult?.exactMatch || remoteResult?.exactMatch + }; + return this.cachedUniqueSearchResults; } @@ -956,14 +1013,7 @@ export class SearchResultModel extends SettingsTreeModel { } private getFlatSettings(): ISetting[] { - const flatSettings: ISetting[] = []; - arrays.coalesce(this.getUniqueResults()) - .forEach(r => { - flatSettings.push( - ...r.filterMatches.map(m => m.setting)); - }); - - return flatSettings; + return this.getUniqueResults()?.filterMatches.map(m => m.setting) ?? []; } } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index fa940fa4782..070eab8f0b2 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -56,18 +56,6 @@ abstract class AbstractSettingsModel extends EditorModel { }); } - private compareTwoNullableNumbers(a: number | undefined, b: number | undefined): number { - const aOrMax = a ?? Number.MAX_SAFE_INTEGER; - const bOrMax = b ?? Number.MAX_SAFE_INTEGER; - if (aOrMax < bOrMax) { - return -1; - } else if (aOrMax > bOrMax) { - return 1; - } else { - return 0; - } - } - filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): ISettingMatch[] { const allGroups = this.filterGroups; @@ -90,36 +78,6 @@ abstract class AbstractSettingsModel extends EditorModel { } } - filterMatches.sort((a, b) => { - if (a.matchType !== b.matchType) { - // Sort by match type if the match types are not the same. - // The priority of the match type is given by the SettingMatchType enum. - return b.matchType - a.matchType; - } else if (a.matchType === SettingMatchType.RemoteMatch) { - // The match types are the same and are RemoteMatch. - // Sort by score. - return b.score - a.score; - } else { - // The match types are the same. - if (a.setting.extensionInfo && b.setting.extensionInfo - && a.setting.extensionInfo.id === b.setting.extensionInfo.id) { - // These settings belong to the same extension. - if (a.setting.categoryLabel !== b.setting.categoryLabel - && (a.setting.categoryOrder !== undefined || b.setting.categoryOrder !== undefined) - && a.setting.categoryOrder !== b.setting.categoryOrder) { - // These two settings don't belong to the same category and have different category orders. - return this.compareTwoNullableNumbers(a.setting.categoryOrder, b.setting.categoryOrder); - } else if (a.setting.categoryLabel === b.setting.categoryLabel - && (a.setting.order !== undefined || b.setting.order !== undefined) - && a.setting.order !== b.setting.order) { - // These two settings belong to the same category, but have different orders. - return this.compareTwoNullableNumbers(a.setting.order, b.setting.order); - } - } - // In the worst case, go back to lexicographical order. - return b.score - a.score; - } - }); return filterMatches; } From 6d1812beedd548c8aadd44b160cd82ea370eb97f Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 15 Sep 2023 09:27:21 -0700 Subject: [PATCH 030/133] Final Support for Notebook CodeActionKind (#193121) * finalize nb CodeActionKind * edit jsdoc + apiproposal change * stricter doc comment --- .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 18 ++++++++++++++++++ .../vscode.proposed.notebookCodeActions.d.ts | 17 ----------------- 3 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index a5e42e1d5d6..8f28d7f7f4d 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -58,7 +58,6 @@ export const allApiProposals = Object.freeze({ languageConfigurationAutoClosingPairs: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.d.ts', notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', - notebookCodeActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCodeActions.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', notebookExecution: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookExecution.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index a8ab72cab99..bb5573aac0e 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -2474,6 +2474,24 @@ declare module 'vscode' { */ static readonly SourceFixAll: CodeActionKind; + /** + * Base kind for all code actions applying to the enitre notebook's scope. CodeActionKinds using + * this should always begin with `notebook.` + * + * This requires that new CodeActions be created for it and contributed via extensions. + * Pre-existing kinds can not just have the new `notebook.` prefix added to them, as the functionality + * is unique to the full-notebook scope. + * + * Notebook CodeActionKinds can be initialized as either of the following (both resulting in `notebook.source.xyz`): + * - `const newKind = CodeActionKind.Notebook.append(CodeActionKind.Source.append('xyz').value)` + * - `const newKind = CodeActionKind.Notebook.append('source.xyz')` + * + * Example Kinds/Actions: + * - `notebook.source.organizeImports` (might move all imports to a new top cell) + * - `notebook.source.normalizeVariableNames` (might rename all variables to a standardized casing format) + */ + static readonly Notebook: CodeActionKind; + /** * Private constructor, use statix `CodeActionKind.XYZ` to derive from an existing code action kind. * diff --git a/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts b/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts deleted file mode 100644 index 0768e7c2d48..00000000000 --- a/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts +++ /dev/null @@ -1,17 +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' { - - // https://github.com/microsoft/vscode/issues/179213 - - export class NotebookCodeActionKind { - // can only return MULTI CELL workspaceEdits - // ex: notebook.organizeImprots - static readonly Notebook: CodeActionKind; - - constructor(value: string); - } -} From 17a2b3a10046b83b3a7a6a021bfebf8760ce703a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:21:05 -0700 Subject: [PATCH 031/133] Process args in place --- .../workbench/api/common/extHostTerminalService.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 60d88fbbcff..0fd8310eebd 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -427,13 +427,16 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I switch (arg?.$mid) { case MarshalledId.TerminalContext: return deserialize(arg); default: { + // Do array transformation in place as this is a hot path if (Array.isArray(arg)) { - return arg.map(e => { - switch (e?.$mid) { - case MarshalledId.TerminalContext: return deserialize(e); - default: return e; + for (let i = 0; i < arg.length; i++) { + if (arg[i].$mid === MarshalledId.TerminalContext) { + arg[i] = deserialize(arg[i]); + } else { + // Probably something else, so exit early + break; } - }); + } } return arg; } From 1a65428e5ccdeba5489c6b97c06d0cd4762dd35e Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 10:45:46 -0700 Subject: [PATCH 032/133] setting details --- .../contrib/notebook/browser/notebook.contribution.ts | 7 ++++++- .../contrib/notebook/browser/view/notebookCellList.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index c911c67c50c..4eefc4b8077 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -1018,7 +1018,12 @@ configurationRegistry.registerConfiguration({ [NotebookSetting.cellExecutionScroll]: { markdownDescription: nls.localize('notebook.executeCell.scrollToRevealBehavior', "How far to scroll when revealing the next cell upon exectuting {0}.", 'notebook.cell.executeAndSelectBelow'), type: 'string', - enum: ['full', 'partial'], + enum: ['full', 'firstLine', 'none'], + markdownEnumDescriptions: [ + nls.localize('notebook.executeCell.scrollToRevealBehavior.full', 'Fully reveal the next cell.'), + nls.localize('notebook.executeCell.scrollToRevealBehavior.firstLine', 'Reveal the first line of the next cell.'), + nls.localize('notebook.executeCell.scrollToRevealBehavior.none', 'Do not scroll with the focus change.'), + ], default: 'full' } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 91a6e89e793..3e2182c7cd9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -916,7 +916,9 @@ export class NotebookCellList extends WorkbenchList implements ID break; case CellRevealPosition.Bottom: if (partial) { - this.view.setScrollTop(elementTop - (this.view.renderHeight - 100)); + const lineHeight = this.viewModel?.layoutInfo?.fontInfo.lineHeight ?? 15; + const cellPadding = 20; + this.view.setScrollTop(this.scrollTop + elementTop - wrapperBottom + lineHeight + cellPadding); break; } this.view.setScrollTop(this.scrollTop + (elementBottom - wrapperBottom)); From 094461dbfae7676b3727fa5e510ba01225743cd0 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 10:47:34 -0700 Subject: [PATCH 033/133] account for toolbar size when measuring initial body height --- .../contrib/notebook/browser/notebookEditorWidget.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 3830664287b..89ebcd2f6a8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1510,7 +1510,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD })); if (this._dimension) { - this._list.layout(this._dimension.height, this._dimension.width); + this._list.layout(this.getBodyHeight(this._dimension.height), this._dimension.width); } else { this._list.layout(); } @@ -1777,6 +1777,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._scrollBeyondLastLine && !this.isEmbedded; } + private getBodyHeight(dimensionHeight: number) { + return Math.max(dimensionHeight - (this._notebookTopToolbar?.useGlobalToolbar ? /** Toolbar height */ 26 : 0), 0); + } + layout(dimension: DOM.Dimension, shadowElement?: HTMLElement, position?: DOM.IDomPosition): void { if (!shadowElement && this._shadowElementViewInfo === null) { this._dimension = dimension; @@ -1800,7 +1804,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._dimension = dimension; this._position = position; - const newBodyHeight = Math.max(dimension.height - (this._notebookTopToolbar?.useGlobalToolbar ? /** Toolbar height */ 26 : 0), 0); + const newBodyHeight = this.getBodyHeight(dimension.height); DOM.size(this._body, dimension.width, newBodyHeight); const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); From 59c811583afc2fc02434e73954d307303bd33e72 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 11:15:22 -0700 Subject: [PATCH 034/133] update setting names, include "none" option --- .../notebook/browser/controller/executeActions.ts | 11 ++++++++--- .../notebook/browser/notebook.contribution.ts | 12 ++++++------ .../notebook/browser/view/notebookCellList.ts | 14 ++++++++------ .../contrib/notebook/common/notebookCommon.ts | 2 +- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 79f377a3984..088373670cc 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -458,9 +458,14 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { const config = accessor.get(IConfigurationService); const scrollBehavior = config.getValue(NotebookSetting.cellExecutionScroll); - const focusOptions: IFocusNotebookCellOptions = { - minimalScrolling: scrollBehavior === 'full' ? 'fullReveal' : 'partialReveal' - }; + let focusOptions: IFocusNotebookCellOptions; + if (scrollBehavior === 'none') { + focusOptions = { skipReveal: true }; + } else { + focusOptions = { + minimalScrolling: scrollBehavior === 'fullCell' ? 'fullReveal' : 'partialReveal' + }; + } if (context.cell.cellKind === CellKind.Markup) { const nextCell = context.notebookEditor.cellAt(idx + 1); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 4eefc4b8077..b100f521208 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -1016,15 +1016,15 @@ configurationRegistry.registerConfiguration({ default: typeof product.quality === 'string' && product.quality !== 'stable' // only enable as default in insiders }, [NotebookSetting.cellExecutionScroll]: { - markdownDescription: nls.localize('notebook.executeCell.scrollToRevealBehavior', "How far to scroll when revealing the next cell upon exectuting {0}.", 'notebook.cell.executeAndSelectBelow'), + markdownDescription: nls.localize('notebook.revealNextOnExecuteBehavior.description', "How far to scroll when revealing the next cell upon exectuting {0}.", 'notebook.cell.executeAndSelectBelow'), type: 'string', - enum: ['full', 'firstLine', 'none'], + enum: ['fullCell', 'firstLine', 'none'], markdownEnumDescriptions: [ - nls.localize('notebook.executeCell.scrollToRevealBehavior.full', 'Fully reveal the next cell.'), - nls.localize('notebook.executeCell.scrollToRevealBehavior.firstLine', 'Reveal the first line of the next cell.'), - nls.localize('notebook.executeCell.scrollToRevealBehavior.none', 'Do not scroll with the focus change.'), + nls.localize('notebook.revealNextOnExecuteBehavior.fullCell.description', 'Scroll to fully reveal the next cell.'), + nls.localize('notebook.revealNextOnExecuteBehavior.firstLine.description', 'Scroll to reveal the first line of the next cell.'), + nls.localize('notebook.revealNextOnExecuteBehavior.nonedescription', 'Do not scroll to reveal the next cell.'), ], - default: 'full' + default: 'fullCell' } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 3e2182c7cd9..c7045b0935a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -881,10 +881,7 @@ export class NotebookCellList extends WorkbenchList implements ID const elementBottom = this.view.elementHeight(viewIndex) + elementTop; if (ignoreIfInsideViewport) { - if (partial && elementBottom > scrollTop && elementTop < wrapperBottom) { - //element is already partially visible - return; - } else if (elementTop >= scrollTop && elementBottom < wrapperBottom) { + if (elementTop >= scrollTop && elementBottom < wrapperBottom) { // element is already fully visible return; } @@ -917,8 +914,13 @@ export class NotebookCellList extends WorkbenchList implements ID case CellRevealPosition.Bottom: if (partial) { const lineHeight = this.viewModel?.layoutInfo?.fontInfo.lineHeight ?? 15; - const cellPadding = 20; - this.view.setScrollTop(this.scrollTop + elementTop - wrapperBottom + lineHeight + cellPadding); + const firstLineLocation = elementTop + lineHeight + 20; + if (firstLineLocation < wrapperBottom) { + // first line is already partially visible + return; + } + + this.view.setScrollTop(this.scrollTop + (firstLineLocation - wrapperBottom)); break; } this.view.setScrollTop(this.scrollTop + (elementBottom - wrapperBottom)); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 269144fa599..5408c07d002 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -964,7 +964,7 @@ export const NotebookSetting = { confirmDeleteRunningCell: 'notebook.confirmDeleteRunningCell', remoteSaving: 'notebook.experimental.remoteSave', gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols', - cellExecutionScroll: 'notebook.executeCell.scrollToRevealBehavior' + cellExecutionScroll: 'notebook.revealNextOnExecuteBehavior' } as const; export const enum CellStatusbarAlignment { From 257869baca269d91b6917ed83bfeece2f527ec6a Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Sep 2023 11:24:34 -0700 Subject: [PATCH 035/133] fix: do not set this._sequence to undefined if path title is not windows path Co-authored-by: Noel Abrahams --- .../contrib/terminal/browser/terminalInstance.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index d1365b440f2..3823ed3e0ad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1935,13 +1935,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // to show just the file name. This should only happen if the title looks like an // absolute Windows file path this._sequence = title; - if (this._processManager.os === OperatingSystem.Windows) { - if (title.match(/^[a-zA-Z]:\\.+\.[a-zA-Z]{1,3}/)) { - title = path.win32.parse(title).name; - this._sequence = title; - } else { - this._sequence = undefined; - } + if (this._processManager.os === OperatingSystem.Windows && + title.match(/^[a-zA-Z]:\\.+\.[a-zA-Z]{1,3}/)) { + this._sequence = path.win32.parse(title).name; } break; } From 8963ff84dbc1e9bc0538162097d4a3765d7a878b Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 12:23:44 -0700 Subject: [PATCH 036/133] explicit behavior name --- .../notebook/browser/controller/executeActions.ts | 4 ++-- .../contrib/notebook/browser/notebookBrowser.ts | 10 +++++++--- .../notebook/browser/notebookEditorWidget.ts | 12 ++++++------ .../notebook/browser/view/notebookCellList.ts | 14 +++++++------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 088373670cc..73480164384 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -21,7 +21,7 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/in import { CTX_INLINE_CHAT_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IFocusNotebookCellOptions, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellKind, CellUri, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -463,7 +463,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { focusOptions = { skipReveal: true }; } else { focusOptions = { - minimalScrolling: scrollBehavior === 'fullCell' ? 'fullReveal' : 'partialReveal' + revealBehavior: scrollBehavior === 'fullCell' ? ScrollToRevealBehavior.fullCell : ScrollToRevealBehavior.firstLine }; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 1701fbbce8e..f525ec94a5c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -147,11 +147,15 @@ export interface ICommonCellInfo { readonly executionId?: string; } +export enum ScrollToRevealBehavior { + fullCell, + firstLine +} + export interface IFocusNotebookCellOptions { readonly skipReveal?: boolean; readonly focusEditorLine?: number; - /** Set a value to only scroll as much as needed, partialReveal will bring the element partially into the view */ - readonly minimalScrolling?: 'fullReveal' | 'partialReveal' | undefined; + readonly revealBehavior?: ScrollToRevealBehavior | undefined; readonly outputId?: string; readonly altOutputId?: string; } @@ -327,7 +331,7 @@ export const enum CellRevealSyncType { Top = 2, Center = 3, CenterIfOutsideViewport = 4, - PartialIfOutsideViewport = 5 + FirstLineIfOutsideViewport = 5 } export enum CellRevealRangeType { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index be3d80cc1f2..0725ac59d3f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -52,7 +52,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_PANE_BACKGROUND, PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealRangeType, CellRevealSyncType, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealRangeType, CellRevealSyncType, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType, ScrollToRevealBehavior } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; @@ -2072,8 +2072,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._list.revealCell(cell, CellRevealSyncType.CenterIfOutsideViewport); } - revealPartialIfOutsideViewport(cell: ICellViewModel) { - this._list.revealCell(cell, CellRevealSyncType.PartialIfOutsideViewport); + revealFirstLineIfOutsideViewport(cell: ICellViewModel) { + this._list.revealCell(cell, CellRevealSyncType.FirstLineIfOutsideViewport); } async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { @@ -2377,10 +2377,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD if (typeof options?.focusEditorLine === 'number') { this._cursorNavMode.set(true); this.revealInView(cell); - } else if (options?.minimalScrolling === 'fullReveal') { + } else if (options?.revealBehavior === ScrollToRevealBehavior.fullCell) { this.revealInView(cell); - } else if (options?.minimalScrolling === 'partialReveal') { - this.revealPartialIfOutsideViewport(cell); + } else if (options?.revealBehavior === ScrollToRevealBehavior.firstLine) { + this.revealFirstLineIfOutsideViewport(cell); } else { this.revealInCenterIfOutsideViewport(cell); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index c7045b0935a..8b0d9992e07 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -826,12 +826,12 @@ export class NotebookCellList extends WorkbenchList implements ID this._revealInViewWithMinimalScrolling(startIndex); } - private _revealInViewWithMinimalScrolling(viewIndex: number, partial?: boolean) { + private _revealInViewWithMinimalScrolling(viewIndex: number, firstLine?: boolean) { const firstIndex = this.view.firstVisibleIndex; if (viewIndex <= firstIndex) { - this._revealInternal(viewIndex, true, CellRevealPosition.Top, partial); + this._revealInternal(viewIndex, true, CellRevealPosition.Top, firstLine); } else { - this._revealInternal(viewIndex, true, CellRevealPosition.Bottom, partial); + this._revealInternal(viewIndex, true, CellRevealPosition.Bottom, firstLine); } } @@ -861,7 +861,7 @@ export class NotebookCellList extends WorkbenchList implements ID case CellRevealSyncType.CenterIfOutsideViewport: this._revealInternal(index, true, CellRevealPosition.Center); break; - case CellRevealSyncType.PartialIfOutsideViewport: + case CellRevealSyncType.FirstLineIfOutsideViewport: this._revealInViewWithMinimalScrolling(index, true); break; case CellRevealSyncType.Default: @@ -870,7 +870,7 @@ export class NotebookCellList extends WorkbenchList implements ID } } - private _revealInternal(viewIndex: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition, partial?: boolean) { + private _revealInternal(viewIndex: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition, firstLine?: boolean) { if (viewIndex >= this.view.length) { return; } @@ -912,11 +912,11 @@ export class NotebookCellList extends WorkbenchList implements ID } break; case CellRevealPosition.Bottom: - if (partial) { + if (firstLine) { const lineHeight = this.viewModel?.layoutInfo?.fontInfo.lineHeight ?? 15; const firstLineLocation = elementTop + lineHeight + 20; if (firstLineLocation < wrapperBottom) { - // first line is already partially visible + // first line is already visible return; } From 5ae15d5398e92feabe82ec570ef21e4f8bb43b10 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 15 Sep 2023 21:13:25 +0200 Subject: [PATCH 037/133] Update @vscode/proxy-agent and enable new cert loading --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- src/vs/workbench/api/node/proxyResolver.ts | 22 ++++++++++++++++++---- yarn.lock | 8 ++++---- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 8e9dd05a1b2..c6dd16c69e4 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.17.2", + "@vscode/proxy-agent": "^0.17.3", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.11", "@vscode/sqlite3": "5.1.6-vscode", diff --git a/remote/package.json b/remote/package.json index 50c98b47a48..fa7bd64e3d6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.17.2", + "@vscode/proxy-agent": "^0.17.3", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.11", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/yarn.lock b/remote/yarn.lock index 1771063a82c..040443b2631 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -58,10 +58,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.17.2": - version "0.17.2" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.2.tgz#0e0dac24478e2d71a4fd1b2bb5f84dc61add79e2" - integrity sha512-aKRo1YfUCsgEjHvr2HXfM6dwHhieyO6G+WHly7jewyyTJ1nANWEocS3JRnRbC4KjlajKhSUEOx838cdnY/vRtA== +"@vscode/proxy-agent@^0.17.3": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.3.tgz#ae46cb64d58a1a2ba063321eb4e3339eaba703b8" + integrity sha512-l+00ALcWQyDsW3kd22uOeAn84KHFkixnk/STiBFRxltbTiaiJsHCCpjlmX+x/RK+B97FcRLlg+MRRb7jtWAnng== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index 6c0cd980ced..b8813437578 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -14,11 +14,11 @@ import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protoc import { IExtensionHostInitData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel as LogServiceLevel } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch } from '@vscode/proxy-agent'; -const systemCertificatesV2Default = false; +const systemCertificatesV2Default = true; export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, @@ -53,8 +53,22 @@ export function connectProxyResolver( extHostLogService.error(message, ...args); } }, - getLogLevel: () => extHostLogService.getLevel(), - // TODO @chrmarti Remove this from proxy agent + getLogLevel: () => { + const level = extHostLogService.getLevel(); + switch (level) { + case LogServiceLevel.Trace: return LogLevel.Trace; + case LogServiceLevel.Debug: return LogLevel.Debug; + case LogServiceLevel.Info: return LogLevel.Info; + case LogServiceLevel.Warning: return LogLevel.Warning; + case LogServiceLevel.Error: return LogLevel.Error; + case LogServiceLevel.Off: return LogLevel.Off; + default: return never(level); + } + function never(level: never) { + extHostLogService.error('Unknown log level', level); + return LogLevel.Debug; + } + }, proxyResolveTelemetry: () => { }, useHostProxy: doUseHostProxy, addCertificates: [], diff --git a/yarn.lock b/yarn.lock index cf5b0450563..a7b290ff3cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1304,10 +1304,10 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.17.2": - version "0.17.2" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.2.tgz#0e0dac24478e2d71a4fd1b2bb5f84dc61add79e2" - integrity sha512-aKRo1YfUCsgEjHvr2HXfM6dwHhieyO6G+WHly7jewyyTJ1nANWEocS3JRnRbC4KjlajKhSUEOx838cdnY/vRtA== +"@vscode/proxy-agent@^0.17.3": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.3.tgz#ae46cb64d58a1a2ba063321eb4e3339eaba703b8" + integrity sha512-l+00ALcWQyDsW3kd22uOeAn84KHFkixnk/STiBFRxltbTiaiJsHCCpjlmX+x/RK+B97FcRLlg+MRRb7jtWAnng== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" From 9abb9c4be86c9d033f145acb3c911fb9bb72c7b5 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 13:15:25 -0700 Subject: [PATCH 038/133] remove unused param --- .../contrib/notebook/test/browser/testNotebookEditor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index b8c0039c16c..759d9fd5ad1 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -215,7 +215,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)); const viewModel: NotebookViewModel = disposables.add(instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false })); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables, viewContext)); + const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); cellList.attachViewModel(viewModel); const listViewInfoAccessor = disposables.add(new ListViewInfoAccessor(cellList)); @@ -416,7 +416,7 @@ export async function withTestNotebook(cells: [source: string, lang: st }); } -export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore, viewContext?: ViewContext) { +export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore) { const delegate: IListVirtualDelegate = { getHeight(element: CellViewModel) { return element.getHeight(17); }, getTemplateId() { return 'template'; } From 339fedbd3af4b5cadb93ad2400cbf11ed1853c5b Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 14:38:57 -0700 Subject: [PATCH 039/133] get actual padding value --- .../contrib/notebook/browser/notebookEditorWidget.ts | 1 + .../contrib/notebook/browser/view/notebookCellList.ts | 5 ++++- .../contrib/notebook/test/browser/testNotebookEditor.ts | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 0725ac59d3f..5f21bca10b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -889,6 +889,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD NotebookCellList, 'NotebookCellList', this._body, + this._viewContext.notebookOptions, this._listDelegate, renderers, this.scopedContextKeyService, diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 8b0d9992e07..0f2c80929bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -31,6 +31,7 @@ import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewM import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListViewOptions, IListView } from 'vs/base/browser/ui/list/listView'; import { NotebookCellListView } from 'vs/workbench/contrib/notebook/browser/view/notebookCellListView'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; const enum CellEditorRevealType { Line, @@ -147,6 +148,7 @@ export class NotebookCellList extends WorkbenchList implements ID constructor( private listUser: string, container: HTMLElement, + private readonly notebookOptions: NotebookOptions, delegate: IListVirtualDelegate, renderers: IListRenderer[], contextKeyService: IContextKeyService, @@ -914,7 +916,8 @@ export class NotebookCellList extends WorkbenchList implements ID case CellRevealPosition.Bottom: if (firstLine) { const lineHeight = this.viewModel?.layoutInfo?.fontInfo.lineHeight ?? 15; - const firstLineLocation = elementTop + lineHeight + 20; + const padding = this.notebookOptions.getLayoutConfiguration().cellTopMargin + this.notebookOptions.getLayoutConfiguration().editorTopPadding; + const firstLineLocation = elementTop + lineHeight + padding; if (firstLineLocation < wrapperBottom) { // first line is already visible return; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 759d9fd5ad1..2583d83d58d 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -215,7 +215,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)); const viewModel: NotebookViewModel = disposables.add(instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false })); - const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); + const cellList = disposables.add(createNotebookCellList(instantiationService, disposables, viewContext)); cellList.attachViewModel(viewModel); const listViewInfoAccessor = disposables.add(new ListViewInfoAccessor(cellList)); @@ -416,7 +416,7 @@ export async function withTestNotebook(cells: [source: string, lang: st }); } -export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore) { +export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore, viewContext?: ViewContext) { const delegate: IListVirtualDelegate = { getHeight(element: CellViewModel) { return element.getHeight(17); }, getTemplateId() { return 'template'; } @@ -429,10 +429,13 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe disposeTemplate() { } }; + const notebookOptions = !!viewContext ? viewContext.notebookOptions + : new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); const cellList: NotebookCellList = disposables.add(instantiationService.createInstance( NotebookCellList, 'NotebookCellList', DOM.$('container'), + notebookOptions, delegate, [renderer], instantiationService.get(IContextKeyService), From 012e79d53c62ac8e9d6510cfb2a158910a00a093 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 15 Sep 2023 15:36:03 -0700 Subject: [PATCH 040/133] dispose test disposable --- .../contrib/notebook/test/browser/testNotebookEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 2583d83d58d..b67c8750640 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -430,7 +430,7 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe }; const notebookOptions = !!viewContext ? viewContext.notebookOptions - : new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); + : disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)); const cellList: NotebookCellList = disposables.add(instantiationService.createInstance( NotebookCellList, 'NotebookCellList', From fa99dace5ee3b35a070ca4970422621af07c2781 Mon Sep 17 00:00:00 2001 From: Bryan Ricker <978899+bricker@users.noreply.github.com> Date: Sat, 16 Sep 2023 07:25:45 -0700 Subject: [PATCH 041/133] fixes typo in description for `terminal.integrated.environmentChangesRelaunch` configuration (#191841) --- .../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 cdb90775373..41d07a65710 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -422,7 +422,7 @@ const terminalConfiguration: IConfigurationNode = { default: 'warnonly' }, [TerminalSettingId.EnvironmentChangesRelaunch]: { - markdownDescription: localize('terminal.integrated.environmentChangesRelaunch', "Whether to relaunch terminals automatically if extension want to contribute to their environment and have not been interacted with yet."), + markdownDescription: localize('terminal.integrated.environmentChangesRelaunch', "Whether to relaunch terminals automatically if extensions want to contribute to their environment and have not been interacted with yet."), type: 'boolean', default: true }, From e0d1d4979cd82f7c3a56a2011c7f9ef66169611b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sat, 16 Sep 2023 12:02:29 -0500 Subject: [PATCH 042/133] fix #193225 --- .../browser/terminal.accessibility.contribution.ts | 13 ++++++++++++- .../browser/terminalAccessibleBufferProvider.ts | 9 --------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 29783e7170b..ac7a6c4c912 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -28,6 +28,8 @@ import { TextAreaSyncAddon } from 'vs/workbench/contrib/terminalContrib/accessib import type { Terminal } from 'xterm'; import { Position } from 'vs/editor/common/core/position'; import { ICommandWithEditorLine, TerminalAccessibleBufferProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; class TextAreaSyncContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.textAreaSync'; @@ -64,7 +66,8 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT widgetManager: TerminalWidgetManager, @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ITerminalService private readonly _terminalService: ITerminalService) { + @ITerminalService private readonly _terminalService: ITerminalService, + @IConfigurationService configurationService: IConfigurationService) { super(); this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { if (this._terminalService.activeInstance !== this._instance) { @@ -73,6 +76,14 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT this.show(); return true; }, TerminalContextKeys.focus)); + this._register(_instance.onDidRunText(() => { + const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); + if (focusAfterRun === 'terminal') { + _instance.focus(true); + } else if (focusAfterRun === 'accessible-buffer') { + this.show(); + } + })); } xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { const addon = this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index c4ba54d12a6..3b13dc83dd5 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -9,7 +9,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { IXtermTerminal, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -32,14 +31,6 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { super(); - this.add(_instance.onDidRunText(() => { - const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); - if (focusAfterRun === 'terminal') { - _instance.focus(true); - } else if (focusAfterRun === 'accessible-buffer') { - _accessibleViewService.show(this); - } - })); this.registerListeners(); } From a2ec2e5837e585ffafcc617a0dd6a0b5a3b17806 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sat, 16 Sep 2023 13:02:21 -0500 Subject: [PATCH 043/133] try to get ensurenodisposables to work for task terminal status tests --- .../contrib/tasks/browser/taskTerminalStatus.ts | 12 ++++++------ .../contrib/tasks/common/problemCollectors.ts | 16 +++++++++------- .../test/browser/taskTerminalStatus.test.ts | 10 +++++++++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index b2331ad6bfd..a83ec7b1be1 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -55,18 +55,18 @@ export class TaskTerminalStatus extends Disposable { addTerminal(task: Task, terminal: ITerminalInstance, problemMatcher: AbstractProblemCollector) { const status: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, severity: Severity.Info }; terminal.statusList.add(status); - problemMatcher.onDidFindFirstMatch(() => { + this._register(problemMatcher.onDidFindFirstMatch(() => { this._marker = terminal.registerMarker(); - }); - problemMatcher.onDidFindErrors(() => { + })); + this._register(problemMatcher.onDidFindErrors(() => { if (this._marker) { terminal.addBufferMarker({ marker: this._marker, hoverMessage: nls.localize('task.watchFirstError', "Beginning of detected errors for this run"), disableCommandStorage: true }); } - }); - problemMatcher.onDidRequestInvalidateLastMarker(() => { + })); + this._register(problemMatcher.onDidRequestInvalidateLastMarker(() => { this._marker?.dispose(); this._marker = undefined; - }); + })); this.terminalMap.set(terminal.instanceId, { terminal, task, status, problemMatcher, taskRunEnded: false }); } diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 0087529e25f..00121e6a40c 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -6,7 +6,7 @@ import { IStringDictionary, INumberDictionary } from 'vs/base/common/collections'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/model'; @@ -35,7 +35,7 @@ export interface IProblemMatcher { processLine(line: string): void; } -export abstract class AbstractProblemCollector implements IDisposable { +export abstract class AbstractProblemCollector extends Disposable implements IDisposable { private matchers: INumberDictionary; private activeMatcher: ILineMatcher | null; @@ -68,6 +68,7 @@ export abstract class AbstractProblemCollector implements IDisposable { readonly onDidRequestInvalidateLastMarker = this._onDidRequestInvalidateLastMarker.event; constructor(public readonly problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, protected modelService: IModelService, fileService?: IFileService) { + super(); this.matchers = Object.create(null); this.bufferLength = 1; problemMatchers.map(elem => createLineMatcher(elem, fileService)).forEach((matcher) => { @@ -99,12 +100,12 @@ export abstract class AbstractProblemCollector implements IDisposable { this.resourcesToClean = new Map>(); this.markers = new Map>>(); this.deliveredMarkers = new Map>(); - this.modelService.onModelAdded((model) => { + this._register(this.modelService.onModelAdded((model) => { this.openModels[model.uri.toString()] = true; - }, this, this.modelListeners); - this.modelService.onModelRemoved((model) => { + }, this, this.modelListeners)); + this._register(this.modelService.onModelRemoved((model) => { delete this.openModels[model.uri.toString()]; - }, this, this.modelListeners); + }, this, this.modelListeners)); this.modelService.getModels().forEach(model => this.openModels[model.uri.toString()] = true); this._onDidStateChange = new Emitter(); @@ -127,7 +128,8 @@ export abstract class AbstractProblemCollector implements IDisposable { protected abstract processLineInternal(line: string): Promise; - public dispose() { + public override dispose() { + super.dispose(); this.modelListeners.dispose(); } 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 1e3880420fd..5b9b15423d8 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -5,6 +5,8 @@ import { ok } from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -60,6 +62,7 @@ class TestProblemCollector implements Partial { } suite('Task Terminal Status', () => { + let store: DisposableStore; let instantiationService: TestInstantiationService; let taskService: TestTaskService; let taskTerminalStatus: TaskTerminalStatus; @@ -68,6 +71,7 @@ suite('Task Terminal Status', () => { let problemCollector: AbstractProblemCollector; let audioCueService: TestAudioCueService; setup(() => { + store = new DisposableStore(); instantiationService = new TestInstantiationService(); taskService = new TestTaskService(); audioCueService = new TestAudioCueService(); @@ -75,10 +79,14 @@ suite('Task Terminal Status', () => { testTerminal = instantiationService.createInstance(TestTerminal) as any; testTask = instantiationService.createInstance(TestTask) as unknown as Task; problemCollector = instantiationService.createInstance(TestProblemCollector) as any; + store.add(instantiationService); + store.add(taskTerminalStatus); + store.add(testTerminal); }); teardown(() => { - instantiationService.dispose(); + store.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); test('Should add failed status when there is an exit code on task end', async () => { taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted }); From 13aed778d84070802533a100637b2e0e39b5086b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Sat, 16 Sep 2023 13:07:48 -0500 Subject: [PATCH 044/133] get it to work --- .../contrib/tasks/test/browser/taskTerminalStatus.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 5b9b15423d8..6de24a38863 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -81,10 +81,9 @@ suite('Task Terminal Status', () => { problemCollector = instantiationService.createInstance(TestProblemCollector) as any; store.add(instantiationService); store.add(taskTerminalStatus); - store.add(testTerminal); }); teardown(() => { - store.dispose(); + store.clear(); }); ensureNoDisposablesAreLeakedInTestSuite(); test('Should add failed status when there is an exit code on task end', async () => { From 8d854d628c6469b0c05dfcf1576ddd654d18a0b6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Sun, 17 Sep 2023 15:41:37 -0500 Subject: [PATCH 045/133] Fixes #193313 --- .../terminal/browser/terminalContextMenu.ts | 19 +++++++------------ .../terminal/browser/terminalEditor.ts | 6 ++---- .../terminal/browser/terminalTabbedView.ts | 8 +++----- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index 490ad05ad0f..d59dae7b61a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -10,7 +10,6 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { SingleOrMany } from 'vs/base/common/types'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu } from 'vs/platform/actions/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ISerializedTerminalInstanceContext } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -31,28 +30,24 @@ class InstanceContext { } class TerminalContextActionRunner extends ActionRunner { - constructor( - private readonly _commandService: ICommandService - ) { - super(); - } - override run(action: IAction, context?: InstanceContext): Promise { + override async runAction(action: IAction, context?: InstanceContext): Promise { if (Array.isArray(context) && context.every(e => e instanceof InstanceContext)) { // arg1: The (first) focused instance // arg2: All selected instances - return this._commandService.executeCommand(action.id, context?.[0], context); + await action.run(context?.[0], context); + return; } - return super.run(action, context); + return super.runAction(action, context); } } -export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, commandService: ICommandService, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { +export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { const standardEvent = new StandardMouseEvent(event); const actions: IAction[] = []; - createAndFillInContextMenuActions(menu, undefined, actions); + createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, actions); if (extraActions) { actions.push(...extraActions); @@ -61,7 +56,7 @@ export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMan const context: InstanceContext[] = contextInstances ? asArray(contextInstances).map(e => new InstanceContext(e)) : []; contextMenuService.showContextMenu({ - actionRunner: new TerminalContextActionRunner(commandService), + actionRunner: new TerminalContextActionRunner(), getAnchor: () => standardEvent, getActions: () => actions, getActionsContext: () => context, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 55903a9e497..24e872ca88e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -29,7 +29,6 @@ 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 { ICommandService } from 'vs/platform/commands/common/commands'; export class TerminalEditor extends EditorPane { @@ -56,7 +55,6 @@ export class TerminalEditor extends EditorPane { @IContextKeyService contextKeyService: IContextKeyService, @IMenuService menuService: IMenuService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ICommandService private readonly _commandService: ICommandService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @INotificationService private readonly _notificationService: INotificationService, @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @@ -140,7 +138,7 @@ export class TerminalEditor extends EditorPane { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._commandService, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); return; } @@ -178,7 +176,7 @@ export class TerminalEditor extends EditorPane { else if (!this._cancelContextMenu && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') { if (!this._cancelContextMenu) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._commandService, this._contextMenuService); + openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 14ef8d64f8a..9df0f04db1e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -25,7 +25,6 @@ import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/termin import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getInstanceHoverInfo } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { ICommandService } from 'vs/platform/commands/common/commands'; const $ = dom.$; @@ -74,7 +73,6 @@ export class TerminalTabbedView extends Disposable { @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotificationService private readonly _notificationService: INotificationService, - @ICommandService private readonly _commandService: ICommandService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IMenuService menuService: IMenuService, @@ -356,7 +354,7 @@ export class TerminalTabbedView extends Disposable { else if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, terminal, this._instanceMenu, this._commandService, this._contextMenuService); + openContextMenu(event, terminal, this._instanceMenu, this._contextMenuService); return; } @@ -390,7 +388,7 @@ export class TerminalTabbedView extends Disposable { } terminalContainer.focus(); if (!this._cancelContextMenu) { - openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._commandService, this._contextMenuService); + openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); @@ -415,7 +413,7 @@ export class TerminalTabbedView extends Disposable { selectedInstances.unshift(focusedInstance); } - openContextMenu(event, selectedInstances, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._commandService, this._contextMenuService, emptyList ? this._getTabActions() : undefined); + openContextMenu(event, selectedInstances, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); } event.preventDefault(); event.stopImmediatePropagation(); From f47b1f387e02e632747099af13675d8f23334b13 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 17 Sep 2023 20:07:56 -0700 Subject: [PATCH 046/133] Tweak hiddenWhen message --- src/vs/workbench/contrib/debug/common/debugSchemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 835bb1df7f8..ac868c2e5ca 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -73,7 +73,7 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE default: '' }, hiddenWhen: { - description: nls.localize('vscode.extension.contributes.debuggers.hiddenWhen', "Condition which must be true to hide this type of debugger from the debugger listing but enabled. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension-defined context key as appropriate for this."), + description: nls.localize('vscode.extension.contributes.debuggers.hiddenWhen', "When this condition is true, this debugger type is hidden from the debugger list, but is still enabled."), type: 'string', default: '' }, From ae23e73ba0690ae82614d585e10a88d608ccdda0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 18 Sep 2023 11:46:17 +0200 Subject: [PATCH 047/133] Update CSS grammar (#193333) --- extensions/css/cgmanifest.json | 2 +- extensions/css/syntaxes/css.tmLanguage.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/css/cgmanifest.json b/extensions/css/cgmanifest.json index fe46b5c4acd..0705aca66c4 100644 --- a/extensions/css/cgmanifest.json +++ b/extensions/css/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "microsoft/vscode-css", "repositoryUrl": "https://github.com/microsoft/vscode-css", - "commitHash": "3bd00206f6b0d16eb2eba53fb886462eb8c58baa" + "commitHash": "c216f777497265700ff336f739328e5197e012cd" } }, "licenseDetail": [ diff --git a/extensions/css/syntaxes/css.tmLanguage.json b/extensions/css/syntaxes/css.tmLanguage.json index c44db9bd68f..2096e16e920 100644 --- a/extensions/css/syntaxes/css.tmLanguage.json +++ b/extensions/css/syntaxes/css.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/microsoft/vscode-css/commit/3bd00206f6b0d16eb2eba53fb886462eb8c58baa", + "version": "https://github.com/microsoft/vscode-css/commit/c216f777497265700ff336f739328e5197e012cd", "name": "CSS", "scopeName": "source.css", "patterns": [ @@ -1401,7 +1401,7 @@ "property-keywords": { "patterns": [ { - "match": "(?xi) (? Date: Mon, 18 Sep 2023 12:03:58 +0200 Subject: [PATCH 048/133] Fixes #193194 --- .../diff/defaultLinesDiffComputer/computeMovedLines.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts index 6436ad0d9e0..2d25234c4e5 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts @@ -196,6 +196,9 @@ function computeUnchangedMoves( for (extendToTop = 0; extendToTop < linesAbove; extendToTop++) { const origLine = move.original.startLineNumber - extendToTop - 1; const modLine = move.modified.startLineNumber - extendToTop - 1; + if (origLine > originalLines.length || modLine > modifiedLines.length) { + break; + } if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) { break; } @@ -213,6 +216,9 @@ function computeUnchangedMoves( for (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) { const origLine = move.original.endLineNumberExclusive + extendToBottom; const modLine = move.modified.endLineNumberExclusive + extendToBottom; + if (origLine > originalLines.length || modLine > modifiedLines.length) { + break; + } if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) { break; } From b2017021a52364dc76a11e5349b10ba19d384d61 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 18 Sep 2023 12:23:23 +0200 Subject: [PATCH 049/133] Don't reset extension tree focus if an element already has focus (#193346) Part of #193020 --- 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 62ca965951c..6a197e4ee24 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -950,7 +950,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { if (item) { this.focus(true, item); this.tree.setFocus([item]); - } else { + } else if (this.tree.getFocus().length === 0) { this.tree.setFocus([]); } } From 348695e913e97a34e2c155af2dc5c2ffea9f19b8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 18 Sep 2023 12:41:56 +0200 Subject: [PATCH 050/133] use space-between so that CC debug toolbar doesn't change when files change --- .../browser/parts/titlebar/commandCenterControl.ts | 8 -------- .../browser/parts/titlebar/media/titlebarpart.css | 7 +++---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 1306b617009..2dbe0416b4e 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -7,7 +7,6 @@ import { reset } from 'vs/base/browser/dom'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; 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'; import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; @@ -183,13 +182,6 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { }); toolbar.setActions(group); this._store.add(toolbar); - - // spacer - if (i < groups.length - 1) { - const icon = renderIcon(Codicon.circleSmallFilled); - icon.classList.add('spacer'); - container.appendChild(icon); - } } } diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index b0c29642ec2..44a1a6aa908 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -178,10 +178,9 @@ text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple .spacer { - height: 100%; - padding: 0 6px; - opacity: 0.5; +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { + justify-content: space-between; + padding: 0 12px; } .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:only-child { From 5bf5ea3b427f7b7f7d9975f99f50de4fc7f0514a Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 18 Sep 2023 12:45:36 +0200 Subject: [PATCH 051/133] define icon for "Focus Session" command --- 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 7c720d874d3..1859e2dc31b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -354,7 +354,7 @@ registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, un registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart); registerDebugToolBarItem(STEP_BACK_ID, localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(REVERSE_CONTINUE_ID, localize('reverseContinue', "Reverse"), 55, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, undefined, CONTEXT_MULTI_SESSION_DEBUG); +registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, Codicon.listTree, CONTEXT_MULTI_SESSION_DEBUG); MenuRegistry.appendMenuItem(MenuId.DebugToolBarStop, { group: 'navigation', From d8e69199806fe5409869dad46dfef926a36ff8f8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 18 Sep 2023 13:02:58 +0200 Subject: [PATCH 052/133] Follow enter vs. space focus vs. reveal convention in Comments view (#193352) Follow enter vs. space focus vs. reveal convention Part of #192377 --- .../contrib/comments/browser/commentThreadZoneWidget.ts | 3 +++ .../contrib/comments/browser/commentsController.ts | 8 ++++---- src/vs/workbench/contrib/comments/browser/commentsView.ts | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 74590df5c4c..b9da89e5985 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -195,6 +195,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget scrollTop = this.editor.getTopForLineNumber(this._commentThread.range.startLineNumber) - height / 2 + commentCoords.top - commentThreadCoords.top; } this.editor.setScrollTop(scrollTop); + if (focus) { + this._commentThreadWidget.focus(); + } return; } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index b67f1e94080..82470cfe934 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -600,18 +600,18 @@ export class CommentController implements IEditorContribution { return editor.getContribution(ID); } - public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean): void { + public revealCommentThread(threadId: string, commentUniqueId: number, fetchOnceIfNotExist: boolean, focus: boolean): void { const commentThreadWidget = this._commentWidgets.filter(widget => widget.commentThread.threadId === threadId); if (commentThreadWidget.length === 1) { - commentThreadWidget[0].reveal(commentUniqueId); + commentThreadWidget[0].reveal(commentUniqueId, focus); } else if (fetchOnceIfNotExist) { if (this._computePromise) { this._computePromise.then(_ => { - this.revealCommentThread(threadId, commentUniqueId, false); + this.revealCommentThread(threadId, commentUniqueId, false, focus); }); } else { this.beginCompute().then(_ => { - this.revealCommentThread(threadId, commentUniqueId, false); + this.revealCommentThread(threadId, commentUniqueId, false, focus); }); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 4be02c8c76c..582b6877ec5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -399,7 +399,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; if (threadToReveal && isCodeEditor(editor)) { const controller = CommentController.get(editor); - controller?.revealCommentThread(threadToReveal, commentToReveal, true); + controller?.revealCommentThread(threadToReveal, commentToReveal, true, !preserveFocus); } return true; @@ -421,7 +421,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { const control = editor.getControl(); if (threadToReveal && isCodeEditor(control)) { const controller = CommentController.get(control); - controller?.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true); + controller?.revealCommentThread(threadToReveal, commentToReveal.uniqueIdInThread, true, !preserveFocus); } } }); From 70444eab69d6c74c78c292f2fc45a3a1c7c2d26f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 14 Sep 2023 14:15:46 +0200 Subject: [PATCH 053/133] Fixes diff bug --- src/vs/base/common/arrays.ts | 6 + src/vs/editor/common/core/offsetRange.ts | 6 + .../algorithms/diffAlgorithm.ts | 73 +++++++-- .../heuristicSequenceOptimizations.ts | 38 +++-- .../common/services/editorSimpleWorker.ts | 2 +- .../services/editorSimpleWorker.test.ts | 8 +- .../diffing/fixtures/invalid-diff-bug/1.tst | 94 ++++++++++++ .../diffing/fixtures/invalid-diff-bug/2.tst | 57 +++++++ .../advanced.expected.diff.json | 130 ++++++++++++++++ .../legacy.expected.diff.json | 145 ++++++++++++++++++ 10 files changed, 527 insertions(+), 32 deletions(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/1.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/2.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/legacy.expected.diff.json diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 35b0620e7b2..bd0f90aa878 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -187,6 +187,12 @@ export function forEachAdjacent(arr: T[], f: (item1: T | undefined, item2: T } } +export function forEachWithNeighbors(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void { + for (let i = 0; i < arr.length; i++) { + f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]); + } +} + interface IMutableSplice extends ISplice { readonly toInsert: T[]; deleteCount: number; diff --git a/src/vs/editor/common/core/offsetRange.ts b/src/vs/editor/common/core/offsetRange.ts index 64c29063206..02053528c4c 100644 --- a/src/vs/editor/common/core/offsetRange.ts +++ b/src/vs/editor/common/core/offsetRange.ts @@ -103,6 +103,12 @@ export class OffsetRange { return undefined; } + public intersectsOrTouches(other: OffsetRange): boolean { + const start = Math.max(this.start, other.start); + const end = Math.min(this.endExclusive, other.endExclusive); + return start <= end; + } + public slice(arr: T[]): T[] { return arr.slice(this.start, this.endExclusive); } diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts index 2d095c6209b..95cc0f22054 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm.ts @@ -16,11 +16,11 @@ export interface IDiffAlgorithm { export class DiffAlgorithmResult { static trivial(seq1: ISequence, seq2: ISequence): DiffAlgorithmResult { - return new DiffAlgorithmResult([new SequenceDiff(new OffsetRange(0, seq1.length), new OffsetRange(0, seq2.length))], false); + return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], false); } static trivialTimedOut(seq1: ISequence, seq2: ISequence): DiffAlgorithmResult { - return new DiffAlgorithmResult([new SequenceDiff(new OffsetRange(0, seq1.length), new OffsetRange(0, seq2.length))], true); + return new DiffAlgorithmResult([new SequenceDiff(OffsetRange.ofLength(seq1.length), OffsetRange.ofLength(seq2.length))], true); } constructor( @@ -36,21 +36,22 @@ export class DiffAlgorithmResult { export class SequenceDiff { public static invert(sequenceDiffs: SequenceDiff[], doc1Length: number): SequenceDiff[] { const result: SequenceDiff[] = []; - forEachAdjacent(sequenceDiffs, (a, b) => { - const seq1Start = a ? a.seq1Range.endExclusive : 0; - const seq2Start = a ? a.seq2Range.endExclusive : 0; - const seq1EndEx = b ? b.seq1Range.start : doc1Length; - const seq2EndEx = b ? b.seq2Range.start : (a ? a.seq2Range.endExclusive - a.seq1Range.endExclusive : 0) + doc1Length; - result.push(new SequenceDiff( - new OffsetRange(seq1Start, seq1EndEx), - new OffsetRange(seq2Start, seq2EndEx), + result.push(SequenceDiff.fromOffsetPairs( + a ? a.getEndExclusives() : OffsetPair.zero, + b ? b.getStarts() : new OffsetPair(doc1Length, (a ? a.seq2Range.endExclusive - a.seq1Range.endExclusive : 0) + doc1Length) )); }); - return result; } + public static fromOffsetPairs(start: OffsetPair, endExclusive: OffsetPair): SequenceDiff { + return new SequenceDiff( + new OffsetRange(start.offset1, endExclusive.offset1), + new OffsetRange(start.offset2, endExclusive.offset2), + ); + } + constructor( public readonly seq1Range: OffsetRange, public readonly seq2Range: OffsetRange, @@ -74,6 +75,56 @@ export class SequenceDiff { } return new SequenceDiff(this.seq1Range.delta(offset), this.seq2Range.delta(offset)); } + + public deltaStart(offset: number): SequenceDiff { + if (offset === 0) { + return this; + } + return new SequenceDiff(this.seq1Range.deltaStart(offset), this.seq2Range.deltaStart(offset)); + } + + public deltaEnd(offset: number): SequenceDiff { + if (offset === 0) { + return this; + } + return new SequenceDiff(this.seq1Range.deltaEnd(offset), this.seq2Range.deltaEnd(offset)); + } + + public intersectsOrTouches(other: SequenceDiff): boolean { + return this.seq1Range.intersectsOrTouches(other.seq1Range) || this.seq2Range.intersectsOrTouches(other.seq2Range); + } + + public intersect(other: SequenceDiff): SequenceDiff | undefined { + const i1 = this.seq1Range.intersect(other.seq1Range); + const i2 = this.seq2Range.intersect(other.seq2Range); + if (!i1 || !i2) { + return undefined; + } + return new SequenceDiff(i1, i2); + } + + public getStarts(): OffsetPair { + return new OffsetPair(this.seq1Range.start, this.seq2Range.start); + } + + public getEndExclusives(): OffsetPair { + return new OffsetPair(this.seq1Range.endExclusive, this.seq2Range.endExclusive); + } +} + +export class OffsetPair { + public static readonly zero = new OffsetPair(0, 0); + public static readonly max = new OffsetPair(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + + constructor( + public readonly offset1: number, + public readonly offset2: number, + ) { + } + + public toString(): string { + return `${this.offset1} <-> ${this.offset2}`; + } } export interface ISequence { diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index b288c473874..eb9c36b1668 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { forEachWithNeighbors } from 'vs/base/common/arrays'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; -import { ISequence, SequenceDiff } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm'; +import { ISequence, OffsetPair, SequenceDiff } from 'vs/editor/common/diff/defaultLinesDiffComputer/algorithms/diffAlgorithm'; import { LineSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/lineSequence'; import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence'; @@ -425,28 +426,33 @@ export function removeVeryShortMatchingTextBetweenLongDiffs(sequence1: LinesSlic diffs = result; } while (counter++ < 10 && shouldRepeat); - // Remove short suffixes/prefixes - for (let i = 0; i < diffs.length; i++) { - const cur = diffs[i]; + const newDiffs: SequenceDiff[] = []; - let range1 = cur.seq1Range; - let range2 = cur.seq2Range; + // Remove short suffixes/prefixes + forEachWithNeighbors(diffs, (prev, cur, next) => { + let newDiff = cur; + + function shouldMarkAsChanged(text: string): boolean { + return text.length > 0 && text.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100; + } const fullRange1 = sequence1.extendToFullLines(cur.seq1Range); const prefix = sequence1.getText(new OffsetRange(fullRange1.start, cur.seq1Range.start)); - if (prefix.length > 0 && prefix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 100) { - range1 = cur.seq1Range.deltaStart(-prefix.length); - range2 = cur.seq2Range.deltaStart(-prefix.length); + if (shouldMarkAsChanged(prefix)) { + newDiff = newDiff.deltaStart(-prefix.length); } - const suffix = sequence1.getText(new OffsetRange(cur.seq1Range.endExclusive, fullRange1.endExclusive)); - if (suffix.length > 0 && (suffix.trim().length <= 3 && cur.seq1Range.length + cur.seq2Range.length > 150)) { - range1 = range1.deltaEnd(suffix.length); - range2 = range2.deltaEnd(suffix.length); + if (shouldMarkAsChanged(suffix)) { + newDiff = newDiff.deltaEnd(suffix.length); } - diffs[i] = new SequenceDiff(range1, range2); - } + const availableSpace = SequenceDiff.fromOffsetPairs( + prev ? prev.getEndExclusives() : OffsetPair.zero, + next ? next.getStarts() : OffsetPair.max, + ); + const result = newDiff.intersect(availableSpace)!; + newDiffs.push(result); + }); - return diffs; + return newDiffs; } diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index ef8fb85da14..12b28f16713 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -574,7 +574,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return result; } - public async computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): Promise { + public computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] { const model = this._getModel(modelUrl); if (!model) { return edits; diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 15accef1657..5dc2dd11f16 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -257,7 +257,7 @@ suite('EditorSimpleWorker', () => { ); }); - test.skip('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () { + test('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () { await testEdits(("const API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const req = {};\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === '/books') {\n handler(req, res);\n }\n },\n });\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n author: mockAuthor,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/author/${mockAuthor}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n title: mockTitle,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/title/${mockTitle}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n});\n").split('\n'), [{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 96, endColumn: 1 }, @@ -337,7 +337,7 @@ function applyEdits(text: string, edits: { range: IRange; text: string }[]): str class PositionOffsetTransformer { private readonly lineStartOffsetByLineIdx: number[]; - constructor(text: string) { + constructor(private readonly text: string) { this.lineStartOffsetByLineIdx = []; this.lineStartOffsetByLineIdx.push(0); for (let i = 0; i < text.length; i++) { @@ -349,7 +349,7 @@ class PositionOffsetTransformer { } getOffset(position: Position): number { - const nextLineOffset = this.lineStartOffsetByLineIdx[position.lineNumber]; - return Math.min(this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1, nextLineOffset - 1); + const maxLineOffset = position.lineNumber >= this.lineStartOffsetByLineIdx.length ? this.text.length : (this.lineStartOffsetByLineIdx[position.lineNumber] - 1); + return Math.min(this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1, maxLineOffset); } } diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/1.tst b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/1.tst new file mode 100644 index 00000000000..8a882b0f3d7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/1.tst @@ -0,0 +1,94 @@ +const API = require('../src/api'); + +describe('API', () => { + let api; + let database; + + beforeAll(() => { + database = { + getAllBooks: jest.fn(), + getBooksByAuthor: jest.fn(), + getBooksByTitle: jest.fn(), + }; + api = new API(database); + }); + + describe('GET /books', () => { + it('should return all books', async () => { + const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }]; + database.getAllBooks.mockResolvedValue(mockBooks); + + const req = {}; + const res = { + json: jest.fn(), + }; + + await api.register({ + get: (path, handler) => { + if (path === '/books') { + handler(req, res); + } + }, + }); + + expect(database.getAllBooks).toHaveBeenCalled(); + expect(res.json).toHaveBeenCalledWith(mockBooks); + }); + }); + + describe('GET /books/author/:author', () => { + it('should return books by author', async () => { + const mockAuthor = 'John Doe'; + const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }]; + database.getBooksByAuthor.mockResolvedValue(mockBooks); + + const req = { + params: { + author: mockAuthor, + }, + }; + const res = { + json: jest.fn(), + }; + + await api.register({ + get: (path, handler) => { + if (path === `/books/author/${mockAuthor}`) { + handler(req, res); + } + }, + }); + + expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor); + expect(res.json).toHaveBeenCalledWith(mockBooks); + }); + }); + + describe('GET /books/title/:title', () => { + it('should return books by title', async () => { + const mockTitle = 'Book 1'; + const mockBooks = [{ title: mockTitle, author: 'John Doe' }]; + database.getBooksByTitle.mockResolvedValue(mockBooks); + + const req = { + params: { + title: mockTitle, + }, + }; + const res = { + json: jest.fn(), + }; + + await api.register({ + get: (path, handler) => { + if (path === `/books/title/${mockTitle}`) { + handler(req, res); + } + }, + }); + + expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle); + expect(res.json).toHaveBeenCalledWith(mockBooks); + }); + }); +}); diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/2.tst b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/2.tst new file mode 100644 index 00000000000..b688d2706ed --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/2.tst @@ -0,0 +1,57 @@ +const request = require('supertest'); +const API = require('../src/api'); + +describe('API', () => { + let api; + let database; + + beforeAll(() => { + database = { + getAllBooks: jest.fn(), + getBooksByAuthor: jest.fn(), + getBooksByTitle: jest.fn(), + }; + api = new API(database); + }); + + describe('GET /books', () => { + it('should return all books', async () => { + const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }]; + database.getAllBooks.mockResolvedValue(mockBooks); + + const response = await request(api.app).get('/books'); + + expect(database.getAllBooks).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockBooks); + }); + }); + + describe('GET /books/author/:author', () => { + it('should return books by author', async () => { + const mockAuthor = 'John Doe'; + const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }]; + database.getBooksByAuthor.mockResolvedValue(mockBooks); + + const response = await request(api.app).get(`/books/author/${mockAuthor}`); + + expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockBooks); + }); + }); + + describe('GET /books/title/:title', () => { + it('should return books by title', async () => { + const mockTitle = 'Book 1'; + const mockBooks = [{ title: mockTitle, author: 'John Doe' }]; + database.getBooksByTitle.mockResolvedValue(mockBooks); + + const response = await request(api.app).get(`/books/title/${mockTitle}`); + + expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockBooks); + }); + }); +}); diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json new file mode 100644 index 00000000000..45413b56af7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json @@ -0,0 +1,130 @@ +{ + "original": { + "content": "const API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const req = {};\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === '/books') {\n handler(req, res);\n }\n },\n });\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n author: mockAuthor,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/author/${mockAuthor}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n title: mockTitle,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/title/${mockTitle}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n});\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "const request = require('supertest');\nconst API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get('/books');\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(`/books/author/${mockAuthor}`);\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(`/books/title/${mockTitle}`);\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n});\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[1,1)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,1 -> 1,1]", + "modifiedRange": "[1,1 -> 2,1]" + } + ] + }, + { + "originalRange": "[21,33)", + "modifiedRange": "[22,23)", + "innerChanges": [ + { + "originalRange": "[21,1 -> 22,1]", + "modifiedRange": "[22,1 -> 22,1]" + }, + { + "originalRange": "[22,13 -> 26,6]", + "modifiedRange": "[22,13 -> 22,23]" + }, + { + "originalRange": "[26,13 -> 26,13]", + "modifiedRange": "[22,30 -> 22,38]" + }, + { + "originalRange": "[26,17 -> 27,9]", + "modifiedRange": "[22,42 -> 22,47]" + }, + { + "originalRange": "[27,1 -> 32,9]", + "modifiedRange": "[22,39 -> 22,60]" + } + ] + }, + { + "originalRange": "[35,36)", + "modifiedRange": "[25,27)", + "innerChanges": [ + { + "originalRange": "[35,14 -> 35,44]", + "modifiedRange": "[25,14 -> 26,36]" + } + ] + }, + { + "originalRange": "[45,61)", + "modifiedRange": "[36,37)", + "innerChanges": [ + { + "originalRange": "[45,1 -> 54,6]", + "modifiedRange": "[36,1 -> 36,23]" + }, + { + "originalRange": "[54,13 -> 54,13]", + "modifiedRange": "[36,30 -> 36,38]" + }, + { + "originalRange": "[54,17 -> 55,9]", + "modifiedRange": "[36,42 -> 36,47]" + }, + { + "originalRange": "[55,12 -> 56,24]", + "modifiedRange": "[36,50 -> 36,51]" + }, + { + "originalRange": "[56,54 -> 60,9]", + "modifiedRange": "[36,81 -> 36,81]" + } + ] + }, + { + "originalRange": "[63,64)", + "modifiedRange": "[39,41)", + "innerChanges": [ + { + "originalRange": "[63,14 -> 63,44]", + "modifiedRange": "[39,14 -> 40,36]" + } + ] + }, + { + "originalRange": "[73,89)", + "modifiedRange": "[50,51)", + "innerChanges": [ + { + "originalRange": "[73,1 -> 82,6]", + "modifiedRange": "[50,1 -> 50,23]" + }, + { + "originalRange": "[82,13 -> 82,13]", + "modifiedRange": "[50,30 -> 50,38]" + }, + { + "originalRange": "[82,17 -> 83,9]", + "modifiedRange": "[50,42 -> 50,47]" + }, + { + "originalRange": "[83,12 -> 84,24]", + "modifiedRange": "[50,50 -> 50,51]" + }, + { + "originalRange": "[84,52 -> 88,9]", + "modifiedRange": "[50,79 -> 50,79]" + } + ] + }, + { + "originalRange": "[91,92)", + "modifiedRange": "[53,55)", + "innerChanges": [ + { + "originalRange": "[91,14 -> 91,44]", + "modifiedRange": "[53,14 -> 54,36]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/legacy.expected.diff.json new file mode 100644 index 00000000000..9a4b104d1f2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/legacy.expected.diff.json @@ -0,0 +1,145 @@ +{ + "original": { + "content": "const API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const req = {};\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === '/books') {\n handler(req, res);\n }\n },\n });\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n author: mockAuthor,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/author/${mockAuthor}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n title: mockTitle,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/title/${mockTitle}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n});\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "const request = require('supertest');\nconst API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get('/books');\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(`/books/author/${mockAuthor}`);\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(`/books/title/${mockTitle}`);\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n});\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[1,1)", + "modifiedRange": "[1,2)", + "innerChanges": null + }, + { + "originalRange": "[21,33)", + "modifiedRange": "[22,23)", + "innerChanges": [ + { + "originalRange": "[21,15 -> 22,8]", + "modifiedRange": "[22,15 -> 22,17]" + }, + { + "originalRange": "[22,11 -> 22,16]", + "modifiedRange": "[22,20 -> 22,21]" + }, + { + "originalRange": "[22,19 -> 26,7]", + "modifiedRange": "[22,24 -> 22,24]" + }, + { + "originalRange": "[26,13 -> 27,9]", + "modifiedRange": "[22,30 -> 22,47]" + }, + { + "originalRange": "[27,12 -> 28,24]", + "modifiedRange": "[22,50 -> 22,51]" + }, + { + "originalRange": "[28,33 -> 32,9]", + "modifiedRange": "[22,60 -> 22,60]" + } + ] + }, + { + "originalRange": "[35,36)", + "modifiedRange": "[25,27)", + "innerChanges": [ + { + "originalRange": "[35,17 -> 35,22]", + "modifiedRange": "[25,17 -> 25,29]" + }, + { + "originalRange": "[35,26 -> 35,44]", + "modifiedRange": "[25,33 -> 26,36]" + } + ] + }, + { + "originalRange": "[45,61)", + "modifiedRange": "[36,37)", + "innerChanges": [ + { + "originalRange": "[45,15 -> 50,8]", + "modifiedRange": "[36,15 -> 36,17]" + }, + { + "originalRange": "[50,11 -> 50,16]", + "modifiedRange": "[36,20 -> 36,21]" + }, + { + "originalRange": "[50,19 -> 54,7]", + "modifiedRange": "[36,24 -> 36,24]" + }, + { + "originalRange": "[54,13 -> 56,24]", + "modifiedRange": "[36,30 -> 36,51]" + }, + { + "originalRange": "[56,54 -> 60,9]", + "modifiedRange": "[36,81 -> 36,81]" + } + ] + }, + { + "originalRange": "[63,64)", + "modifiedRange": "[39,41)", + "innerChanges": [ + { + "originalRange": "[63,17 -> 63,22]", + "modifiedRange": "[39,17 -> 39,29]" + }, + { + "originalRange": "[63,26 -> 63,44]", + "modifiedRange": "[39,33 -> 40,36]" + } + ] + }, + { + "originalRange": "[73,89)", + "modifiedRange": "[50,51)", + "innerChanges": [ + { + "originalRange": "[73,15 -> 73,16]", + "modifiedRange": "[50,15 -> 50,21]" + }, + { + "originalRange": "[73,19 -> 78,12]", + "modifiedRange": "[50,24 -> 50,29]" + }, + { + "originalRange": "[78,15 -> 79,16]", + "modifiedRange": "[50,32 -> 50,34]" + }, + { + "originalRange": "[79,19 -> 83,9]", + "modifiedRange": "[50,37 -> 50,47]" + }, + { + "originalRange": "[83,12 -> 84,24]", + "modifiedRange": "[50,50 -> 50,51]" + }, + { + "originalRange": "[84,52 -> 88,9]", + "modifiedRange": "[50,79 -> 50,79]" + } + ] + }, + { + "originalRange": "[91,92)", + "modifiedRange": "[53,55)", + "innerChanges": [ + { + "originalRange": "[91,17 -> 91,22]", + "modifiedRange": "[53,17 -> 53,29]" + }, + { + "originalRange": "[91,26 -> 91,44]", + "modifiedRange": "[53,33 -> 54,36]" + } + ] + } + ] +} \ No newline at end of file From 10765021158cf51eaf6785361598e1668f53e06e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 18 Sep 2023 15:45:18 +0200 Subject: [PATCH 054/133] Fixes test --- .../fixtures/invalid-diff-bug/advanced.expected.diff.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json index 45413b56af7..8a805f72ae1 100644 --- a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json @@ -39,8 +39,8 @@ "modifiedRange": "[22,42 -> 22,47]" }, { - "originalRange": "[27,1 -> 32,9]", - "modifiedRange": "[22,39 -> 22,60]" + "originalRange": "[27,9 -> 33,1]", + "modifiedRange": "[22,47 -> 23,1]" } ] }, From dc692705515ba0b5854e1cf5e0d9eccd09aed605 Mon Sep 17 00:00:00 2001 From: KhubaibAlam <74249716+khubaibalam2000@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:47:21 +0500 Subject: [PATCH 055/133] Added hyperlink to Hot Exit for more details (#193354) * Added hyperlink to Hot Exit for more details * added markdown description * added forward link in hot exit --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index f6de1a29bc4..0a6f9369ba4 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -130,7 +130,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ? nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`'), nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`') ], - 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) + 'markdownDescription': nls.localize('hotExit', "[Hot Exit](https://aka.ms/vscode-hot-exit) controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) } : { 'type': 'string', 'scope': ConfigurationScope.APPLICATION, @@ -140,7 +140,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ? nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with editors that have unsaved changes.'), nls.localize('hotExit.onExitAndWindowCloseBrowser', 'Hot exit will be triggered when the browser quits or the window or tab is closed.') ], - 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) + 'markdownDescription': nls.localize('hotExit', "[Hot Exit](https://aka.ms/vscode-hot-exit) controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) }; configurationRegistry.registerConfiguration({ From db563b0b8675d822b19ef0ab821064604d152929 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Sep 2023 08:45:02 -0700 Subject: [PATCH 056/133] Add placeholder for rename and change color term tab Fixes #193377 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3823ed3e0ad..797498b8dca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2177,7 +2177,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { items.push({ label: `$(${icon.id})`, description: `${icon.id}`, icon }); } const result = await this._quickInputService.pick(items, { - matchOnDescription: true + matchOnDescription: true, + placeHolder: nls.localize('changeIcon', 'Select an icon for the terminal') }); if (result) { this._icon = result.icon; @@ -2208,6 +2209,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const quickPick = this._quickInputService.createQuickPick(); quickPick.items = items; quickPick.matchOnDescription = true; + quickPick.placeholder = nls.localize('changeColor', 'Select a color for the terminal'); quickPick.show(); const disposables: IDisposable[] = []; const result = await new Promise(r => { From 63fa4bccfb8f39631f049da482bbe91ec81228d3 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 18 Sep 2023 18:01:25 +0200 Subject: [PATCH 057/133] Finalize `LanguageConfiguration.autoClosingPairs` (#193197) Finalize LanguageConfiguration.autoClosingPairs --- .../workbench/api/common/extHost.api.impl.ts | 1 + .../api/common/extHostLanguageFeatures.ts | 23 +++++++--- src/vs/workbench/api/common/extHostTypes.ts | 18 ++++++++ .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 44 +++++++++++++++++++ ...languageConfigurationAutoClosingPairs.d.ts | 17 ------- 6 files changed, 79 insertions(+), 25 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.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 eaa75c565f9..e2a4841815e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1509,6 +1509,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TextEditorLineNumbersStyle: extHostTypes.TextEditorLineNumbersStyle, TextEditorRevealType: extHostTypes.TextEditorRevealType, TextEditorSelectionChangeKind: extHostTypes.TextEditorSelectionChangeKind, + SyntaxTokenType: extHostTypes.SyntaxTokenType, TextDocumentChangeReason: extHostTypes.TextDocumentChangeReason, ThemeColor: extHostTypes.ThemeColor, ThemeIcon: extHostTypes.ThemeIcon, diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index ec2fbcebeef..f80c0f8d6b3 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; 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, CodeActionTriggerKind } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKind, InternalDataTransferItem, CodeActionTriggerKind, 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'; @@ -33,9 +33,10 @@ 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 { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import { localize } from 'vs/nls'; +import { IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; // --- adapter @@ -2564,6 +2565,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); } + private static _serializeAutoClosingPair(autoClosingPair: vscode.AutoClosingPair): IAutoClosingPairConditional { + return { + open: autoClosingPair.open, + close: autoClosingPair.close, + notIn: autoClosingPair.notIn ? autoClosingPair.notIn.map(v => SyntaxTokenType.toString(v)) : undefined, + }; + } + + private static _serializeAutoClosingPairs(autoClosingPairs: vscode.AutoClosingPair[]): IAutoClosingPairConditional[] { + return autoClosingPairs.map(ExtHostLanguageFeatures._serializeAutoClosingPair); + } + setLanguageConfiguration(extension: IExtensionDescription, languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { const { wordPattern } = configuration; @@ -2589,10 +2602,6 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF `Do not use.`); } - if (configuration.autoClosingPairs) { - checkProposedApiEnabled(extension, 'languageConfigurationAutoClosingPairs'); - } - const handle = this._nextHandle(); const serializedConfiguration: extHostProtocol.ILanguageConfigurationDto = { comments: configuration.comments, @@ -2602,7 +2611,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF onEnterRules: configuration.onEnterRules ? ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules) : undefined, __electricCharacterSupport: configuration.__electricCharacterSupport, __characterPairSupport: configuration.__characterPairSupport, - autoClosingPairs: configuration.autoClosingPairs + autoClosingPairs: configuration.autoClosingPairs ? ExtHostLanguageFeatures._serializeAutoClosingPairs(configuration.autoClosingPairs) : undefined, }; this._proxy.$setLanguageConfiguration(handle, languageId, serializedConfiguration); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0f4dcb21281..b1ea9b94055 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1872,6 +1872,24 @@ export namespace TextEditorSelectionChangeKind { } } +export enum SyntaxTokenType { + Other = 0, + Comment = 1, + String = 2, + RegEx = 3 +} +export namespace SyntaxTokenType { + export function toString(v: SyntaxTokenType | unknown): 'other' | 'comment' | 'string' | 'regex' { + switch (v) { + case SyntaxTokenType.Other: return 'other'; + case SyntaxTokenType.Comment: return 'comment'; + case SyntaxTokenType.String: return 'string'; + case SyntaxTokenType.RegEx: return 'regex'; + } + return 'other'; + } +} + @es5ClassCompat export class DocumentLink { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index e57cd2a81b6..f10d182e0d5 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -55,7 +55,6 @@ export const allApiProposals = Object.freeze({ interactiveUserActions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveUserActions.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', - languageConfigurationAutoClosingPairs: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts', mappedEditsProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.mappedEditsProvider.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', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index bb5573aac0e..53e139a1671 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -6216,6 +6216,46 @@ declare module 'vscode' { action: EnterAction; } + /** + * Enumeration of commonly encountered syntax token types. + */ + export enum SyntaxTokenType { + /** + * Everything except tokens that are part of comments, string literals and regular expressions. + */ + Other = 0, + /** + * A comment. + */ + Comment = 1, + /** + * A string literal. + */ + String = 2, + /** + * A regular expression. + */ + RegEx = 3 + } + + /** + * Describes pairs of strings where the close string will be automatically inserted when typing the opening string. + */ + export interface AutoClosingPair { + /** + * The string that will trigger the automatic insertion of the closing string. + */ + open: string; + /** + * The closing string that will be automatically inserted when typing the opening string. + */ + close: string; + /** + * A set of tokens where the pair should not be auto closed. + */ + notIn?: SyntaxTokenType[]; + } + /** * The language configuration interfaces defines the contract between extensions * and various editor features, like automatic bracket insertion, automatic indentation etc. @@ -6246,6 +6286,10 @@ declare module 'vscode' { * The language's rules to be evaluated when pressing Enter. */ onEnterRules?: OnEnterRule[]; + /** + * The language's auto closing pairs. + */ + autoClosingPairs?: AutoClosingPair[]; /** * **Deprecated** Do not use. diff --git a/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts b/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts deleted file mode 100644 index 3cea4fdf090..00000000000 --- a/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts +++ /dev/null @@ -1,17 +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' { - - // https://github.com/microsoft/vscode/issues/173738 @alexdima - - export interface LanguageConfiguration { - autoClosingPairs?: { - open: string; - close: string; - notIn?: string[]; - }[]; - } -} From facfc8601c7f15c66a7a3a5032c84070e344a1de Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 09:04:12 -0700 Subject: [PATCH 058/133] fix #187859 --- .../contrib/accessibility/browser/accessibilityContributions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 90544aabea6..11bf6795196 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -96,7 +96,7 @@ class AccessibilityHelpProvider implements IAccessibleContentProvider { } } - if (options.get(EditorOption.stickyScroll)) { + if (options.get(EditorOption.stickyScroll).enabled) { content.push(this._descriptionForCommand('editor.action.focusStickyScroll', AccessibilityHelpNLS.stickScrollKb, AccessibilityHelpNLS.stickScrollNoKb)); } From b7ba646a9d1e25fe7ea97ddec7a85fd5d82728c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:19:56 -0700 Subject: [PATCH 059/133] Fix createTerminal embedder API Fixes microsoft/vscode-internalbacklog#4557 --- src/vs/workbench/browser/web.api.ts | 2 +- src/vs/workbench/browser/web.factory.ts | 6 ++++++ src/vs/workbench/browser/web.main.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 1d703966a89..642bfeb1004 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -101,7 +101,7 @@ export interface IWorkbench { * @param options The definition of the terminal, this is similar to * `ExtensionTerminalOptions` in the extension API. */ - createTerminal(options: IEmbedderTerminalOptions): void; + createTerminal(options: IEmbedderTerminalOptions): Promise; }; workspace: { diff --git a/src/vs/workbench/browser/web.factory.ts b/src/vs/workbench/browser/web.factory.ts index a39b1f28c33..5fab020f5de 100644 --- a/src/vs/workbench/browser/web.factory.ts +++ b/src/vs/workbench/browser/web.factory.ts @@ -14,6 +14,7 @@ import { DeferredPromise } from 'vs/base/common/async'; import { asArray } from 'vs/base/common/arrays'; import { IProgress, IProgressCompositeOptions, IProgressDialogOptions, IProgressNotificationOptions, IProgressOptions, IProgressStep, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { LogLevel } from 'vs/platform/log/common/log'; +import { IEmbedderTerminalOptions } from 'vs/workbench/services/terminal/common/embedderTerminalService'; let created = false; const workbenchPromise = new DeferredPromise(); @@ -144,6 +145,11 @@ export namespace window { return workbench.window.withProgress(options, task); } + + export async function createTerminal(options: IEmbedderTerminalOptions): Promise { + const workbench = await workbenchPromise.p; + workbench.window.createTerminal(options); + } } export namespace workspace { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 580e3ac1026..03a5a2f4d7e 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -187,7 +187,7 @@ export class BrowserMain extends Disposable { }, window: { withProgress: (options, task) => progressService.withProgress(options, task), - createTerminal: (options) => embedderTerminalService.createTerminal(options), + createTerminal: async (options) => embedderTerminalService.createTerminal(options), }, workspace: { openTunnel: async tunnelOptions => { From 613640d25feb7b9bdd8c6d056a7d5c7acfa870f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:04:34 -0700 Subject: [PATCH 060/133] Fix protected minification error Fixes #193390 --- .../workbench/contrib/terminal/browser/terminalContextMenu.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index d59dae7b61a..4c36da7dd25 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -31,7 +31,8 @@ class InstanceContext { class TerminalContextActionRunner extends ActionRunner { - override async runAction(action: IAction, context?: InstanceContext): Promise { + // eslint-disable-next-line @typescript-eslint/naming-convention + protected override async runAction(action: IAction, context?: InstanceContext): Promise { if (Array.isArray(context) && context.every(e => e instanceof InstanceContext)) { // arg1: The (first) focused instance // arg2: All selected instances From 6f5d3529923bf39591060fcccd23924c8302103c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 10:21:21 -0700 Subject: [PATCH 061/133] use closest instead of big parent chain --- .../contrib/terminalContrib/quickFix/browser/quickFixAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 365dd9f00d1..9d318850e75 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -283,7 +283,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, updateLayout(this._configurationService, e); this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); - const parentElement = e.parentElement?.parentElement?.parentElement?.parentElement; + const parentElement = e.closest('.xterm.terminal.focus') as HTMLElement; if (!parentElement) { return; } From 3197655a6089ad6300ba29b540cc8c92471103e0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 10:26:25 -0700 Subject: [PATCH 062/133] fix css --- .../terminal/browser/media/terminal.css | 13 ------------- .../terminal/browser/terminal.contribution.ts | 1 + .../browser/media/terminalQuickFix.css | 19 +++++++++++++++++++ 3 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index c32f1c421b0..a11d5cc80b7 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -285,10 +285,6 @@ border-style: solid; } -.xterm-screen .xterm-decoration-container .xterm-decoration.quick-fix { - z-index: 7; -} - .monaco-workbench .part.sidebar > .title > .title-actions .switch-terminal { display: flex; align-items: center; @@ -484,15 +480,6 @@ pointer-events: none; color: var(--vscode-terminalCommandDecoration-defaultBackground); } -.monaco-workbench .terminal .terminal-command-decoration.quick-fix { - color: var(--vscode-editorLightBulb-foreground) !important; - background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); -} -.monaco-workbench .terminal .terminal-command-decoration.quick-fix.explainOnly { - /* Use success background to blend in with the terminal better as it's lower priority. We will - * probably want to add an explicit color for this eventually. */ - color: var(--vscode-terminalCommandDecoration-successBackground) !important; -} .terminal-scroll-highlight { left: 0; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 1bb341555ef..b45c61f9218 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -8,6 +8,7 @@ import 'vs/css!./media/scrollbar'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import 'vs/css!./media/terminal'; +import 'vs/css!./media/terminalQuickFix'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css new file mode 100644 index 00000000000..11a54028fe5 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.xterm-screen .xterm-decoration-container .xterm-decoration.quick-fix { + z-index: 7; +} + +.monaco-workbench .terminal .terminal-command-decoration.quick-fix { + color: var(--vscode-editorLightBulb-foreground) !important; + background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); +} + +.monaco-workbench .terminal .terminal-command-decoration.quick-fix.explainOnly { + /* Use success background to blend in with the terminal better as it's lower priority. We will + * probably want to add an explicit color for this eventually. */ + color: var(--vscode-terminalCommandDecoration-successBackground) !important; +} From f30f7d80cc2609b868b74b2d811a2c554366f029 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 10:35:10 -0700 Subject: [PATCH 063/133] move css to where it works --- .../quickFix => terminal}/browser/media/terminalQuickFix.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/workbench/contrib/{terminalContrib/quickFix => terminal}/browser/media/terminalQuickFix.css (100%) diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css b/src/vs/workbench/contrib/terminal/browser/media/terminalQuickFix.css similarity index 100% rename from src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css rename to src/vs/workbench/contrib/terminal/browser/media/terminalQuickFix.css From 5be77a3d8b293f24934585676f8bed42b010dca5 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 18 Sep 2023 10:42:57 -0700 Subject: [PATCH 064/133] Notebooks respect `files.xyz` settings (#192941) * notebook support trim newline and whitespace * `files.insertFinalNewLine` suupport * perf fixes * readability + small cleaning bits --- .../saveParticipants/saveParticipants.ts | 231 +++++++++++++++++- 1 file changed, 219 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index 89989e87bfa..e4d9224bfe3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -6,33 +6,40 @@ import { localize } from 'vs/nls'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { isEqual } from 'vs/base/common/resources'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CodeActionProvider, CodeActionTriggerType, IWorkspaceTextEdit } from 'vs/editor/common/languages'; +import { IReadonlyTextBuffer, ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ApplyCodeActionReason, applyCodeAction, getCodeActions } from 'vs/editor/contrib/codeAction/browser/codeAction'; +import { CodeActionKind, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; import { getDocumentFormattingEditsUntilResult } from 'vs/editor/contrib/format/browser/format'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; 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 { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { SaveReason } from 'vs/workbench/common/editor'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookFileWorkingCopyModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ITextModel } from 'vs/editor/common/model'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { CodeActionKind, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; -import { CodeActionTriggerType, CodeActionProvider, IWorkspaceTextEdit } from 'vs/editor/common/languages'; -import { applyCodeAction, ApplyCodeActionReason, getCodeActions } from 'vs/editor/contrib/codeAction/browser/codeAction'; -import { isEqual } from 'vs/base/common/resources'; const NotebookCodeAction = new CodeActionKind('notebook'); - class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { constructor( @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @@ -86,7 +93,199 @@ class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { return []; })); - await this.bulkEditService.apply(/* edit */allCellEdits.flat(), { label: localize('label', "Format Notebook"), code: 'undoredo.formatNotebook', }); + await this.bulkEditService.apply(/* edit */allCellEdits.flat(), { label: localize('formatNotebook', "Format Notebook"), code: 'undoredo.formatNotebook', }); + + } finally { + progress.report({ increment: 100 }); + disposable.dispose(); + } + } +} + +class TrimWhitespaceParticipant implements IStoredFileWorkingCopySaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @ITextModelService private readonly textModelService: ITextModelService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + ) { } + + async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + if (this.configurationService.getValue('files.trimTrailingWhitespace')) { + await this.doTrimTrailingWhitespace(workingCopy, context.reason === SaveReason.AUTO, progress); + } + } + + private async doTrimTrailingWhitespace(workingCopy: IStoredFileWorkingCopy, isAutoSaved: boolean, progress: IProgress) { + if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { + return; + } + + const disposable = new DisposableStore(); + const notebook = workingCopy.model.notebookModel; + const activeCellEditor = getActiveCellCodeEditor(this.editorService); + + let cursors: Position[] = []; + let prevSelection: Selection[] = []; + try { + const allCellEdits = await Promise.all(notebook.cells.map(async (cell) => { + if (cell.cellKind !== CellKind.Code) { + return []; + } + + const ref = await this.textModelService.createModelReference(cell.uri); + disposable.add(ref); + const model = ref.object.textEditorModel; + + const isActiveCell = (activeCellEditor && cell.uri.toString() === activeCellEditor.getModel()?.uri.toString()); + if (isActiveCell) { + prevSelection = activeCellEditor.getSelections() ?? []; + if (isAutoSaved) { + cursors = prevSelection.map(s => s.getPosition()); // get initial cursor positions + const snippetsRange = SnippetController2.get(activeCellEditor)?.getSessionEnclosingRange(); + if (snippetsRange) { + for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) { + cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber))); + } + } + } + } + + const ops = trimTrailingWhitespace(model, cursors); + if (!ops.length) { + return []; // Nothing to do + } + + return ops.map(op => new ResourceTextEdit(model.uri, { ...op, text: op.text || '' }, model.getVersionId())); + })); + + const filteredEdits = allCellEdits.flat().filter(edit => edit !== undefined) as ResourceEdit[]; + await this.bulkEditService.apply(filteredEdits, { label: localize('trimNotebookWhitespace', "Notebook Trim Trailing Whitespace"), code: 'undoredo.notebookTrimTrailingWhitespace' }); + + } finally { + progress.report({ increment: 100 }); + disposable.dispose(); + } + } +} + +class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + ) { } + + async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + if (this.configurationService.getValue('files.trimTrailingWhitespace')) { + this.doTrimFinalNewLines(workingCopy, context.reason === SaveReason.AUTO, progress); + } + } + + /** + * returns 0 if the entire file is empty + */ + private findLastNonEmptyLine(textBuffer: IReadonlyTextBuffer): number { + for (let lineNumber = textBuffer.getLineCount(); lineNumber >= 1; lineNumber--) { + const lineLength = textBuffer.getLineLength(lineNumber); + if (lineLength) { + // this line has content + return lineNumber; + } + } + // no line has content + return 0; + } + + private async doTrimFinalNewLines(workingCopy: IStoredFileWorkingCopy, isAutoSaved: boolean, progress: IProgress): Promise { + if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { + return; + } + + const disposable = new DisposableStore(); + const notebook = workingCopy.model.notebookModel; + const activeCellEditor = getActiveCellCodeEditor(this.editorService); + + try { + const allCellEdits = await Promise.all(notebook.cells.map(async (cell) => { + if (cell.cellKind !== CellKind.Code) { + return; + } + + // autosave -- don't trim every trailing line, just up to the cursor line + let cannotTouchLineNumber = 0; + const isActiveCell = (activeCellEditor && cell.uri.toString() === activeCellEditor.getModel()?.uri.toString()); + if (isAutoSaved && isActiveCell) { + const selections = activeCellEditor.getSelections() ?? []; + for (const sel of selections) { + cannotTouchLineNumber = Math.max(cannotTouchLineNumber, sel.selectionStartLineNumber); + } + } + + const textBuffer = cell.textBuffer; + const lastNonEmptyLine = this.findLastNonEmptyLine(textBuffer); + const deleteFromLineNumber = Math.max(lastNonEmptyLine + 1, cannotTouchLineNumber + 1); + const deletionRange = new Range(deleteFromLineNumber, 1, textBuffer.getLineCount(), textBuffer.getLineLastNonWhitespaceColumn(textBuffer.getLineCount())); + + if (deletionRange.isEmpty()) { + return; + } + + // create the edit to delete all lines in deletionRange + return new ResourceTextEdit(cell.uri, { range: deletionRange, text: '' }, cell.textModel?.getVersionId()); + })); + + const filteredEdits = allCellEdits.flat().filter(edit => edit !== undefined) as ResourceEdit[]; + await this.bulkEditService.apply(filteredEdits, { label: localize('trimNotebookNewlines', "Trim Final New Lines"), code: 'undoredo.trimFinalNewLines' }); + + } finally { + progress.report({ increment: 100 }); + disposable.dispose(); + } + } +} + +class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + ) { } + + async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { + if (this.configurationService.getValue('files.insertFinalNewline')) { + this.doInsertFinalNewLine(workingCopy, context, progress); + } + } + + private async doInsertFinalNewLine(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress): Promise { + if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { + return; + } + + const disposable = new DisposableStore(); + const notebook = workingCopy.model.notebookModel; + + try { + const allCellEdits = await Promise.all(notebook.cells.map(async (cell) => { + if (cell.cellKind !== CellKind.Code) { + return; + } + + const lineCount = cell.textBuffer.getLineCount(); + const lastLineIsEmptyOrWhitespace = cell.textBuffer.getLineFirstNonWhitespaceColumn(lineCount) === 0; + + if (!lineCount || lastLineIsEmptyOrWhitespace) { + return; + } + + return new ResourceTextEdit(cell.uri, { range: new Range(lineCount + 1, cell.textBuffer.getLineLength(lineCount), lineCount + 1, cell.textBuffer.getLineLength(lineCount)), text: cell.textBuffer.getEOL() }, cell.textModel?.getVersionId()); + })); + + const filteredEdits = allCellEdits.filter(edit => edit !== undefined) as ResourceEdit[]; + await this.bulkEditService.apply(filteredEdits, { label: localize('insertFinalNewLine', "Insert Final New Line"), code: 'undoredo.insertFinalNewLine' }); } finally { progress.report({ increment: 100 }); @@ -287,7 +486,12 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa } } - +function getActiveCellCodeEditor(editorService: IEditorService): ICodeEditor | undefined { + const activePane = editorService.activeEditorPane; + const notebookEditor = getNotebookEditorFromEditorPane(activePane); + const activeCodeEditor = notebookEditor?.activeCodeEditor; + return activeCodeEditor; +} export class SaveParticipantsContribution extends Disposable implements IWorkbenchContribution { constructor( @@ -299,8 +503,11 @@ export class SaveParticipantsContribution extends Disposable implements IWorkben } private registerSaveParticipants(): void { + this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(TrimWhitespaceParticipant))); this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(CodeActionOnSaveParticipant))); this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant))); + this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant))); + this._register(this.workingCopyFileService.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant))); } } From 027d141bdf8db942a1f07ae76e04ca518ff227f3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 10:47:58 -0700 Subject: [PATCH 065/133] less specific selector --- .../contrib/terminalContrib/quickFix/browser/quickFixAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 9d318850e75..697f6db5009 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -283,7 +283,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, updateLayout(this._configurationService, e); this._audioCueService.playAudioCue(AudioCue.terminalQuickFix); - const parentElement = e.closest('.xterm.terminal.focus') as HTMLElement; + const parentElement = e.closest('.xterm') as HTMLElement; if (!parentElement) { return; } From e6b8e0352eb87f656c0bda93c3ebe066a49aad61 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 18 Sep 2023 11:07:54 -0700 Subject: [PATCH 066/133] Notebook CodeActionKind Support (#192248) * shift nb codeaction filter to editor layer * readability * revised API, clearer typings, filter out autosave triggers * quick lil rename bc i kept confusing myself * revert back to boolean setting. wait for editor enum --- .../contrib/codeAction/browser/codeAction.ts | 10 +++- .../editor/contrib/codeAction/common/types.ts | 1 + .../workbench/api/common/extHost.api.impl.ts | 1 - .../api/common/extHostLanguageFeatures.ts | 7 +-- src/vs/workbench/api/common/extHostTypes.ts | 10 ---- .../saveParticipants/saveParticipants.ts | 57 +++++++++---------- .../notebook/browser/notebook.contribution.ts | 8 ++- 7 files changed, 44 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 9ce2ced9889..9a203b85257 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { coalesce, equals, isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { illegalArgument, isCancellationError, onUnexpectedExternalError } from 'vs/base/common/errors'; @@ -18,7 +19,6 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelService } from 'vs/editor/common/services/model'; import { TextModelCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; -import * as nls from 'vs/nls'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -89,6 +89,10 @@ export async function getCodeActions( token: CancellationToken, ): Promise { const filter = trigger.filter || {}; + const notebookFilter: CodeActionFilter = { + ...filter, + excludes: [...(filter.excludes || []), CodeActionKind.Notebook], + }; const codeActionContext: languages.CodeActionContext = { only: filter.include?.value, @@ -96,7 +100,9 @@ export async function getCodeActions( }; const cts = new TextModelCancellationTokenSource(model, token); - const providers = getCodeActionProviders(registry, model, filter); + // if the trigger is auto (autosave, lightbulb, etc), we should exclude notebook codeActions + const excludeNotebookCodeActions = (trigger.type === languages.CodeActionTriggerType.Auto); + const providers = getCodeActionProviders(registry, model, (excludeNotebookCodeActions) ? notebookFilter : filter); const disposables = new DisposableStore(); const promises = providers.map(async provider => { diff --git a/src/vs/editor/contrib/codeAction/common/types.ts b/src/vs/editor/contrib/codeAction/common/types.ts index e1e8d835765..123b4c24c3d 100644 --- a/src/vs/editor/contrib/codeAction/common/types.ts +++ b/src/vs/editor/contrib/codeAction/common/types.ts @@ -20,6 +20,7 @@ export class CodeActionKind { public static readonly RefactorInline = CodeActionKind.Refactor.append('inline'); public static readonly RefactorMove = CodeActionKind.Refactor.append('move'); public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite'); + public static readonly Notebook = new CodeActionKind('notebook'); public static readonly Source = new CodeActionKind('source'); public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e2a4841815e..948c269fa21 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1585,7 +1585,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, StackFrameFocus: extHostTypes.StackFrameFocus, ThreadFocus: extHostTypes.ThreadFocus, - NotebookCodeActionKind: extHostTypes.NotebookCodeActionKind, RelatedInformationType: extHostTypes.RelatedInformationType }; }; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index f80c0f8d6b3..bead4ad7611 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; 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, CodeActionTriggerKind, SyntaxTokenType } from 'vs/workbench/api/common/extHostTypes'; +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'; @@ -377,7 +377,6 @@ class CodeActionAdapter { private readonly _cache = new Cache('CodeAction'); private readonly _disposables = new Map(); - private readonly nbKind = new CodeActionKind('notebook'); constructor( private readonly _documents: ExtHostDocuments, @@ -437,10 +436,6 @@ class CodeActionAdapter { command: this._commands.toInternal(candidate, disposables), }); } else { - if (codeActionContext.triggerKind !== CodeActionTriggerKind.Invoke && candidate.kind && this.nbKind.contains(candidate.kind)) { - continue; - } - if (codeActionContext.only) { if (!candidate.kind) { this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b1ea9b94055..670dffdcbb4 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1397,16 +1397,6 @@ export class CodeActionKind { } } -export class NotebookCodeActionKind extends CodeActionKind { - public static override Notebook: CodeActionKind; - - constructor( - public override readonly value: string - ) { - super(value); - } -} - CodeActionKind.Empty = new CodeActionKind(''); CodeActionKind.QuickFix = CodeActionKind.Empty.append('quickfix'); CodeActionKind.Refactor = CodeActionKind.Empty.append('refactor'); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index e4d9224bfe3..dc42987bbce 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -38,8 +38,6 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IStoredFileWorkingCopy, IStoredFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; import { IStoredFileWorkingCopySaveParticipant, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -const NotebookCodeAction = new CodeActionKind('notebook'); - class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { constructor( @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @@ -306,6 +304,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa } async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { + const nbDisposable = new DisposableStore(); const isTrusted = this.workspaceTrustManagementService.isWorkspaceTrusted(); if (!isTrusted) { return; @@ -315,31 +314,46 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa return; } + let saveTrigger = ''; if (context.reason === SaveReason.AUTO) { - return undefined; - } - - const setting = this.configurationService.getValue<{ [kind: string]: boolean } | string[]>(NotebookSetting.codeActionsOnSave); - if (!setting) { + // currently this won't happen, as vs/editor/contrib/codeAction/browser/codeAction.ts L#104 filters out codeactions on autosave. Just future-proofing + // ? notebook CodeActions on autosave seems dangerous (perf-wise) + saveTrigger = 'always'; + } else if (context.reason === SaveReason.EXPLICIT) { + saveTrigger = 'explicit'; + } else { + // SaveReason.FOCUS_CHANGE, WINDOW_CHANGE need to be addressed when autosaves are enabled return undefined; } const notebookModel = workingCopy.model.notebookModel; + const setting = this.configurationService.getValue<{ [kind: string]: string }>(NotebookSetting.codeActionsOnSave); + if (!setting) { + return undefined; + } const settingItems: string[] = Array.isArray(setting) ? setting : Object.keys(setting).filter(x => setting[x]); - if (!settingItems.length) { return undefined; } - const codeActionsOnSave = this.createCodeActionsOnSave(settingItems).filter(x => !NotebookCodeAction.contains(x)); - const notebookCodeActionsOnSave = this.createCodeActionsOnSave(settingItems).filter(x => NotebookCodeAction.contains(x)); + const allCodeActions = this.createCodeActionsOnSave(settingItems); + const excludedActions = allCodeActions + .filter(x => setting[x.value] === 'never'); + const includedActions = allCodeActions + .filter(x => setting[x.value] === saveTrigger); + + const editorCodeActionsOnSave = includedActions.filter(x => !CodeActionKind.Notebook.contains(x)); + const notebookCodeActionsOnSave = includedActions.filter(x => CodeActionKind.Notebook.contains(x)); + if (!editorCodeActionsOnSave.length && !notebookCodeActionsOnSave.length) { + return undefined; + } // prioritize `source.fixAll` code actions if (!Array.isArray(setting)) { - codeActionsOnSave.sort((a, b) => { + editorCodeActionsOnSave.sort((a, b) => { if (CodeActionKind.SourceFixAll.contains(a)) { if (CodeActionKind.SourceFixAll.contains(b)) { return 0; @@ -353,21 +367,6 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa }); } - - - - if (!codeActionsOnSave.length && !notebookCodeActionsOnSave.length) { - return undefined; - } - - const excludedActions = Array.isArray(setting) - ? [] - : Object.keys(setting) - .filter(x => setting[x] === false) - .map(x => new CodeActionKind(x)); - - const nbDisposable = new DisposableStore(); - // run notebook code actions progress.report({ message: localize('notebookSaveParticipants.notebookCodeActions', "Running 'Notebook' code actions") }); try { @@ -387,7 +386,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa // run cell level code actions const disposable = new DisposableStore(); - progress.report({ message: localize('notebookSaveParticipants.cellCodeActions', "Running code actions") }); + progress.report({ message: localize('notebookSaveParticipants.cellCodeActions', "Running 'Cell' code actions") }); try { await Promise.all(notebookModel.cells.map(async cell => { const ref = await this.textModelService.createModelReference(cell.uri); @@ -395,7 +394,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa const textEditorModel = ref.object.textEditorModel; - await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token); + await this.applyOnSaveActions(textEditorModel, editorCodeActionsOnSave, excludedActions, progress, token); })); } catch { this.logService.error('Failed to apply code action on save'); @@ -447,7 +446,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa for (const action of actionsToRun.validActions) { const codeActionEdits = action.action.edit?.edits; let breakFlag = false; - if (!action.action.kind?.includes('notebook')) { + if (!action.action.kind?.startsWith('notebook')) { for (const edit of codeActionEdits ?? []) { const workspaceTextEdit = edit as IWorkspaceTextEdit; if (workspaceTextEdit.resource && isEqual(workspaceTextEdit.resource, model.uri)) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b100f521208..c4628df2fd4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -964,10 +964,14 @@ configurationRegistry.registerConfiguration({ default: false }, [NotebookSetting.codeActionsOnSave]: { - markdownDescription: nls.localize('notebook.codeActionsOnSave', "Experimental. Run a series of CodeActions for a notebook on save. CodeActions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `source.fixAll: true`"), + markdownDescription: nls.localize('notebook.codeActionsOnSave', "Run a series of CodeActions for a notebook on save. CodeActions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `source.fixAll: true`"), type: 'object', additionalProperties: { - type: 'boolean' + type: 'string', + enum: ['explicit', 'never'], + // enum: ['explicit', 'always', 'never'], -- autosave support needs to be built first + // nls.localize('always', 'Always triggers Code Actions on save, including autosave, focus, and window change events.'), + enumDescriptions: [nls.localize('never', 'Never triggers Code Actions on save.'), nls.localize('explicit', 'Triggers Code Actions only when explicitly saved.')], }, default: {} }, From 90854deeb46af96050610baadfcb9a9726953a1d Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 18 Sep 2023 13:02:27 -0700 Subject: [PATCH 067/133] Re #167719. Avoid heap operations when dealing with large files. (#193309) --- src/vs/editor/common/model.ts | 7 +++++++ src/vs/editor/common/model/textModel.ts | 17 +++++++++++++++++ .../contrib/find/browser/findController.ts | 13 ++++++++++--- .../find/test/browser/findController.test.ts | 6 ++++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 3d9f01f7dfa..b5cfb6cf6c7 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -840,6 +840,13 @@ export interface ITextModel { */ isTooLargeForTokenization(): boolean; + /** + * The file is so large, that operations on it might be too large for heap + * and can lead to OOM crashes so they should be disabled. + * @internal + */ + isTooLargeForHeapOperation(): boolean; + /** * Search the model. * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index b600bd7e3bb..ea5f3ffea5e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -177,6 +177,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati static _MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB, // used in tests private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB; private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines + private static readonly LARGE_FILE_HEAP_OPERATION_THRESHOLD = 256 * 1024 * 1024; // 256M characters, usually ~> 512MB memory usage public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = { isForSimpleWidget: false, @@ -257,6 +258,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati private _initialUndoRedoSnapshot: ResourceEditStackSnapshot | null; private readonly _isTooLargeForSyncing: boolean; private readonly _isTooLargeForTokenization: boolean; + private readonly _isTooLargeForHeapOperation: boolean; //#region Editing private readonly _commandManager: EditStack; @@ -345,8 +347,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati (bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD) || (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD) ); + + this._isTooLargeForHeapOperation = bufferTextLength > TextModel.LARGE_FILE_HEAP_OPERATION_THRESHOLD; } else { this._isTooLargeForTokenization = false; + this._isTooLargeForHeapOperation = false; } this._isTooLargeForSyncing = (bufferTextLength > TextModel._MODEL_SYNC_LIMIT); @@ -587,6 +592,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati return this._isTooLargeForTokenization; } + public isTooLargeForHeapOperation(): boolean { + return this._isTooLargeForHeapOperation; + } + public isDisposed(): boolean { return this._isDisposed; } @@ -743,6 +752,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati public getValue(eol?: model.EndOfLinePreference, preserveBOM: boolean = false): string { this._assertNotDisposed(); + if (this.isTooLargeForHeapOperation()) { + throw new BugIndicatingError('Operation would exceed heap memory limits'); + } + const fullModelRange = this.getFullModelRange(); const fullModelValue = this.getValueInRange(fullModelRange, eol); @@ -809,6 +822,10 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati public getLinesContent(): string[] { this._assertNotDisposed(); + if (this.isTooLargeForHeapOperation()) { + throw new BugIndicatingError('Operation would exceed heap memory limits'); + } + return this._buffer.getLinesContent(); } diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 168d7e5c230..ce1cdb9084e 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -97,6 +97,7 @@ export class CommonFindController extends Disposable implements IEditorContribut protected readonly _storageService: IStorageService; private readonly _clipboardService: IClipboardService; protected readonly _contextKeyService: IContextKeyService; + protected readonly _notificationService: INotificationService; get editor() { return this._editor; @@ -110,7 +111,8 @@ export class CommonFindController extends Disposable implements IEditorContribut editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IStorageService storageService: IStorageService, - @IClipboardService clipboardService: IClipboardService + @IClipboardService clipboardService: IClipboardService, + @INotificationService notificationService: INotificationService ) { super(); this._editor = editor; @@ -118,6 +120,7 @@ export class CommonFindController extends Disposable implements IEditorContribut this._contextKeyService = contextKeyService; this._storageService = storageService; this._clipboardService = clipboardService; + this._notificationService = notificationService; this._updateHistoryDelayer = new Delayer(500); this._state = this._register(new FindReplaceState()); @@ -390,6 +393,10 @@ export class CommonFindController extends Disposable implements IEditorContribut public replaceAll(): boolean { if (this._model) { + if (this._editor.getModel()?.isTooLargeForHeapOperation()) { + this._notificationService.warn(nls.localize('too.large.for.replaceall', "The file is too large to perform a replace all operation.")); + return false; + } this._model.replaceAll(); return true; } @@ -437,11 +444,11 @@ export class FindController extends CommonFindController implements IFindControl @IContextKeyService _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IThemeService private readonly _themeService: IThemeService, - @INotificationService private readonly _notificationService: INotificationService, + @INotificationService notificationService: INotificationService, @IStorageService _storageService: IStorageService, @IClipboardService clipboardService: IClipboardService, ) { - super(editor, _contextKeyService, _storageService, clipboardService); + super(editor, _contextKeyService, _storageService, clipboardService, notificationService); this._widget = null; this._findOptionsWidget = null; } diff --git a/src/vs/editor/contrib/find/test/browser/findController.test.ts b/src/vs/editor/contrib/find/test/browser/findController.test.ts index 96505ecc752..27717765fa3 100644 --- a/src/vs/editor/contrib/find/test/browser/findController.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findController.test.ts @@ -20,6 +20,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService 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 { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, InMemoryStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; class TestFindController extends CommonFindController { @@ -33,9 +34,10 @@ class TestFindController extends CommonFindController { editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IStorageService storageService: IStorageService, - @IClipboardService clipboardService: IClipboardService + @IClipboardService clipboardService: IClipboardService, + @INotificationService notificationService: INotificationService ) { - super(editor, contextKeyService, storageService, clipboardService); + super(editor, contextKeyService, storageService, clipboardService, notificationService); this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService); this._updateHistoryDelayer = new Delayer(50); this.hasFocus = false; From b2cb1d8a4c455bfc0c98fadebb5ee9bd432f6574 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 13:59:04 -0700 Subject: [PATCH 068/133] move listener to contribution --- .../terminal.accessibility.contribution.ts | 9 +++++++++ .../browser/terminalAccessibleBufferProvider.ts | 16 +++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index ac7a6c4c912..88e56b61679 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -30,6 +30,7 @@ import { Position } from 'vs/editor/common/core/position'; import { ICommandWithEditorLine, TerminalAccessibleBufferProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { Event } from 'vs/base/common/event'; class TextAreaSyncContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.textAreaSync'; @@ -90,6 +91,14 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT xterm.raw.loadAddon(addon); addon.activate(xterm.raw); this._xterm = xterm; + this._register(this._xterm.raw.onWriteParsed(async () => { + if (this._xterm!.raw.buffer.active.baseY === 0) { + this._bufferTracker?.update(); + this.show(); + } + })); + const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); + this._register(onRequestUpdateEditor(() => this.show())); } show(): void { if (!this._xterm) { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 3b13dc83dd5..7b870c37e52 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -10,11 +10,10 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { IXtermTerminal, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; import type { Terminal } from 'xterm'; -import { Event } from 'vs/base/common/event'; export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal' }; @@ -27,11 +26,9 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements @IConfigurationService _configurationService: IConfigurationService, @IContextKeyService _contextKeyService: IContextKeyService, @ITerminalService _terminalService: ITerminalService, - @IConfigurationService configurationService: IConfigurationService, - @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + @IConfigurationService configurationService: IConfigurationService ) { super(); - this.registerListeners(); } onClose() { @@ -41,14 +38,7 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements if (!this._xterm) { return; } - this._xterm.raw.onWriteParsed(async () => { - if (this._xterm!.raw.buffer.active.baseY === 0) { - this._bufferTracker.update(); - this._accessibleViewService.show(this); - } - }); - const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); - this.add(onRequestUpdateEditor(() => this._accessibleViewService.show(this))); + } provideContent(): string { From 962e89aa7ef353d5d4d322dcfd0edb08e5de19a7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 14:01:55 -0700 Subject: [PATCH 069/133] Revert "move css to where it works" This reverts commit f30f7d80cc2609b868b74b2d811a2c554366f029. --- .../quickFix}/browser/media/terminalQuickFix.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/workbench/contrib/{terminal => terminalContrib/quickFix}/browser/media/terminalQuickFix.css (100%) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminalQuickFix.css b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css similarity index 100% rename from src/vs/workbench/contrib/terminal/browser/media/terminalQuickFix.css rename to src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css From 5a1642eddaf8e89a727a242493cc88f889b6ae15 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 14:02:51 -0700 Subject: [PATCH 070/133] add import in the right place --- .../workbench/contrib/terminal/browser/terminal.contribution.ts | 1 - .../quickFix/browser/terminal.quickFix.contribution.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index b45c61f9218..1bb341555ef 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/scrollbar'; import 'vs/css!./media/widgets'; import 'vs/css!./media/xterm'; import 'vs/css!./media/terminal'; -import 'vs/css!./media/terminalQuickFix'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts index 96f2fccd74f..4fca04ec9a9 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.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/terminalQuickFix'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; From 188dddf5d64658994da96bfcd1e3a54ff4555b02 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 14:33:30 -0700 Subject: [PATCH 071/133] fix the issue --- .../accessibility/browser/accessibleView.ts | 9 +++++++ .../terminal.accessibility.contribution.ts | 27 ++++++++++--------- .../terminalAccessibleBufferProvider.ts | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index bbde0f86f2c..568d0719d21 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -101,6 +101,7 @@ export interface IAccessibleViewOptions { */ language?: string; type: AccessibleViewType; + positionBottom?: boolean; } export class AccessibleView extends Disposable { @@ -412,6 +413,14 @@ export class AccessibleView extends Disposable { } this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); + if (this._currentProvider?.options.positionBottom) { + const lastLine = this.editorWidget.getModel()?.getLineCount(); + const position = lastLine !== undefined && lastLine > 0 ? new Position(lastLine, 1) : undefined; + if (position) { + this._editorWidget.setPosition(position); + this._editorWidget.revealLine(position.lineNumber); + } + } }); this._updateToolbar(provider.actions, provider.options.type); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 88e56b61679..bd9d7f3b50b 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; @@ -68,7 +68,8 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalService private readonly _terminalService: ITerminalService, - @IConfigurationService configurationService: IConfigurationService) { + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { if (this._terminalService.activeInstance !== this._instance) { @@ -92,14 +93,24 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT addon.activate(xterm.raw); this._xterm = xterm; this._register(this._xterm.raw.onWriteParsed(async () => { - if (this._xterm!.raw.buffer.active.baseY === 0) { + if (this._isTerminalAccessibleViewOpen() && this._xterm!.raw.buffer.active.baseY === 0) { this._bufferTracker?.update(); this.show(); } })); + const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); - this._register(onRequestUpdateEditor(() => this.show())); + this._register(onRequestUpdateEditor(() => { + if (this._isTerminalAccessibleViewOpen()) { + this.show(); + } + })); } + + private _isTerminalAccessibleViewOpen(): boolean { + return accessibleViewCurrentProviderId.getValue(this._contextKeyService) === AccessibleViewProviderId.Terminal; + } + show(): void { if (!this._xterm) { return; @@ -108,14 +119,6 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT this._bufferTracker = this._register(this._instantiationService.createInstance(BufferContentTracker, this._xterm)); } this._accessibleViewService.show(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); - // wait for the render to happen so that the line count is correct and - // the cursor is at the bottom of the buffer - setTimeout(() => { - const lastPosition = this._accessibleViewService.getLastPosition(); - if (lastPosition) { - this._accessibleViewService.setPosition(lastPosition, true); - } - }, 50); } navigateToCommand(type: NavigationType): void { const currentLine = this._accessibleViewService.getPosition()?.lineNumber || this._accessibleViewService.getLastPosition()?.lineNumber; diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 7b870c37e52..07d4b28f474 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -16,7 +16,7 @@ import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/acces import type { Terminal } from 'xterm'; export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { - options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal' }; + options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', positionBottom: true }; verbositySettingKey = AccessibilityVerbositySettingId.Terminal; private _xterm: IXtermTerminal & { raw: Terminal } | undefined; constructor( From 66b02b587a52019320b6f2dd5a01515880fc988a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 14:49:07 -0700 Subject: [PATCH 072/133] improve --- .../browser/terminal.accessibility.contribution.ts | 6 +++++- .../browser/terminalAccessibleBufferProvider.ts | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index bd9d7f3b50b..4a99e75a1f6 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -60,6 +60,7 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT return instance.getContribution(TerminalAccessibleViewContribution.ID); } private _bufferTracker: BufferContentTracker | undefined; + private _bufferProvider: TerminalAccessibleBufferProvider | undefined; private _xterm: Pick & { raw: Terminal } | undefined; constructor( private readonly _instance: ITerminalInstance, @@ -118,7 +119,10 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT if (!this._bufferTracker) { this._bufferTracker = this._register(this._instantiationService.createInstance(BufferContentTracker, this._xterm)); } - this._accessibleViewService.show(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + if (!this._bufferProvider) { + this._bufferProvider = this._register(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + } + this._accessibleViewService.show(this._bufferProvider); } navigateToCommand(type: NavigationType): void { const currentLine = this._accessibleViewService.getPosition()?.lineNumber || this._accessibleViewService.getLastPosition()?.lineNumber; diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 07d4b28f474..5e0a5b0fbe5 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -38,7 +38,6 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements if (!this._xterm) { return; } - } provideContent(): string { From ffc9eaf2cc537e7a4f43196e4b00e8462e063f11 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 18 Sep 2023 14:50:14 -0700 Subject: [PATCH 073/133] rm redundant call --- .../accessibility/browser/terminal.accessibility.contribution.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 4a99e75a1f6..95d437c7ee2 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -95,7 +95,6 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT this._xterm = xterm; this._register(this._xterm.raw.onWriteParsed(async () => { if (this._isTerminalAccessibleViewOpen() && this._xterm!.raw.buffer.active.baseY === 0) { - this._bufferTracker?.update(); this.show(); } })); From e7902c58b0aa0bad4188d6e1287ac8c352912c93 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 18 Sep 2023 16:28:07 -0700 Subject: [PATCH 074/133] Bump distro (#193413) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6dd16c69e4..cc298739be3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.83.0", - "distro": "c13fa037c20b672a48c9e7990df10998974196f9", + "distro": "5a794f00ac87e37b6abea46ccf030aeb4b323aeb", "author": { "name": "Microsoft Corporation" }, From f20ca3aa50446818fc76d1cd696eff85efd19323 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:30:01 -0700 Subject: [PATCH 075/133] Mark packages as private (#193414) --- extensions/grunt/package.json | 1 + extensions/ini/package.json | 1 + extensions/npm/package.json | 1 + 3 files changed, 3 insertions(+) diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index 0c00aec71c6..6869f9ce506 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -4,6 +4,7 @@ "description": "Extension to add Grunt capabilities to VS Code.", "displayName": "Grunt support for VS Code", "version": "1.0.0", + "private": true, "icon": "images/grunt.png", "license": "MIT", "engines": { diff --git a/extensions/ini/package.json b/extensions/ini/package.json index 0c7dda3c880..ac1a59e8af5 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -3,6 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "version": "1.0.0", + "private": true, "publisher": "vscode", "license": "MIT", "engines": { diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 2bacd70b76d..624e2c91a13 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -4,6 +4,7 @@ "displayName": "%displayName%", "description": "%description%", "version": "1.0.1", + "private": true, "license": "MIT", "engines": { "vscode": "0.10.x" From ee8f89f6145ff7202e30b80280adc50b156f46e5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 18 Sep 2023 16:50:05 -0700 Subject: [PATCH 076/133] Allow cmd+c to copy chat row content (#193410) * Allow cmd+c to copy chat row content Fix microsoft/vscode-copilot#1433 * Add onDidBlur listener --- .../chat/browser/actions/chatCopyActions.ts | 16 ++++++++++++++-- .../workbench/contrib/chat/browser/chatWidget.ts | 7 ++++++- .../contrib/chat/common/chatContextKeys.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index a2e7aa52b62..8899be6939b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CONTEXT_IN_CHAT_LIST } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export function registerChatCopyActions() { @@ -57,14 +60,23 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyC, + when: CONTEXT_IN_CHAT_LIST } }); } run(accessor: ServicesAccessor, ...args: any[]) { - const item = args[0]; + let item = args[0]; if (!isRequestVM(item) && !isResponseVM(item)) { - return; + const widgetService = accessor.get(IChatWidgetService); + item = widgetService.lastFocusedWidget?.getFocus(); + if (!isRequestVM(item) && !isResponseVM(item)) { + return; + } } const clipboardService = accessor.get(IClipboardService); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 91c15635f2e..7948c9dd8fc 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -25,7 +25,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_LIST, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; @@ -119,6 +119,8 @@ export class ChatWidget extends Disposable implements IChatWidget { private lastSlashCommands: ISlashCommand[] | undefined; private slashCommandsPromise: Promise | undefined; + private readonly chatListFocused: IContextKey; + constructor( readonly viewContext: IChatWidgetViewContext, private readonly styles: IChatWidgetStyles, @@ -132,6 +134,7 @@ export class ChatWidget extends Disposable implements IChatWidget { ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); + this.chatListFocused = CONTEXT_IN_CHAT_LIST.bindTo(contextKeyService); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); this._register((chatWidgetService as ChatWidgetService).register(this)); @@ -351,7 +354,9 @@ export class ChatWidget extends Disposable implements IChatWidget { })); this._register(this.tree.onDidFocus(() => { this._onDidFocus.fire(); + this.chatListFocused.set(this.tree.isDOMFocused()); })); + this._register(this.tree.onDidBlur(() => this.chatListFocused.set(false))); } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 631f0af7d00..95dd8d6e825 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -17,5 +17,6 @@ export const CONTEXT_REQUEST = new RawContextKey('chatRequest', false, export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); +export const CONTEXT_IN_CHAT_LIST = new RawContextKey('chatListFocused', false, { type: 'boolean', description: localize('chatListFocused', "True when a row of the chat list is focused, but not when focus is on a different element inside the chat row.") }); export const CONTEXT_PROVIDER_EXISTS = new RawContextKey('hasChatProvider', false, { type: 'boolean', description: localize('hasChatProvider', "True when some chat provider has been registered.") }); From 516ab4fc73f164695eff89f602cc6fdcefd94bd9 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Mon, 18 Sep 2023 17:40:02 -0700 Subject: [PATCH 077/133] Add support for debugger type for selectAndStartDebugging (#193156) * Add support for debugger type for selectAndStartDebugging This PR adds in an arg to the command 'selectAndStartDebugging' so users are able to jump directly to a debugger type instead of having users to select a debugger type then select the configuration they want. * Move accessors to the top to avoid calling one after an await --------- Co-authored-by: Rob Lourens --- .../contrib/debug/browser/debugCommands.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 1fecc75d4f9..0a74560e1b4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -704,8 +704,26 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: SELECT_AND_START_ID, - handler: async (accessor: ServicesAccessor) => { + handler: async (accessor: ServicesAccessor, debugType: string | unknown) => { const quickInputService = accessor.get(IQuickInputService); + const debugService = accessor.get(IDebugService); + + if (debugType) { + const configManager = debugService.getConfigurationManager(); + const dynamicProviders = await configManager.getDynamicProviders(); + for (const provider of dynamicProviders) { + if (provider.type === debugType) { + const pick = await provider.pick(); + if (pick) { + await configManager.selectConfiguration(pick.launch, pick.config.name, pick.config, { type: provider.type }); + debugService.startDebugging(pick.launch, pick.config, { startedByUser: true }); + + return; + } + } + } + } + quickInputService.quickAccess.show(DEBUG_QUICK_ACCESS_PREFIX); } }); From 062ba1ed6c2b9ff4819f4f7dad76de3fde0044ab Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 18 Sep 2023 21:07:29 -0700 Subject: [PATCH 078/133] Initial implementation of TF-IDF for similar commands (#193418) --- src/vs/base/common/tfIdf.ts | 35 ++++++++++ .../quickinput/browser/commandsQuickAccess.ts | 64 ++++++++++++++++++- .../browser/commandsQuickAccess.ts | 9 +-- 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/tfIdf.ts b/src/vs/base/common/tfIdf.ts index f76a24d1ce0..993f41b5a78 100644 --- a/src/vs/base/common/tfIdf.ts +++ b/src/vs/base/common/tfIdf.ts @@ -29,6 +29,17 @@ export interface TfIdfDocument { export interface TfIdfScore { readonly key: string; + /** + * An unbounded number. + */ + readonly score: number; +} + +export interface NormalizedTfIdfScore { + readonly key: string; + /** + * A number between 0 and 1. + */ readonly score: number; } @@ -204,3 +215,27 @@ export class TfIdfCalculator { return embedding; } } + +/** + * Normalize the scores to be between 0 and 1 and sort them decending. + * @param scores array of scores from {@link TfIdfCalculator.calculateScores} + * @returns normalized scores + */ +export function normalizeTfIdfScores(scores: TfIdfScore[]): NormalizedTfIdfScore[] { + + // copy of scores + const result = scores.slice(0) as { score: number }[]; + + // sort descending + result.sort((a, b) => b.score - a.score); + + // normalize + const max = result[0]?.score ?? 0; + if (max > 0) { + for (const score of result) { + score.score /= max; + } + } + + return result as TfIdfScore[]; +} diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index 49e70c41c70..2ff14dbaa44 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -8,8 +8,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isCancellationError } from 'vs/base/common/errors'; import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from 'vs/base/common/filters'; +import { once } from 'vs/base/common/functional'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; +import { TfIdfCalculator, normalizeTfIdfScores } from 'vs/base/common/tfIdf'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -25,6 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ICommandQuickPick extends IPickerQuickAccessItem { readonly commandId: string; readonly commandAlias?: string; + tfIdfScore?: number; readonly args?: any[]; } @@ -37,6 +40,9 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc static PREFIX = '>'; + private static readonly TFIDF_THRESHOLD = 0.5; + private static readonly TFIDF_MAX_RESULTS = 5; + private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory)); @@ -65,6 +71,19 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return []; } + const runTfidf = once(() => { + const tfidf = new TfIdfCalculator(); + tfidf.updateDocuments(allCommandPicks.map(commandPick => ({ + key: commandPick.commandId, + textChunks: [commandPick.label + (commandPick.commandAlias ? ` ${commandPick.commandAlias}` : '')] + }))); + const result = tfidf.calculateScores(filter, token); + + return normalizeTfIdfScores(result) + .filter(score => score.score > AbstractCommandsQuickAccessProvider.TFIDF_THRESHOLD) + .slice(0, AbstractCommandsQuickAccessProvider.TFIDF_MAX_RESULTS); + }); + // Filter const filteredCommandPicks: ICommandQuickPick[] = []; for (const commandPick of allCommandPicks) { @@ -85,6 +104,21 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc else if (filter === commandPick.commandId) { filteredCommandPicks.push(commandPick); } + + // Handle tf-idf scoring for the rest if there's a filter + else if (filter.length >= 3) { + const tfidf = runTfidf(); + if (token.isCancellationRequested) { + return []; + } + + // Add if we have a tf-idf score + const tfidfScore = tfidf.find(score => score.key === commandPick.commandId); + if (tfidfScore) { + commandPick.tfIdfScore = tfidfScore.score; + filteredCommandPicks.push(commandPick); + } + } } // Add description to commands that have duplicate labels @@ -101,6 +135,18 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc // Sort by MRU order and fallback to name otherwise filteredCommandPicks.sort((commandPickA, commandPickB) => { + // If a result came from tf-idf, we want to put that towards the bottom + if (commandPickA.tfIdfScore && commandPickB.tfIdfScore) { + if (commandPickA.tfIdfScore === commandPickB.tfIdfScore) { + return commandPickA.label.localeCompare(commandPickB.label); // prefer lexicographically smaller command + } + return commandPickB.tfIdfScore - commandPickA.tfIdfScore; // prefer higher tf-idf score + } else if (commandPickA.tfIdfScore) { + return 1; // first command has a score but other doesn't so other wins + } else if (commandPickB.tfIdfScore) { + return -1; // other command has a score but first doesn't so first wins + } + const commandACounter = this.commandsHistory.peek(commandPickA.commandId); const commandBCounter = this.commandsHistory.peek(commandPickB.commandId); @@ -139,6 +185,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc const commandPicks: Array = []; let addOtherSeparator = false; + let addSuggestedSeparator = true; let addCommonlyUsedSeparator = !!this.options.suggestedCommandIds; for (let i = 0; i < filteredCommandPicks.length; i++) { const commandPick = filteredCommandPicks[i]; @@ -149,15 +196,20 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc addOtherSeparator = true; } + if (addSuggestedSeparator && commandPick.tfIdfScore !== undefined) { + commandPicks.push({ type: 'separator', label: localize('suggested', "similar commands") }); + addSuggestedSeparator = false; + } + // Separator: commonly used - if (addCommonlyUsedSeparator && !this.commandsHistory.peek(commandPick.commandId) && this.options.suggestedCommandIds?.has(commandPick.commandId)) { + if (addCommonlyUsedSeparator && commandPick.tfIdfScore === undefined && !this.commandsHistory.peek(commandPick.commandId) && this.options.suggestedCommandIds?.has(commandPick.commandId)) { commandPicks.push({ type: 'separator', label: localize('commonlyUsed', "commonly used") }); addOtherSeparator = true; addCommonlyUsedSeparator = false; } // Separator: other commands - if (addOtherSeparator && !this.commandsHistory.peek(commandPick.commandId) && !this.options.suggestedCommandIds?.has(commandPick.commandId)) { + if (addOtherSeparator && commandPick.tfIdfScore === undefined && !this.commandsHistory.peek(commandPick.commandId) && !this.options.suggestedCommandIds?.has(commandPick.commandId)) { commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") }); addOtherSeparator = false; } @@ -178,7 +230,13 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return []; } - return additionalCommandPicks.map(commandPick => this.toCommandPick(commandPick, runOptions)); + const commandPicks: Array = additionalCommandPicks.map(commandPick => this.toCommandPick(commandPick, runOptions)); + // Basically, if we haven't already added a separator, we add one before the additional picks so long + // as one hasn't been added to the start of the array. + if (addSuggestedSeparator && commandPicks[0]?.type !== 'separator') { + commandPicks.unshift({ type: 'separator', label: localize('suggested', "similar commands") }); + } + return commandPicks; })() }; } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 1dc6c5919bf..7f00f9f2e9c 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -40,7 +40,7 @@ import { CHAT_OPEN_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/c export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { - private static AI_RELATED_INFORMATION_MAX_PICKS = 3; + private static AI_RELATED_INFORMATION_MAX_PICKS = 5; private static AI_RELATED_INFORMATION_THRESHOLD = 0.8; private static AI_RELATED_INFORMATION_DEBOUNCE = 200; @@ -165,13 +165,6 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce return []; } - if (additionalPicks.length) { - additionalPicks.unshift({ - type: 'separator', - label: localize('similarCommands', "similar commands") - }); - } - if (picksSoFar.length || additionalPicks.length) { additionalPicks.push({ type: 'separator' From 7c7f7eee860e299499a3bd2915ad716f09f2d6a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Sep 2023 09:18:20 +0100 Subject: [PATCH 079/133] rewrite blob upload promises (#193434) --- build/azure-pipelines/common/createAsset.js | 48 ++++++++++++--------- build/azure-pipelines/common/createAsset.ts | 44 +++++++++++-------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index e4ca63b6573..6adcbca142c 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -166,32 +166,38 @@ async function main() { } }; const uploadPromises = []; - if (await blobClient.exists()) { - uploadPromises.push(Promise.reject(new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`))); - } - else { - uploadPromises.push((0, retry_1.retry)(async (attempt) => { - console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); - await blobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Azure storage.'); - })); - } + uploadPromises.push((async () => { + console.log(`Checking for blob in Azure...`); + if (await blobClient.exists()) { + throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + } + else { + await (0, retry_1.retry)(async (attempt) => { + console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); + await blobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Azure storage.'); + }); + } + })()); const shouldUploadToMooncake = /true/i.test(process.env['VSCODE_PUBLISH_TO_MOONCAKE'] ?? 'true'); if (shouldUploadToMooncake) { const mooncakeCredential = new identity_1.ClientSecretCredential(process.env['AZURE_MOONCAKE_TENANT_ID'], process.env['AZURE_MOONCAKE_CLIENT_ID'], process.env['AZURE_MOONCAKE_CLIENT_SECRET']); const mooncakeBlobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.chinacloudapi.cn`, mooncakeCredential, storagePipelineOptions); const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); - if (await mooncakeBlobClient.exists()) { - uploadPromises.push(Promise.reject(new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`))); - } - else { - uploadPromises.push((0, retry_1.retry)(async (attempt) => { - console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); - await mooncakeBlobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Mooncake Azure storage.'); - })); - } + uploadPromises.push((async () => { + console.log(`Checking for blob in Mooncake Azure...`); + if (await mooncakeBlobClient.exists()) { + throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + } + else { + await (0, retry_1.retry)(async (attempt) => { + console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); + await mooncakeBlobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Mooncake Azure storage.'); + }); + } + })()); } const promiseResults = await Promise.allSettled(uploadPromises); const rejectedPromiseResults = promiseResults.filter(result => result.status === 'rejected'); @@ -235,4 +241,4 @@ main().then(() => { console.error(err); process.exit(1); }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8s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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8s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 \ No newline at end of file diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index 74c76407a78..fe29f584900 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -197,15 +197,19 @@ async function main(): Promise { const uploadPromises: Promise[] = []; - if (await blobClient.exists()) { - uploadPromises.push(Promise.reject(new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`))); - } else { - uploadPromises.push(retry(async (attempt) => { - console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); - await blobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Azure storage.'); - })); - } + uploadPromises.push((async () => { + console.log(`Checking for blob in Azure...`); + + if (await blobClient.exists()) { + throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + } else { + await retry(async (attempt) => { + console.log(`Uploading blobs to Azure storage (attempt ${attempt})...`); + await blobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Azure storage.'); + }); + } + })()); const shouldUploadToMooncake = /true/i.test(process.env['VSCODE_PUBLISH_TO_MOONCAKE'] ?? 'true'); @@ -215,15 +219,19 @@ async function main(): Promise { const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); - if (await mooncakeBlobClient.exists()) { - uploadPromises.push(Promise.reject(new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`))); - } else { - uploadPromises.push(retry(async (attempt) => { - console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); - await mooncakeBlobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Mooncake Azure storage.'); - })); - } + uploadPromises.push((async () => { + console.log(`Checking for blob in Mooncake Azure...`); + + if (await mooncakeBlobClient.exists()) { + throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + } else { + await retry(async (attempt) => { + console.log(`Uploading blobs to Mooncake Azure storage (attempt ${attempt})...`); + await mooncakeBlobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Mooncake Azure storage.'); + }); + } + })()); } const promiseResults = await Promise.allSettled(uploadPromises); From ef023da6db0157f82a19f3d99aad9a30734612e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Sep 2023 13:19:12 +0100 Subject: [PATCH 080/133] more defense against bad AAD auth (#193447) add retry to check blob --- build/azure-pipelines/common/createAsset.js | 6 +++--- build/azure-pipelines/common/createAsset.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index 6adcbca142c..89f675ebcec 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -168,7 +168,7 @@ async function main() { const uploadPromises = []; uploadPromises.push((async () => { console.log(`Checking for blob in Azure...`); - if (await blobClient.exists()) { + if (await (0, retry_1.retry)(() => blobClient.exists())) { throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`); } else { @@ -187,7 +187,7 @@ async function main() { const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); uploadPromises.push((async () => { console.log(`Checking for blob in Mooncake Azure...`); - if (await mooncakeBlobClient.exists()) { + if (await (0, retry_1.retry)(() => mooncakeBlobClient.exists())) { throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); } else { @@ -241,4 +241,4 @@ main().then(() => { console.error(err); process.exit(1); }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8s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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8s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 \ No newline at end of file diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index fe29f584900..97c90ab40e2 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -200,7 +200,7 @@ async function main(): Promise { uploadPromises.push((async () => { console.log(`Checking for blob in Azure...`); - if (await blobClient.exists()) { + if (await retry(() => blobClient.exists())) { throw new Error(`Blob ${quality}, ${blobName} already exists, not publishing again.`); } else { await retry(async (attempt) => { @@ -222,7 +222,7 @@ async function main(): Promise { uploadPromises.push((async () => { console.log(`Checking for blob in Mooncake Azure...`); - if (await mooncakeBlobClient.exists()) { + if (await retry(() => mooncakeBlobClient.exists())) { throw new Error(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); } else { await retry(async (attempt) => { From e20eb064c690e2abf9c11259dce63664f7091993 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 14:36:47 +0200 Subject: [PATCH 081/133] `treeView.message = undefined` should not block `viewsWelcome` from appearing (#193457) Fixes #193435 --- 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 6a197e4ee24..24231e4dd00 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -129,7 +129,7 @@ export class TreeViewPane extends ViewPane { } override shouldShowWelcome(): boolean { - return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined); + return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && ((this.treeView.message === undefined) || (this.treeView.message === '')); } protected override layoutBody(height: number, width: number): void { From 1545aeab06f372167839d5102c15bef1536187cd Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:58:22 +0200 Subject: [PATCH 082/133] SCM - Initial implementation of the Sync view (#193440) --- extensions/git/package.json | 1 + extensions/git/src/actionButton.ts | 272 ++++---- extensions/git/src/api/git.d.ts | 2 + extensions/git/src/git.ts | 46 +- extensions/git/src/historyProvider.ts | 132 ++++ extensions/git/src/operation.ts | 8 +- extensions/git/src/repository.ts | 30 +- extensions/git/tsconfig.json | 1 + src/vs/workbench/api/browser/mainThreadSCM.ts | 99 ++- .../workbench/api/common/extHost.protocol.ts | 29 + src/vs/workbench/api/common/extHostSCM.ts | 97 ++- .../contrib/scm/browser/media/scm.css | 38 ++ .../contrib/scm/browser/scm.contribution.ts | 21 +- .../contrib/scm/browser/scmSyncViewPane.ts | 584 ++++++++++++++++++ .../contrib/scm/browser/scmViewPane.ts | 2 +- src/vs/workbench/contrib/scm/browser/util.ts | 4 + .../workbench/contrib/scm/common/history.ts | 43 ++ src/vs/workbench/contrib/scm/common/scm.ts | 5 + .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.scmHistoryProvider.d.ts | 72 +++ 20 files changed, 1359 insertions(+), 128 deletions(-) create mode 100644 extensions/git/src/historyProvider.ts create mode 100644 src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts create mode 100644 src/vs/workbench/contrib/scm/common/history.ts create mode 100644 src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index f236c524faf..e42d7b9c393 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -18,6 +18,7 @@ "editSessionIdentityProvider", "quickDiffProvider", "scmActionButton", + "scmHistoryProvider", "scmSelectedProvider", "scmValidation", "tabInputTextMerge", diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 6b99a2a967a..5dd42eebdd5 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -20,24 +20,24 @@ interface ActionButtonState { readonly repositoryHasChangesToCommit: boolean; } -export class ActionButtonCommand { - private _onDidChange = new EventEmitter(); +abstract class AbstractActionButton { + protected _onDidChange = new EventEmitter(); get onDidChange(): Event { return this._onDidChange.event; } private _state: ActionButtonState; - private get state() { return this._state; } - private set state(state: ActionButtonState) { + protected get state() { return this._state; } + protected set state(state: ActionButtonState) { if (JSON.stringify(this._state) !== JSON.stringify(state)) { this._state = state; this._onDidChange.fire(); } } - private disposables: Disposable[] = []; + abstract get button(): SourceControlActionButton | undefined; - constructor( - readonly repository: Repository, - readonly postCommitCommandCenter: CommitCommandsCenter) { + protected disposables: Disposable[] = []; + + constructor(readonly repository: Repository) { this._state = { HEAD: undefined, isCheckoutInProgress: false, @@ -50,6 +50,126 @@ export class ActionButtonCommand { repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); + } + + protected getPublishBranchActionButton(): SourceControlActionButton | undefined { + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)'; + + return { + command: { + command: 'git.publish', + title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }), + tooltip: this.state.isSyncInProgress ? + (this.state.HEAD?.name ? + l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : + l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) : + (this.repository.HEAD?.name ? + l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : + l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })), + arguments: [this.repository.sourceControl], + }, + enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress + }; + } + + protected getSyncChangesActionButton(): SourceControlActionButton | undefined { + const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0; + + const ahead = this.state.HEAD?.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; + const behind = this.state.HEAD?.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)'; + + return { + command: { + command: 'git.sync', + title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead), + tooltip: this.state.isSyncInProgress ? + l10n.t('Synchronizing Changes...') + : this.repository.syncTooltip, + arguments: [this.repository.sourceControl], + }, + description: `${icon}${behind}${ahead}`, + enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress && branchIsAheadOrBehind + }; + } + + private onDidChangeOperations(): void { + const isCheckoutInProgress + = this.repository.operations.isRunning(OperationKind.Checkout) || + this.repository.operations.isRunning(OperationKind.CheckoutTracking); + + const isCommitInProgress = + this.repository.operations.isRunning(OperationKind.Commit) || + this.repository.operations.isRunning(OperationKind.PostCommitCommand) || + this.repository.operations.isRunning(OperationKind.RebaseContinue); + + const isSyncInProgress = + this.repository.operations.isRunning(OperationKind.Sync) || + this.repository.operations.isRunning(OperationKind.Push) || + this.repository.operations.isRunning(OperationKind.Pull); + + this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress }; + } + + private onDidRunGitStatus(): void { + this.state = { + ...this.state, + HEAD: this.repository.HEAD, + isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0, + isRebaseInProgress: !!this.repository.rebaseCommit, + repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() + }; + } + + protected repositoryHasChangesToCommit(): boolean { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const enableSmartCommit = config.get('enableSmartCommit') === true; + const suggestSmartCommit = config.get('suggestSmartCommit') === true; + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all'); + + const resources = [...this.repository.indexGroup.resourceStates]; + + if ( + // Smart commit enabled (all) + (enableSmartCommit && smartCommitChanges === 'all') || + // Smart commit disabled, smart suggestion enabled + (!enableSmartCommit && suggestSmartCommit) + ) { + resources.push(...this.repository.workingTreeGroup.resourceStates); + } + + // Smart commit enabled (tracked only) + if (enableSmartCommit && smartCommitChanges === 'tracked') { + resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED)); + } + + return resources.length !== 0; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} + +export class CommitActionButton extends AbstractActionButton { + override get button(): SourceControlActionButton | undefined { + if (!this.state.HEAD) { return undefined; } + + let actionButton: SourceControlActionButton | undefined; + + if (this.state.repositoryHasChangesToCommit) { + // Commit Changes (enabled) + actionButton = this.getCommitActionButton(); + } + + // Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled) + return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton(); + } + + constructor( + repository: Repository, + readonly postCommitCommandCenter: CommitCommandsCenter) { + super(repository); this.disposables.push(repository.onDidChangeBranchProtection(() => this._onDidChange.fire())); this.disposables.push(postCommitCommandCenter.onDidChange(() => this._onDidChange.fire())); @@ -62,7 +182,8 @@ export class ActionButtonCommand { this.onDidChangeSmartCommitSettings(); } - if (e.affectsConfiguration('git.branchProtectionPrompt', root) || + if (e.affectsConfiguration('scm.experimental.showSyncView') || + e.affectsConfiguration('git.branchProtectionPrompt', root) || e.affectsConfiguration('git.postCommitCommand', root) || e.affectsConfiguration('git.rememberPostCommitCommand', root) || e.affectsConfiguration('git.showActionButton', root)) { @@ -71,20 +192,6 @@ export class ActionButtonCommand { })); } - get button(): SourceControlActionButton | undefined { - if (!this.state.HEAD) { return undefined; } - - let actionButton: SourceControlActionButton | undefined; - - if (this.state.repositoryHasChangesToCommit) { - // Commit Changes (enabled) - actionButton = this.getCommitActionButton(); - } - - // Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled) - return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton(); - } - private getCommitActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true }); @@ -133,34 +240,27 @@ export class ActionButtonCommand { return commandGroups; } - private getPublishBranchActionButton(): SourceControlActionButton | undefined { + protected override getPublishBranchActionButton(): SourceControlActionButton | undefined { + const scmConfig = workspace.getConfiguration('scm'); + if (scmConfig.get('experimental.showSyncView', false)) { + return undefined; + } + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); // Not a branch (tag, detached), branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled if (this.state.HEAD?.type === RefType.Tag || !this.state.HEAD?.name || this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; } - // Button icon - const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)'; - - return { - command: { - command: 'git.publish', - title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }), - tooltip: this.state.isSyncInProgress ? - (this.state.HEAD?.name ? - l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : - l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) : - (this.repository.HEAD?.name ? - l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : - l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })), - arguments: [this.repository.sourceControl], - }, - enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress - }; + return super.getPublishBranchActionButton(); } - private getSyncChangesActionButton(): SourceControlActionButton | undefined { + protected override getSyncChangesActionButton(): SourceControlActionButton | undefined { + const scmConfig = workspace.getConfiguration('scm'); + if (scmConfig.get('experimental.showSyncView', false)) { + return undefined; + } + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0; @@ -168,40 +268,7 @@ export class ActionButtonCommand { // Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; } - const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; - const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; - const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)'; - - return { - command: { - command: 'git.sync', - title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead), - tooltip: this.state.isSyncInProgress ? - l10n.t('Synchronizing Changes...') - : this.repository.syncTooltip, - arguments: [this.repository.sourceControl], - }, - description: `${icon}${behind}${ahead}`, - enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress - }; - } - - private onDidChangeOperations(): void { - const isCheckoutInProgress - = this.repository.operations.isRunning(OperationKind.Checkout) || - this.repository.operations.isRunning(OperationKind.CheckoutTracking); - - const isCommitInProgress = - this.repository.operations.isRunning(OperationKind.Commit) || - this.repository.operations.isRunning(OperationKind.PostCommitCommand) || - this.repository.operations.isRunning(OperationKind.RebaseContinue); - - const isSyncInProgress = - this.repository.operations.isRunning(OperationKind.Sync) || - this.repository.operations.isRunning(OperationKind.Push) || - this.repository.operations.isRunning(OperationKind.Pull); - - this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress }; + return super.getSyncChangesActionButton(); } private onDidChangeSmartCommitSettings(): void { @@ -210,43 +277,30 @@ export class ActionButtonCommand { repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() }; } +} - private onDidRunGitStatus(): void { - this.state = { - ...this.state, - HEAD: this.repository.HEAD, - isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0, - isRebaseInProgress: !!this.repository.rebaseCommit, - repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() - }; +export class SyncActionButton extends AbstractActionButton { + override get button(): SourceControlActionButton | undefined { + if (!this.state.HEAD) { return undefined; } + + // Publish Branch -> Sync Changes + return this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton(); } - private repositoryHasChangesToCommit(): boolean { - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const enableSmartCommit = config.get('enableSmartCommit') === true; - const suggestSmartCommit = config.get('suggestSmartCommit') === true; - const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all'); + constructor(repository: Repository) { + super(repository); - const resources = [...this.repository.indexGroup.resourceStates]; - - if ( - // Smart commit enabled (all) - (enableSmartCommit && smartCommitChanges === 'all') || - // Smart commit disabled, smart suggestion enabled - (!enableSmartCommit && suggestSmartCommit) - ) { - resources.push(...this.repository.workingTreeGroup.resourceStates); - } - - // Smart commit enabled (tracked only) - if (enableSmartCommit && smartCommitChanges === 'tracked') { - resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED)); - } - - return resources.length !== 0; + this.disposables.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('scm.experimental.showSyncView')) { + this._onDidChange.fire(); + } + })); } - dispose(): void { - this.disposables = dispose(this.disposables); + protected override getPublishBranchActionButton(): SourceControlActionButton | undefined { + // Not a branch (tag, detached), branch does have an upstream + if (this.state.HEAD?.type === RefType.Tag || this.state.HEAD?.upstream) { return undefined; } + + return super.getPublishBranchActionButton(); } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 05af77899f0..d972ce59f7e 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -131,6 +131,8 @@ export interface LogOptions { readonly path?: string; /** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */ readonly range?: string; + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; } export interface CommitOptions { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 502e8de947b..f8e080e76fd 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1023,17 +1023,24 @@ export class Repository { } async log(options?: LogOptions): Promise { - const maxEntries = options?.maxEntries ?? 32; - const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z']; + const args = ['log', `--format=${COMMIT_FORMAT}`, '-z']; + + if (options?.reverse) { + args.push('--reverse', '--ancestry-path'); + } + + if (options?.sortByAuthorDate) { + args.push('--author-date-order'); + } if (options?.range) { args.push(options.range); + } else { + args.push(`-n${options?.maxEntries ?? 32}`); } - args.push('--'); - if (options?.path) { - args.push(options.path); + args.push('--', options.path); } const result = await this.exec(args); @@ -1258,7 +1265,7 @@ export class Repository { diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; - diffIndexWithHEAD(path?: string | undefined): Promise; + diffIndexWithHEAD(path?: string | undefined): Promise; async diffIndexWithHEAD(path?: string): Promise { if (!path) { return await this.diffFiles(true); @@ -1303,6 +1310,17 @@ export class Repository { return result.stdout.trim(); } + async diffBetweenShortStat(ref1: string, ref2: string): Promise { + const args = ['diff', '--shortstat', `${ref1}...${ref2}`]; + + const result = await this.exec(args); + if (result.exitCode) { + return ''; + } + + return result.stdout.trim(); + } + private async diffFiles(cached: boolean, ref?: string): Promise { const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR']; if (cached) { @@ -2450,6 +2468,15 @@ export class Repository { return Promise.reject(new Error('No such branch')); } + async getDefaultBranch(): Promise { + const result = await this.exec(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']); + if (!result.stdout) { + throw new Error('No default branch'); + } + + return this.getBranch(result.stdout.trim()); + } + // TODO: Support core.commentChar stripCommitMessageComments(message: string): string { return message.replace(/^\s*#.*$\n?/gm, '').trim(); @@ -2510,6 +2537,13 @@ export class Repository { return commits[0]; } + async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { + const result = await this.exec(['rev-list', '--count', '--left-right', range]); + const [ahead, behind] = result.stdout.trim().split('\t'); + + return { ahead: Number(ahead) || 0, behind: Number(behind) || 0 }; + } + async updateSubmodules(paths: string[]): Promise { const args = ['submodule', 'update']; diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts new file mode 100644 index 00000000000..3e3862ae65f --- /dev/null +++ b/extensions/git/src/historyProvider.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { Disposable, Event, EventEmitter, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon } from 'vscode'; +import { Repository } from './repository'; +import { IDisposable } from './util'; +import { toGitUri } from './uri'; +import { SyncActionButton } from './actionButton'; + +export class GitHistoryProvider implements SourceControlHistoryProvider, IDisposable { + + private readonly _onDidChangeActionButton = new EventEmitter(); + readonly onDidChangeActionButton: Event = this._onDidChangeActionButton.event; + + private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); + readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; + + private _actionButton: SourceControlActionButton | undefined; + get actionButton(): SourceControlActionButton | undefined { return this._actionButton; } + set actionButton(button: SourceControlActionButton | undefined) { + this._actionButton = button; + this._onDidChangeActionButton.fire(); + } + + private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined; + + get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } + set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) { + this._currentHistoryItemGroup = value; + this._onDidChangeCurrentHistoryItemGroup.fire(); + } + + private disposables: Disposable[] = []; + + constructor(protected readonly repository: Repository) { + const actionButton = new SyncActionButton(repository); + this.actionButton = actionButton.button; + this.disposables.push(actionButton); + + this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this)); + this.disposables.push(actionButton.onDidChange(() => this.actionButton = actionButton.button)); + } + + private async onDidRunGitStatus(): Promise { + if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit) { return; } + + this.currentHistoryItemGroup = { + id: `refs/heads/${this.repository.HEAD.name}`, + label: this.repository.HEAD.name, + upstream: this.repository.HEAD.upstream ? + { + 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 + }; + } + + async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise { + //TODO@lszomoru - support limit and cursor + if (typeof options.limit === 'number') { + throw new Error('Unsupported options.'); + } + if (typeof options.limit?.id !== 'string') { + throw new Error('Unsupported options.'); + } + + const optionsRef = options.limit.id; + const [commits, summary] = await Promise.all([ + this.repository.log({ range: `${optionsRef}..${historyItemGroupId}`, sortByAuthorDate: true }), + this.getSummaryHistoryItem(optionsRef, historyItemGroupId) + ]); + + const historyItems = commits.length === 0 ? [] : [summary]; + historyItems.push(...commits.map(commit => { + const newLineIndex = commit.message.indexOf('\n'); + const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message; + + return { + id: commit.hash, + parentIds: commit.parents, + label: subject, + description: commit.authorName, + icon: new ThemeIcon('account'), + timestamp: commit.authorDate?.getTime() + }; + })); + + return historyItems; + } + + async provideHistoryItemChanges(historyItemId: string): Promise { + const [ref1, ref2] = historyItemId.includes('..') + ? historyItemId.split('..') + : [`${historyItemId}^`, historyItemId]; + + const changes = await this.repository.diffBetween(ref1, ref2); + + return changes.map(change => ({ + uri: change.uri.with({ query: `ref=${historyItemId}` }), + originalUri: toGitUri(change.originalUri, ref1), + modifiedUri: toGitUri(change.originalUri, ref2), + renameUri: change.renameUri, + })); + } + + async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { + refId2 = refId2 ?? (await this.repository.getDefaultBranch()).name ?? ''; + if (refId2 === '') { + return undefined; + } + + const ancestor = await this.repository.getMergeBase(refId1, refId2); + if (ancestor === '') { + return undefined; + } + + const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`); + return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; + } + + private async getSummaryHistoryItem(ref1: string, ref2: string): Promise { + const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2); + return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: 'Changes', description: diffShortStat }; + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index b385acb11c5..29eff86a59b 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -53,6 +53,7 @@ export const enum OperationKind { RebaseContinue = 'RebaseContinue', RevertFiles = 'RevertFiles', RevertFilesNoProgress = 'RevertFilesNoProgress', + RevList = 'RevList', SetBranchUpstream = 'SetBranchUpstream', Show = 'Show', Stage = 'Stage', @@ -69,8 +70,9 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | - ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | SetBranchUpstreamOperation | - ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation; + ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation | + SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | + TagOperation; type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean }; export type AddOperation = BaseOperation & { kind: OperationKind.Add }; @@ -116,6 +118,7 @@ export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase }; export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort }; export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue }; export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles }; +export type RevListOperation = BaseOperation & { kind: OperationKind.RevList }; export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream }; export type ShowOperation = BaseOperation & { kind: OperationKind.Show }; export type StageOperation = BaseOperation & { kind: OperationKind.Stage }; @@ -169,6 +172,7 @@ export const Operation = { RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation, RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation, RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation), + RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation, SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation, Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation, Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ea4d6cbe56f..16b1e84be0c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -19,10 +19,11 @@ import { IFileWatcher, watch } from './watch'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; -import { ActionButtonCommand } from './actionButton'; +import { CommitActionButton } from './actionButton'; import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands'; import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; +import { GitHistoryProvider } from './historyProvider'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -833,8 +834,13 @@ export class Repository implements Disposable { const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); - this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; + + const historyProvider = new GitHistoryProvider(this); + this._sourceControl.historyProvider = historyProvider; + this.disposables.push(historyProvider); + + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); @@ -921,10 +927,10 @@ export class Repository implements Disposable { this.commitCommandCenter = new CommitCommandsCenter(globalState, this, postCommitCommandsProviderRegistry); this.disposables.push(this.commitCommandCenter); - const actionButton = new ActionButtonCommand(this, this.commitCommandCenter); - this.disposables.push(actionButton); - actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button); - this._sourceControl.actionButton = actionButton.button; + const commitActionButton = new CommitActionButton(this, this.commitCommandCenter); + this.disposables.push(commitActionButton); + commitActionButton.onDidChange(() => this._sourceControl.actionButton = commitActionButton.button); + this._sourceControl.actionButton = commitActionButton.button; const progressManager = new ProgressManager(this); this.disposables.push(progressManager); @@ -1115,6 +1121,10 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path)); } + diffBetweenShortStat(ref1: string, ref2: string): Promise { + return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2)); + } + getMergeBase(ref1: string, ref2: string): Promise { return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2)); } @@ -1421,6 +1431,10 @@ export class Repository implements Disposable { await this.run(Operation.Move, () => this.repository.move(from, to)); } + async getDefaultBranch(): Promise { + return await this.run(Operation.GetBranch, () => this.repository.getDefaultBranch()); + } + async getBranch(name: string): Promise { return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } @@ -1506,6 +1520,10 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } + async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { + return await this.run(Operation.RevList, () => this.repository.getCommitCount(range)); + } + async reset(treeish: string, hard?: boolean): Promise { await this.run(Operation.Reset, () => this.repository.reset(treeish, hard)); } diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index c62c25401f2..d5fdbd539da 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -12,6 +12,7 @@ "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 8f7c4742fea..67f6b0d5078 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType, ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm'; -import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemDto, SCMActionButtonDto, SCMHistoryItemGroupDto } from '../common/extHost.protocol'; import { Command } from 'vs/editor/common/languages'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ISplice, Sequence } from 'vs/base/common/sequence'; @@ -16,6 +16,20 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; + +function getSCMHistoryItemIcon(historyItem: SCMHistoryItemDto): URI | { light: URI; dark: URI } | ThemeIcon | undefined { + if (!historyItem.icon) { + return undefined; + } else if (URI.isUri(historyItem.icon)) { + return URI.revive(historyItem.icon); + } else if (ThemeIcon.isThemeIcon(historyItem.icon)) { + return historyItem.icon; + } else { + const icon = historyItem.icon as { light: UriComponents; dark: UriComponents }; + return { light: URI.revive(icon.light), dark: URI.revive(icon.dark) }; + } +} class MainThreadSCMResourceGroup implements ISCMResourceGroup { @@ -121,6 +135,7 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { get contextValue(): string { return this._contextValue; } get commitTemplate(): string { return this.features.commitTemplate || ''; } + get historyProvider(): ISCMHistoryProvider | undefined { return this._historyProvider; } get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; } get actionButton(): ISCMActionButtonDescriptor | undefined { return this.features.actionButton ?? undefined; } get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; } @@ -132,12 +147,22 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { private readonly _onDidChangeStatusBarCommands = new Emitter(); get onDidChangeStatusBarCommands(): Event { return this._onDidChangeStatusBarCommands.event; } + private readonly _onDidChangeHistoryProviderActionButton = new Emitter(); + readonly onDidChangeHistoryProviderActionButton: Event = this._onDidChangeHistoryProviderActionButton.event; + + private readonly _onDidChangeHistoryProviderCurrentHistoryItemGroup = new Emitter(); + readonly onDidChangeHistoryProviderCurrentHistoryItemGroup: Event = this._onDidChangeHistoryProviderCurrentHistoryItemGroup.event; + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; private _quickDiff: IDisposable | undefined; public readonly isSCM: boolean = true; + private _historyProvider: ISCMHistoryProvider | undefined; + private _historyProviderActionButton: SCMActionButtonDto | undefined | null; + private _historyProviderCurrentHistoryItemGroup: SCMHistoryItemGroupDto | undefined; + constructor( private readonly proxy: ExtHostSCMShape, private readonly _handle: number, @@ -280,6 +305,45 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { return result && URI.revive(result); } + $registerHistoryProvider(): void { + this._historyProvider = { + actionButton: () => this._historyProviderActionButton ?? undefined, + currentHistoryItemGroup: () => this._historyProviderCurrentHistoryItemGroup ?? undefined, + provideHistoryItems: (historyItemGroupId, options) => this.provideHistoryItems(historyItemGroupId, options), + provideHistoryItemChanges: (historyItemId: string) => this.provideHistoryItemChanges(historyItemId), + resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1, historyItemGroupId2) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), + }; + } + + $onDidChangeHistoryProviderActionButton(actionButton?: SCMActionButtonDto | null): void { + this._historyProviderActionButton = actionButton; + this._onDidChangeHistoryProviderActionButton.fire(); + } + + $onDidChangeHistoryProviderCurrentHistoryItemGroup(currentHistoryItemGroup?: SCMHistoryItemGroupDto): void { + this._historyProviderCurrentHistoryItemGroup = currentHistoryItemGroup; + this._onDidChangeHistoryProviderCurrentHistoryItemGroup.fire(); + } + + async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { + return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); + } + + async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise { + const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None); + return historyItems?.map(historyItem => ({ ...historyItem, icon: getSCMHistoryItemIcon(historyItem), })); + } + + async provideHistoryItemChanges(historyItemId: string): Promise { + const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, CancellationToken.None); + return changes?.map(change => ({ + uri: URI.revive(change.uri), + originalUri: change.originalUri && URI.revive(change.originalUri), + modifiedUri: change.modifiedUri && URI.revive(change.modifiedUri), + renameUri: change.renameUri && URI.revive(change.renameUri) + })); + } + toJSON(): any { return { $mid: MarshalledId.ScmProvider, @@ -486,4 +550,37 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.validateInput = async () => undefined; } } + + $registerHistoryProvider(sourceControlHandle: number): void { + const repository = this._repositories.get(sourceControlHandle); + + if (!repository) { + return; + } + + const provider = repository.provider as MainThreadSCMProvider; + provider.$registerHistoryProvider(); + } + + $onDidChangeHistoryProviderActionButton(sourceControlHandle: number, actionButton?: SCMActionButtonDto | null | undefined): void { + const repository = this._repositories.get(sourceControlHandle); + + if (!repository) { + return; + } + + const provider = repository.provider as MainThreadSCMProvider; + provider.$onDidChangeHistoryProviderActionButton(actionButton); + } + + $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): void { + const repository = this._repositories.get(sourceControlHandle); + + if (!repository) { + return; + } + + const provider = repository.provider as MainThreadSCMProvider; + provider.$onDidChangeHistoryProviderCurrentHistoryItemGroup(historyItemGroup); + } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b6789136034..eb64a9843b8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1401,6 +1401,28 @@ export type SCMRawResourceSplices = [ SCMRawResourceSplice[] ]; +export interface SCMHistoryItemGroupDto { + readonly id: string; + readonly label: string; + readonly upstream?: SCMHistoryItemGroupDto; +} + +export interface SCMHistoryItemDto { + readonly id: string; + readonly parentIds: string[]; + readonly label: string; + readonly description?: string; + readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; + readonly timestamp?: number; +} + +export interface SCMHistoryItemChangeDto { + readonly uri: UriComponents; + readonly originalUri: UriComponents | undefined; + readonly modifiedUri: UriComponents | undefined; + readonly renameUri: UriComponents | undefined; +} + export interface MainThreadSCMShape extends IDisposable { $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): void; $updateSourceControl(handle: number, features: SCMProviderFeatures): void; @@ -1419,6 +1441,10 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; + + $registerHistoryProvider(sourceControlHandle: number): void; + $onDidChangeHistoryProviderActionButton(sourceControlHandle: number, actionButton?: SCMActionButtonDto | null): void; + $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): void; } export interface MainThreadQuickDiffShape extends IDisposable { @@ -2126,6 +2152,9 @@ export interface ExtHostSCMShape { $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise; $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; + $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; + $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; + $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } export interface ExtHostQuickDiffShape { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index a8d22102648..775c243a1dc 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import type * as vscode from 'vscode'; @@ -45,6 +45,19 @@ function getIconResource(decorations?: vscode.SourceControlResourceThemableDecor } } +function getHistoryItemIconDto(historyItem: vscode.SourceControlHistoryItem): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined { + if (!historyItem.icon) { + return undefined; + } else if (URI.isUri(historyItem.icon)) { + return historyItem.icon; + } else if (ThemeIcon.isThemeIcon(historyItem.icon)) { + return historyItem.icon; + } else { + const icon = historyItem.icon as { light: URI; dark: URI }; + return { light: icon.light, dark: icon.dark }; + } +} + function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number { if (!a.iconPath && !b.iconPath) { return 0; @@ -197,6 +210,18 @@ function commandListEquals(a: readonly vscode.Command[], b: readonly vscode.Comm return equals(a, b, commandEquals); } +function historyItemGroupEquals(a: vscode.SourceControlHistoryItemGroup | undefined, b: vscode.SourceControlHistoryItemGroup | undefined): boolean { + if (a === b) { + return true; + } + + if (!a || !b) { + return false; + } + + return a.id === b.id && a.label === b.label && a.upstream?.id === b.upstream?.id && a.upstream?.label === b.upstream?.label; +} + export interface IValidateInput { (value: string, cursorPosition: number): vscode.ProviderResult; } @@ -507,6 +532,52 @@ class ExtHostSourceControl implements vscode.SourceControl { this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider, quickDiffLabel }); } + private _historyProvider: vscode.SourceControlHistoryProvider | undefined; + private _historyProviderDisposable = new MutableDisposable(); + private _historyProviderCurrentHistoryItemGroup: vscode.SourceControlHistoryItemGroup | undefined; + private _historyProviderActionButtonDisposable = new MutableDisposable(); + + get historyProvider(): vscode.SourceControlHistoryProvider | undefined { + checkProposedApiEnabled(this._extension, 'scmHistoryProvider'); + return this._historyProvider; + } + + set historyProvider(historyProvider: vscode.SourceControlHistoryProvider | undefined) { + checkProposedApiEnabled(this._extension, 'scmHistoryProvider'); + + this._historyProvider = historyProvider; + this._historyProviderDisposable.value = new DisposableStore(); + + if (!historyProvider) { + return; + } + + this.#proxy.$registerHistoryProvider(this.handle); + + this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + if (historyItemGroupEquals(this._historyProviderCurrentHistoryItemGroup, historyProvider?.currentHistoryItemGroup)) { + return; + } + + this._historyProviderCurrentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; + this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup); + })); + + this._historyProviderDisposable.value.add(historyProvider.onDidChangeActionButton(() => { + checkProposedApiEnabled(this._extension, 'scmActionButton'); + + this._historyProviderActionButtonDisposable.value = new DisposableStore(); + const internal = historyProvider.actionButton !== undefined ? + { + command: this._commands.converter.toInternal(historyProvider.actionButton.command, this._historyProviderActionButtonDisposable.value), + description: historyProvider.actionButton.description, + enabled: historyProvider.actionButton.enabled + } : undefined; + + this.#proxy.$onDidChangeHistoryProviderActionButton(this.handle, internal ?? null); + })); + } + private _commitTemplate: string | undefined = undefined; get commitTemplate(): string | undefined { @@ -882,4 +953,28 @@ export class ExtHostSCM implements ExtHostSCMShape { this._selectedSourceControlHandle = selectedSourceControlHandle; return Promise.resolve(undefined); } + + async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; + } + + async $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + const historyItems = await historyProvider?.provideHistoryItems(historyItemGroupId, options, token); + + return historyItems?.map(item => ({ + id: item.id, + parentIds: item.parentIds, + label: item.label, + description: item.description, + icon: getHistoryItemIconDto(item), + timestamp: item.timestamp, + })) ?? undefined; + } + + async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + return await historyProvider?.provideHistoryItemChanges(historyItemId, token) ?? undefined; + } } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 2478d508c61..f937d75dd31 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -110,12 +110,50 @@ line-height: 22px; } +.scm-view .monaco-list-row .history, +.scm-view .monaco-list-row .history-item-group, .scm-view .monaco-list-row .resource-group { display: flex; height: 100%; align-items: center; } +.scm-view .monaco-list-row .history-item-group .monaco-highlighted-label { + display: flex; + align-items: center; +} + +.scm-view .monaco-list-row .history-item-group .monaco-icon-label, +.scm-view .monaco-list-row .history-item .monaco-icon-label { + flex-grow: 1; + align-items: center; +} + +.scm-view .monaco-list-row .history-item-group .monaco-icon-label > .monaco-icon-label-container { + display: flex; +} + +.scm-sync-view .monaco-list-row .monaco-icon-label .icon-container +.scm-sync-view .monaco-list-row .monaco-icon-label .icon-container { + display: flex; + font-size: 14px; + padding-right: 4px; +} + +.scm-sync-view .monaco-list-row .history-item .monaco-icon-label .icon-container { + display: flex; + font-size: 14px; + padding-right: 4px; +} + +.scm-sync-view .monaco-list-row .history-item .monaco-icon-label .avatar { + width: 14px; + height: 14px; + border-radius: 14px; +} + +.scm-view .monaco-list-row .history > .name, +.scm-view .monaco-list-row .history-item-group > .name, .scm-view .monaco-list-row .resource-group > .name { flex: 1; overflow: hidden; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index f78bcd673ab..9f9c56695d7 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; 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 { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID, SYNC_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'; @@ -32,6 +32,7 @@ import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/sug import { MANAGE_TRUST_COMMAND_ID, WorkspaceTrustContext } from 'vs/workbench/contrib/workspace/common/workspace'; import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; +import { SCMSyncViewPane } from 'vs/workbench/contrib/scm/browser/scmSyncViewPane'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -79,7 +80,7 @@ viewsRegistry.registerViews([{ ctorDescriptor: new SyncDescriptor(SCMViewPane), canToggleVisibility: true, canMoveView: true, - weight: 80, + weight: 60, order: -999, containerIcon: sourceControlViewIcon, openCommandActionDescriptor: { @@ -109,6 +110,17 @@ viewsRegistry.registerViews([{ containerIcon: sourceControlViewIcon }], viewContainer); +viewsRegistry.registerViews([{ + id: SYNC_VIEW_PANE_ID, + name: localize('source control sync', "Source Control Sync"), + ctorDescriptor: new SyncDescriptor(SCMSyncViewPane), + canToggleVisibility: false, + canMoveView: true, + weight: 20, + order: -998, + when: ContextKeyExpr.equals('config.scm.experimental.showSyncView', true), +}], viewContainer); + Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMActiveResourceContextKeyController, LifecyclePhase.Restored); @@ -279,6 +291,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', markdownDescription: localize('showActionButton', "Controls whether an action button can be shown in the Source Control view."), default: true + }, + 'scm.experimental.showSyncView': { + type: 'boolean', + description: localize('showSyncView', "Controls whether the Source Control Sync view is shown."), + default: false } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts new file mode 100644 index 00000000000..82030e97e49 --- /dev/null +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -0,0 +1,584 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'vs/base/common/path'; +import { append, $, prepend } from 'vs/base/browser/dom'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IAsyncDataSource, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { DisposableStore, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +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 { IOpenEvent, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { defaultCountBadgeStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; +import { ActionButtonRenderer } from 'vs/workbench/contrib/scm/browser/scmViewPane'; +import { getActionViewItemProvider, isSCMActionButton, isSCMRepository, isSCMRepositoryArray } from 'vs/workbench/contrib/scm/browser/util'; +import { ISCMActionButton, ISCMRepository, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent } from 'vs/workbench/contrib/scm/common/scm'; +import { comparePaths } from 'vs/base/common/comparers'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup } from 'vs/workbench/contrib/scm/common/history'; +import { localize } from 'vs/nls'; +import { Iterable } from 'vs/base/common/iterator'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; + +type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement; + +function isSCMHistoryItemGroupTreeElement(obj: any): obj is SCMHistoryItemGroupTreeElement { + return (obj as SCMHistoryItemGroupTreeElement).type === 'historyItemGroup'; +} + +function isSCMHistoryItemTreeElement(obj: any): obj is SCMHistoryItemTreeElement { + return (obj as SCMHistoryItemTreeElement).type === 'historyItem'; +} + +function isSCMHistoryItemChangeTreeElement(obj: any): obj is SCMHistoryItemChangeTreeElement { + return (obj as SCMHistoryItemChangeTreeElement).type === 'historyItemChange'; +} + +function toDiffEditorArguments(uri: URI, originalUri: URI, modifiedUri: URI): unknown[] { + const basename = path.basename(uri.fsPath); + const originalQuery = JSON.parse(originalUri.query) as { path: string; ref: string }; + const modifiedQuery = JSON.parse(modifiedUri.query) as { path: string; ref: string }; + + const originalShortRef = originalQuery.ref.substring(0, 8).concat(originalQuery.ref.endsWith('^') ? '^' : ''); + const modifiedShortRef = modifiedQuery.ref.substring(0, 8).concat(modifiedQuery.ref.endsWith('^') ? '^' : ''); + + return [originalUri, modifiedUri, `${basename} (${originalShortRef}) ↔ ${basename} (${modifiedShortRef})`]; +} + +function getSCMResourceId(element: TreeElement): string { + if (isSCMRepository(element)) { + const provider = element.provider; + return `repo:${provider.id}`; + } else if (isSCMActionButton(element)) { + const provider = element.repository.provider; + return `actionButton:${provider.id}`; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + const provider = element.repository.provider; + return `historyItemGroup:${provider.id}/${element.id}`; + } else if (isSCMHistoryItemTreeElement(element)) { + const historyItemGroup = element.historyItemGroup; + const provider = historyItemGroup.repository.provider; + return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}`; + } else if (isSCMHistoryItemChangeTreeElement(element)) { + const historyItem = element.historyItem; + const historyItemGroup = historyItem.historyItemGroup; + const provider = historyItemGroup.repository.provider; + return `historyItemChange:${provider.id}/${historyItemGroup.id}/${historyItem.id}/${element.uri.toString()}`; + } else { + throw new Error('Invalid tree element'); + } +} + +interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup { + readonly description?: string; + readonly ancestor?: string; + readonly count?: number; + readonly repository: ISCMRepository; + readonly type: 'historyItemGroup'; +} + +interface SCMHistoryItemTreeElement extends ISCMHistoryItem { + readonly historyItemGroup: SCMHistoryItemGroupTreeElement; + readonly type: 'historyItem'; +} + +interface SCMHistoryItemChangeTreeElement extends ISCMHistoryItemChange { + readonly historyItem: SCMHistoryItemTreeElement; + readonly type: 'historyItemChange'; +} + +class ListDelegate implements IListVirtualDelegate { + + getHeight(element: any): number { + if (isSCMActionButton(element)) { + return ActionButtonRenderer.DEFAULT_HEIGHT + 10; + } else { + return 22; + } + } + + getTemplateId(element: any): string { + if (isSCMRepository(element)) { + return RepositoryRenderer.TEMPLATE_ID; + } else if (isSCMActionButton(element)) { + return ActionButtonRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return HistoryItemGroupRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemTreeElement(element)) { + return HistoryItemRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemChangeTreeElement(element)) { + return HistoryItemChangeRenderer.TEMPLATE_ID; + } else { + throw new Error('Invalid tree element'); + } + } +} + +interface HistoryItemGroupTemplate { + readonly label: IconLabel; + readonly count: CountBadge; + readonly disposables: IDisposable; +} + +class HistoryItemGroupRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'history-item-group'; + get templateId(): string { return HistoryItemGroupRenderer.TEMPLATE_ID; } + + renderTemplate(container: HTMLElement) { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + + const element = append(container, $('.history-item-group')); + const label = new IconLabel(element, { supportIcons: true }); + const countContainer = append(element, $('.count')); + const count = new CountBadge(countContainer, {}, defaultCountBadgeStyles); + + return { label, count, disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { + const historyItemGroup = node.element; + templateData.label.setLabel(historyItemGroup.label, historyItemGroup.description); + templateData.count.setCount(historyItemGroup.count ?? 0); + } + + disposeTemplate(templateData: HistoryItemGroupTemplate): void { + templateData.disposables.dispose(); + } +} + +interface HistoryItemTemplate { + readonly iconContainer: HTMLElement; + // readonly avatarImg: HTMLImageElement; + readonly iconLabel: IconLabel; + readonly timestampContainer: HTMLElement; + readonly timestamp: HTMLSpanElement; + readonly disposables: IDisposable; +} + +class HistoryItemRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'history-item'; + get templateId(): string { return HistoryItemRenderer.TEMPLATE_ID; } + + renderTemplate(container: HTMLElement): HistoryItemTemplate { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + + const element = append(container, $('.history-item')); + const iconLabel = new IconLabel(element, { supportIcons: true }); + + const iconContainer = prepend(iconLabel.element, $('.icon-container')); + // const avatarImg = append(iconContainer, $('img.avatar')) as HTMLImageElement; + + const timestampContainer = append(iconLabel.element, $('.timestamp-container')); + const timestamp = append(timestampContainer, $('span.timestamp')); + + return { iconContainer, iconLabel, timestampContainer, timestamp, disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + const historyItem = node.element; + + templateData.iconContainer.className = 'icon-container'; + if (historyItem.icon && ThemeIcon.isThemeIcon(historyItem.icon)) { + templateData.iconContainer.classList.add(...ThemeIcon.asClassNameArray(historyItem.icon)); + } + + // if (commit.authorAvatar) { + // templateData.avatarImg.src = commit.authorAvatar; + // templateData.avatarImg.style.display = 'block'; + // templateData.iconContainer.classList.remove(...ThemeIcon.asClassNameArray(Codicon.account)); + // } else { + // templateData.avatarImg.style.display = 'none'; + // templateData.iconContainer.classList.add(...ThemeIcon.asClassNameArray(Codicon.account)); + // } + + templateData.iconLabel.setLabel(historyItem.label, historyItem.description); + + // templateData.timestampContainer.classList.toggle('timestamp-duplicate', commit.hideTimestamp === true); + // templateData.timestamp.textContent = fromNow(commit.timestamp); + } + + disposeTemplate(templateData: HistoryItemTemplate): void { + templateData.disposables.dispose(); + } +} + +interface HistoryItemChangeTemplate { + readonly element: HTMLElement; + readonly name: HTMLElement; + readonly fileLabel: IResourceLabel; + readonly decorationIcon: HTMLElement; + readonly disposables: IDisposable; +} + +class HistoryItemChangeRenderer implements ITreeRenderer { + + static readonly TEMPLATE_ID = 'historyItemChange'; + get templateId(): string { return HistoryItemChangeRenderer.TEMPLATE_ID; } + + constructor(private labels: ResourceLabels) { } + + renderTemplate(container: HTMLElement): HistoryItemChangeTemplate { + const element = append(container, $('.change')); + const name = append(element, $('.name')); + const fileLabel = this.labels.create(name, { supportDescriptionHighlights: true, supportHighlights: true }); + const decorationIcon = append(element, $('.decoration-icon')); + + return { element, name, fileLabel, decorationIcon, disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void { + templateData.fileLabel.setFile(node.element.uri, { + fileDecorations: { colors: false, badges: true }, + hidePath: false, + }); + } + + disposeTemplate(templateData: HistoryItemChangeTemplate): void { + templateData.disposables.dispose(); + } +} + +class SCMSyncViewPaneAccessibilityProvider implements IListAccessibilityProvider { + + getAriaLabel(element: TreeElement): string { + // TODO - add aria labels + return ''; + } + getWidgetAriaLabel(): string { + return localize('scmSync', 'Source Control Sync'); + } + +} + +class SCMSyncViewPaneTreeIdentityProvider implements IIdentityProvider { + + getId(element: TreeElement): string { + return getSCMResourceId(element); + } + +} + +class SCMSyncViewPaneTreeSorter implements ITreeSorter { + + compare(element: TreeElement, otherElement: TreeElement): number { + // Repository + if (isSCMRepository(element)) { + if (!isSCMRepository(otherElement)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + + // Action button + if (isSCMActionButton(element)) { + return -1; + } else if (isSCMActionButton(otherElement)) { + return 1; + } + + // History item group + if (isSCMHistoryItemGroupTreeElement(element)) { + if (!isSCMHistoryItemGroupTreeElement(otherElement)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + + // History item + if (isSCMHistoryItemTreeElement(element)) { + if (!isSCMHistoryItemTreeElement(otherElement)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + + // History item change + const elementPath = (element as SCMHistoryItemChangeTreeElement).uri.fsPath; + const otherElementPath = (otherElement as SCMHistoryItemChangeTreeElement).uri.fsPath; + + return comparePaths(elementPath, otherElementPath); + } +} + +export class SCMSyncViewPane extends ViewPane { + + private listLabels!: ResourceLabels; + private _tree!: WorkbenchAsyncDataTree; + + private _viewModel!: SCMSyncPaneViewModel; + get viewModel(): SCMSyncPaneViewModel { return this._viewModel; } + + private readonly disposables = new DisposableStore(); + + constructor( + options: IViewPaneOptions, + @ICommandService private commandService: ICommandService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + } + + protected override renderBody(container: HTMLElement): void { + super.renderBody(container); + + const treeContainer = append(container, $('.scm-view.scm-sync-view.file-icon-themable-tree')); + + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); + this._register(this.listLabels); + + this._tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'SCM Sync View', + treeContainer, + new ListDelegate(), + [ + this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(ActionButtonRenderer), + this.instantiationService.createInstance(HistoryItemGroupRenderer), + this.instantiationService.createInstance(HistoryItemRenderer), + this.instantiationService.createInstance(HistoryItemChangeRenderer, this.listLabels), + ], + this.instantiationService.createInstance(SCMSyncDataSource), + { + horizontalScrolling: false, + accessibilityProvider: new SCMSyncViewPaneAccessibilityProvider(), + identityProvider: new SCMSyncViewPaneTreeIdentityProvider(), + sorter: new SCMSyncViewPaneTreeSorter(), + }) as WorkbenchAsyncDataTree; + + this._register(this._tree); + this._register(this._tree.onDidOpen(this.onDidOpen, this)); + + this._viewModel = this.instantiationService.createInstance(SCMSyncPaneViewModel, this._tree); + } + + private async onDidOpen(e: IOpenEvent): Promise { + if (!e.element) { + return; + } else if (isSCMHistoryItemChangeTreeElement(e.element)) { + if (e.element.originalUri && e.element.modifiedUri) { + await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri)); + } + } + } + + override dispose(): void { + this.disposables.dispose(); + super.dispose(); + } +} + +class SCMSyncPaneViewModel { + + private repositories = new Map(); + private alwaysShowRepositories = false; + + private disposables = new DisposableStore(); + + constructor( + private readonly tree: WorkbenchAsyncDataTree, + @ISCMViewService scmViewService: ISCMViewService, + @IConfigurationService private readonly configurationService: IConfigurationService, + + ) { + configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + this.onDidChangeConfiguration(); + + scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.disposables); + this._onDidChangeVisibleRepositories({ added: scmViewService.visibleRepositories, removed: [] }); + } + + private onDidChangeConfiguration(e?: IConfigurationChangeEvent): void { + if (!e || e.affectsConfiguration('scm.alwaysShowRepositories')) { + this.alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); + this.refresh(); + } + } + + private _onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { + for (const repository of added) { + const repositoryDisposable: IDisposable = combinedDisposable( + repository.provider.onDidChangeHistoryProviderActionButton(() => this.refresh(repository)), + repository.provider.onDidChangeHistoryProviderCurrentHistoryItemGroup(() => this.refresh(repository)) + ); + + this.repositories.set(repository, { dispose() { repositoryDisposable.dispose(); } }); + } + + for (const repository of removed) { + this.repositories.get(repository)?.dispose(); + this.repositories.delete(repository); + } + + this.refresh(); + } + + private async refresh(repository?: ISCMRepository): Promise { + if (this.repositories.size === 0) { + return; + } + + if (repository) { + // Particular repository + await this.tree.updateChildren(repository); + } else if (this.repositories.size === 1 && !this.alwaysShowRepositories) { + // Single repository and not always show repositories + await this.tree.setInput(Iterable.first(this.repositories.keys())!); + } else { + // Expand repository nodes + const expanded = Array.from(this.repositories.keys()) + .map(repository => `repo:${repository.provider.id}`); + + // Multiple repositories or always show repositories + await this.tree.setInput([...this.repositories.keys()], { expanded }); + } + this.tree.layout(); + } +} + +class SCMSyncDataSource implements IAsyncDataSource { + + hasChildren(element: TreeElement): boolean { + if (isSCMRepositoryArray(element)) { + return true; + } else if (isSCMRepository(element)) { + return true; + } else if (isSCMActionButton(element)) { + return false; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return true; + } else if (isSCMHistoryItemTreeElement(element)) { + return true; + } else if (isSCMHistoryItemChangeTreeElement(element)) { + return false; + } else { + throw new Error('hasChildren not implemented.'); + } + } + + async getChildren(element: TreeElement): Promise { + const children: TreeElement[] = []; + + if (isSCMRepositoryArray(element)) { + children.push(...element); + } else if (isSCMRepository(element)) { + const scmProvider = element.provider; + const historyProvider = scmProvider.historyProvider; + const historyItemGroup = historyProvider?.currentHistoryItemGroup(); + + if (!historyProvider || !historyItemGroup) { + return children; + } + + // Action Button + const actionButton = historyProvider.actionButton(); + if (actionButton) { + children.push({ + type: 'actionButton', + repository: element, + button: actionButton + } as ISCMActionButton); + } + + // Common ancestor, ahead, behind + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroup.upstream?.id); + + // Incoming + if (historyItemGroup?.upstream) { + children.push({ + id: historyItemGroup.upstream.id, + label: localize('incoming', "$(cloud-download) Incoming Changes"), + description: historyItemGroup.upstream.label, + ancestor: ancestor?.id, + count: ancestor?.behind ?? 0, + repository: element, + type: 'historyItemGroup' + } as SCMHistoryItemGroupTreeElement); + } + + // Outgoing + if (historyItemGroup) { + children.push({ + id: historyItemGroup.id, + label: localize('outgoing', "$(cloud-upload) Outgoing Changes"), + description: historyItemGroup.label, + ancestor: ancestor?.id, + count: ancestor?.ahead ?? 0, + repository: element, + type: 'historyItemGroup' + } as SCMHistoryItemGroupTreeElement); + } + } else if (isSCMHistoryItemGroupTreeElement(element)) { + const scmProvider = element.repository.provider; + const historyProvider = scmProvider.historyProvider; + + if (!historyProvider) { + return children; + } + + const historyItems = await historyProvider.provideHistoryItems(element.id, { limit: { id: element.ancestor } }) ?? []; + children.push(...historyItems.map(historyItem => ({ + id: historyItem.id, + label: historyItem.label, + description: historyItem.description, + icon: historyItem.icon, + historyItemGroup: element, + type: 'historyItem' + } as SCMHistoryItemTreeElement))); + } else if (isSCMHistoryItemTreeElement(element)) { + const repository = element.historyItemGroup.repository; + const historyProvider = repository.provider.historyProvider; + + if (!historyProvider) { + return children; + } + + // History Item Changes + const changes = await historyProvider.provideHistoryItemChanges(element.id) ?? []; + children.push(...changes.map(change => ({ + uri: change.uri, + originalUri: change.originalUri, + modifiedUri: change.modifiedUri, + renameUri: change.renameUri, + historyItem: element, + type: 'historyItemChange' + } as SCMHistoryItemChangeTreeElement))); + } else { + throw new Error('getChildren Method not implemented.'); + } + + return children; + } +} diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 9060e9dbdca..420b7c808ba 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -112,7 +112,7 @@ interface ActionButtonTemplate { readonly templateDisposable: IDisposable; } -class ActionButtonRenderer implements ICompressibleTreeRenderer { +export class ActionButtonRenderer implements ICompressibleTreeRenderer { static readonly DEFAULT_HEIGHT = 30; static readonly TEMPLATE_ID = 'actionButton'; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index f27763e9c26..34b7170c71e 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -17,6 +17,10 @@ import { Command } from 'vs/editor/common/languages'; import { reset } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +export function isSCMRepositoryArray(element: any): element is ISCMRepository[] { + return Array.isArray(element) && element.every(r => isSCMRepository(r)); +} + export function isSCMRepository(element: any): element is ISCMRepository { return !!(element as ISCMRepository).provider && !!(element as ISCMRepository).input; } diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts new file mode 100644 index 00000000000..d25aa709a78 --- /dev/null +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm'; + +export interface ISCMHistoryProvider { + actionButton: () => ISCMActionButtonDescriptor | undefined; + currentHistoryItemGroup: () => ISCMHistoryItemGroup | undefined; + provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; + provideHistoryItemChanges(historyItemId: string): Promise; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined>; +} + +export interface ISCMHistoryOptions { + readonly cursor?: string; + readonly limit?: number | { id?: string }; +} + +export interface ISCMHistoryItemGroup { + readonly id: string; + readonly label: string; + readonly upstream?: ISCMHistoryItemGroup; +} + +export interface ISCMHistoryItem { + readonly id: string; + readonly parentIds: string[]; + readonly label: string; + readonly description?: string; + readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; + readonly timestamp?: number; +} + +export interface ISCMHistoryItemChange { + readonly uri: URI; + readonly originalUri?: URI; + readonly modifiedUri?: URI; + readonly renameUri?: URI; +} diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 4c4c3cc3729..52d9d7637c7 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -13,10 +13,12 @@ import { IAction } from 'vs/base/common/actions'; import { IMenu } from 'vs/platform/actions/common/actions'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; export const VIEWLET_ID = 'workbench.view.scm'; export const VIEW_PANE_ID = 'workbench.scm'; export const REPOSITORIES_VIEW_PANE_ID = 'workbench.scm.repositories'; +export const SYNC_VIEW_PANE_ID = 'workbench.scm.sync'; export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; @@ -63,7 +65,10 @@ export interface ISCMProvider extends IDisposable { readonly inputBoxDocumentUri: URI; readonly count?: number; readonly commitTemplate: string; + readonly historyProvider?: ISCMHistoryProvider; readonly onDidChangeCommitTemplate: Event; + readonly onDidChangeHistoryProviderActionButton: Event; + readonly onDidChangeHistoryProviderCurrentHistoryItemGroup: Event; readonly onDidChangeStatusBarCommands?: Event; readonly acceptInputCommand?: Command; readonly actionButton?: ISCMActionButtonDescriptor; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index f10d182e0d5..58141dd1bc6 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -73,6 +73,7 @@ export const allApiProposals = Object.freeze({ resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', saveEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.saveEditor.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', + scmHistoryProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts', scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', scmTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts', scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts new file mode 100644 index 00000000000..5dcdbb4655b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * 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/185269 + + export interface SourceControl { + historyProvider?: SourceControlHistoryProvider; + } + + export interface SourceControlHistoryProvider { + actionButton?: SourceControlActionButton; + currentHistoryItemGroup?: SourceControlHistoryItemGroup; + + /** + * Fires when the action button changes + */ + onDidChangeActionButton: Event; + + /** + * Fires when the current history item group changes (ex: checkout) + */ + onDidChangeCurrentHistoryItemGroup: Event; + + /** + * Fires when the history item groups change (ex: commit, push, fetch) + */ + // onDidChangeHistoryItemGroups: Event; + + provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; + provideHistoryItemChanges(historyItemId: string, token: CancellationToken): ProviderResult; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; + + // resolveHistoryItemGroup(historyItemGroupId: string, token: CancellationToken): ProviderResult; + } + + export interface SourceControlHistoryOptions { + readonly cursor?: string; + readonly limit?: number | { id?: string }; + } + + export interface SourceControlHistoryItemGroup { + readonly id: string; + readonly label: string; + readonly upstream?: SourceControlHistoryItemGroup; + } + + export interface SourceControlHistoryItem { + readonly id: string; + readonly parentIds: string[]; + readonly label: string; + readonly description?: string; + readonly icon?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + readonly timestamp?: number; + } + + export interface SourceControlHistoryItemChange { + readonly uri: Uri; + readonly originalUri: Uri | undefined; + readonly modifiedUri: Uri | undefined; + readonly renameUri: Uri | undefined; + } + + // export interface SourceControlHistoryChangeEvent { + // readonly added: Iterable; + // readonly removed: Iterable; + // readonly modified: Iterable; + // } + +} From ba7698ca1f80cc0438aa552168f1ef5174c1bc64 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 16:00:40 +0200 Subject: [PATCH 083/133] Dedupe continue-on and local un-submitted comments (#193463) --- .../workbench/contrib/comments/browser/commentService.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 2dbc44ada62..1454cb6054a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -370,6 +370,13 @@ export class CommentService extends Disposable implements ICommentService { for (const control of this._commentControls.values()) { commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None) .then(documentComments => { + // Check that there aren't any continue on comments in the provided comments + // This can happen because continue on comments are stored separately from local un-submitted comments. + for (const documentCommentThread of documentComments.threads) { + if (documentCommentThread.comments?.length === 0 && documentCommentThread.range) { + this.removeContinueOnComment({ range: documentCommentThread.range, uri: resource, owner: documentComments.owner }); + } + } const pendingComments = this._continueOnComments.get(documentComments.owner); documentComments.pendingCommentThreads = pendingComments?.filter(pendingComment => pendingComment.uri.toString() === resource.toString()); return documentComments; From 23a4d89e42914603bf6e4b503adddffb667ff2b6 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:59:13 +0200 Subject: [PATCH 084/133] SCM - tweak API for the SCM sync view (#193470) --- src/vs/workbench/api/browser/mainThreadSCM.ts | 33 +++++------- .../workbench/api/common/extHost.protocol.ts | 9 +++- src/vs/workbench/api/common/extHostSCM.ts | 52 +++++++++---------- .../contrib/scm/browser/scm.contribution.ts | 2 +- .../contrib/scm/browser/scmSyncViewPane.ts | 6 ++- .../workbench/contrib/scm/common/history.ts | 7 ++- .../vscode.proposed.scmHistoryProvider.d.ts | 7 ++- 7 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 67f6b0d5078..40e92bf942c 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -196,6 +196,18 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._quickDiff.dispose(); this._quickDiff = undefined; } + + if (features.hasHistoryProvider && !this._historyProvider) { + this._historyProvider = { + actionButton: () => this._historyProviderActionButton ?? undefined, + currentHistoryItemGroup: () => this._historyProviderCurrentHistoryItemGroup ?? undefined, + provideHistoryItems: (historyItemGroupId, options) => this.provideHistoryItems(historyItemGroupId, options), + provideHistoryItemChanges: (historyItemId: string) => this.provideHistoryItemChanges(historyItemId), + resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1, historyItemGroupId2) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), + }; + } else if (features.hasHistoryProvider === false && this._historyProvider) { + this._historyProvider = undefined; + } } $registerGroups(_groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][]): void { @@ -305,16 +317,6 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { return result && URI.revive(result); } - $registerHistoryProvider(): void { - this._historyProvider = { - actionButton: () => this._historyProviderActionButton ?? undefined, - currentHistoryItemGroup: () => this._historyProviderCurrentHistoryItemGroup ?? undefined, - provideHistoryItems: (historyItemGroupId, options) => this.provideHistoryItems(historyItemGroupId, options), - provideHistoryItemChanges: (historyItemId: string) => this.provideHistoryItemChanges(historyItemId), - resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1, historyItemGroupId2) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), - }; - } - $onDidChangeHistoryProviderActionButton(actionButton?: SCMActionButtonDto | null): void { this._historyProviderActionButton = actionButton; this._onDidChangeHistoryProviderActionButton.fire(); @@ -551,17 +553,6 @@ export class MainThreadSCM implements MainThreadSCMShape { } } - $registerHistoryProvider(sourceControlHandle: number): void { - const repository = this._repositories.get(sourceControlHandle); - - if (!repository) { - return; - } - - const provider = repository.provider as MainThreadSCMProvider; - provider.$registerHistoryProvider(); - } - $onDidChangeHistoryProviderActionButton(sourceControlHandle: number, actionButton?: SCMActionButtonDto | null | undefined): void { const repository = this._repositories.get(sourceControlHandle); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index eb64a9843b8..5591666698c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1359,6 +1359,7 @@ export interface MainThreadExtensionServiceShape extends IDisposable { } export interface SCMProviderFeatures { + hasHistoryProvider?: boolean; hasQuickDiffProvider?: boolean; quickDiffLabel?: string; count?: number; @@ -1404,7 +1405,12 @@ export type SCMRawResourceSplices = [ export interface SCMHistoryItemGroupDto { readonly id: string; readonly label: string; - readonly upstream?: SCMHistoryItemGroupDto; + readonly upstream?: SCMRemoteHistoryItemGroupDto; +} + +export interface SCMRemoteHistoryItemGroupDto { + readonly id: string; + readonly label: string; } export interface SCMHistoryItemDto { @@ -1442,7 +1448,6 @@ export interface MainThreadSCMShape extends IDisposable { $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; - $registerHistoryProvider(sourceControlHandle: number): void; $onDidChangeHistoryProviderActionButton(sourceControlHandle: number, actionButton?: SCMActionButtonDto | null): void; $onDidChangeHistoryProviderCurrentHistoryItemGroup(sourceControlHandle: number, historyItemGroup: SCMHistoryItemGroupDto | undefined): void; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 775c243a1dc..307ff297dc7 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -548,34 +548,32 @@ class ExtHostSourceControl implements vscode.SourceControl { this._historyProvider = historyProvider; this._historyProviderDisposable.value = new DisposableStore(); - if (!historyProvider) { - return; + this.#proxy.$updateSourceControl(this.handle, { hasHistoryProvider: !!historyProvider }); + + if (historyProvider) { + this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemGroup(() => { + if (historyItemGroupEquals(this._historyProviderCurrentHistoryItemGroup, historyProvider?.currentHistoryItemGroup)) { + return; + } + + this._historyProviderCurrentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; + this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup); + })); + + this._historyProviderDisposable.value.add(historyProvider.onDidChangeActionButton(() => { + checkProposedApiEnabled(this._extension, 'scmActionButton'); + + this._historyProviderActionButtonDisposable.value = new DisposableStore(); + const internal = historyProvider.actionButton !== undefined ? + { + command: this._commands.converter.toInternal(historyProvider.actionButton.command, this._historyProviderActionButtonDisposable.value), + description: historyProvider.actionButton.description, + enabled: historyProvider.actionButton.enabled + } : undefined; + + this.#proxy.$onDidChangeHistoryProviderActionButton(this.handle, internal ?? null); + })); } - - this.#proxy.$registerHistoryProvider(this.handle); - - this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemGroup(() => { - if (historyItemGroupEquals(this._historyProviderCurrentHistoryItemGroup, historyProvider?.currentHistoryItemGroup)) { - return; - } - - this._historyProviderCurrentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; - this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemGroup(this.handle, this._historyProviderCurrentHistoryItemGroup); - })); - - this._historyProviderDisposable.value.add(historyProvider.onDidChangeActionButton(() => { - checkProposedApiEnabled(this._extension, 'scmActionButton'); - - this._historyProviderActionButtonDisposable.value = new DisposableStore(); - const internal = historyProvider.actionButton !== undefined ? - { - command: this._commands.converter.toInternal(historyProvider.actionButton.command, this._historyProviderActionButtonDisposable.value), - description: historyProvider.actionButton.description, - enabled: historyProvider.actionButton.enabled - } : undefined; - - this.#proxy.$onDidChangeHistoryProviderActionButton(this.handle, internal ?? null); - })); } private _commitTemplate: string | undefined = undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 9f9c56695d7..eeffaa40d44 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -114,7 +114,7 @@ viewsRegistry.registerViews([{ id: SYNC_VIEW_PANE_ID, name: localize('source control sync', "Source Control Sync"), ctorDescriptor: new SyncDescriptor(SCMSyncViewPane), - canToggleVisibility: false, + canToggleVisibility: true, canMoveView: true, weight: 20, order: -998, diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index 82030e97e49..bd4f27878c8 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -384,6 +384,11 @@ export class SCMSyncViewPane extends ViewPane { this._viewModel = this.instantiationService.createInstance(SCMSyncPaneViewModel, this._tree); } + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + this._tree.layout(height, width); + } + private async onDidOpen(e: IOpenEvent): Promise { if (!e.element) { return; @@ -464,7 +469,6 @@ class SCMSyncPaneViewModel { // Multiple repositories or always show repositories await this.tree.setInput([...this.repositories.keys()], { expanded }); } - this.tree.layout(); } } diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index d25aa709a78..7fd225a37cd 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -23,7 +23,12 @@ export interface ISCMHistoryOptions { export interface ISCMHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: ISCMHistoryItemGroup; + readonly upstream?: ISCMRemoteHistoryItemGroup; +} + +export interface ISCMRemoteHistoryItemGroup { + readonly id: string; + readonly label: string; } export interface ISCMHistoryItem { diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 5dcdbb4655b..5fad4bd5da4 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -44,7 +44,12 @@ declare module 'vscode' { export interface SourceControlHistoryItemGroup { readonly id: string; readonly label: string; - readonly upstream?: SourceControlHistoryItemGroup; + readonly upstream?: SourceControlRemoteHistoryItemGroup; + } + + export interface SourceControlRemoteHistoryItemGroup { + readonly id: string; + readonly label: string; } export interface SourceControlHistoryItem { From fa45b00051606aa4e7bd8b4da05f209faa5ba02d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 16:59:47 +0200 Subject: [PATCH 085/133] Add a context key for active editor commenting ranges (#193460) Part of #192377 --- .../comments/browser/commentService.ts | 11 ++---- .../comments/browser/commentsController.ts | 35 ++++++++++--------- .../browser/commentsEditorContribution.ts | 19 +++++----- .../comments/common/commentContextKeys.ts | 25 +++++++++++++ 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 1454cb6054a..303f24a6a84 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; @@ -17,16 +16,12 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const ICommentService = createDecorator('commentService'); -export const WorkspaceHasCommenting = new RawContextKey('workspaceHasCommenting', false, { - description: nls.localize('hasCommentingProvider', "Whether the open workspace has either comments or commenting ranges."), - type: 'boolean' -}); - interface IResourceCommentThreadEvent { resource: URI; commentInfos: ICommentInfo[]; @@ -174,7 +169,7 @@ export class CommentService extends Disposable implements ICommentService { super(); this._handleConfiguration(); this._handleZenMode(); - this._workspaceHasCommenting = WorkspaceHasCommenting.bindTo(contextKeyService); + this._workspaceHasCommenting = CommentContextKeys.WorkspaceHasCommenting.bindTo(contextKeyService); const storageListener = this._register(new DisposableStore()); storageListener.add(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener)((v) => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 82470cfe934..23eea520440 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -33,12 +33,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; import { COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/contrib/comments/browser/commentReply'; import { Emitter } from 'vs/base/common/event'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Position } from 'vs/editor/common/core/position'; import { CommentThreadRangeDecorator } from 'vs/workbench/contrib/comments/browser/commentThreadRangeDecorator'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; import { status } from 'vs/base/browser/ui/aria/aria'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const ID = 'editor.contrib.review'; @@ -347,11 +348,6 @@ class CommentingRangeDecorator { } } -export const ActiveCursorHasCommentingRange = new RawContextKey('activeCursorHasCommentingRange', false, { - description: nls.localize('hasCommentingRange', "Whether the position at the active cursor has a commenting range"), - type: 'boolean' -}); - export class CommentController implements IEditorContribution { private readonly globalToDispose = new DisposableStore(); private readonly localToDispose = new DisposableStore(); @@ -371,7 +367,8 @@ export class CommentController implements IEditorContribution { private _pendingEditsCache: { [key: string]: { [key: string]: { [key: number]: string } } }; // owner -> threadId -> uniqueIdInThread -> pending comment private _editorDisposables: IDisposable[] = []; private _activeCursorHasCommentingRange: IContextKey; - private _hasProvidedAriaStatus: boolean = false; + private _activeEditorHasCommentingRange: IContextKey; + private _hasRespondedToEditorChange: boolean = false; constructor( editor: ICodeEditor, @@ -390,7 +387,8 @@ export class CommentController implements IEditorContribution { this._pendingNewCommentCache = {}; this._pendingEditsCache = {}; this._computePromise = null; - this._activeCursorHasCommentingRange = ActiveCursorHasCommentingRange.bindTo(contextKeyService); + this._activeCursorHasCommentingRange = CommentContextKeys.activeCursorHasCommentingRange.bindTo(contextKeyService); + this._activeEditorHasCommentingRange = CommentContextKeys.activeEditorHasCommentingRange.bindTo(contextKeyService); if (editor instanceof EmbeddedCodeEditorWidget) { return; @@ -419,8 +417,8 @@ export class CommentController implements IEditorContribution { } this.beginCompute(); })); - this.globalToDispose.add(this.commentService.onDidSetDataProvider(_ => this.beginComputeAndProvideStatus())); - this.globalToDispose.add(this.commentService.onDidUpdateCommentingRanges(_ => this.beginComputeAndProvideStatus())); + this.globalToDispose.add(this.commentService.onDidSetDataProvider(_ => this.beginComputeAndHandleEditorChange())); + this.globalToDispose.add(this.commentService.onDidUpdateCommentingRanges(_ => this.beginComputeAndHandleEditorChange())); this.globalToDispose.add(this.commentService.onDidSetResourceCommentInfos(e => { const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; @@ -750,7 +748,7 @@ export class CommentController implements IEditorContribution { return; } - this._hasProvidedAriaStatus = false; + this._hasRespondedToEditorChange = false; this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); @@ -841,14 +839,19 @@ export class CommentController implements IEditorContribution { this._commentThreadRangeDecorator.update(this.editor, commentInfo); })); - this.beginComputeAndProvideStatus(); + this.beginComputeAndHandleEditorChange(); } - private beginComputeAndProvideStatus(): void { + private beginComputeAndHandleEditorChange(): void { this.beginCompute().then(() => { - if (!this._hasProvidedAriaStatus && this._commentInfos.some(commentInfo => commentInfo.commentingRanges.ranges.length > 0 || commentInfo.commentingRanges.fileComments)) { - this._hasProvidedAriaStatus = true; - status(nls.localize('hasCommentRanges', "Editor has commenting ranges.")); + if (!this._hasRespondedToEditorChange) { + if (this._commentInfos.some(commentInfo => commentInfo.commentingRanges.ranges.length > 0 || commentInfo.commentingRanges.fileComments)) { + this._hasRespondedToEditorChange = true; + this._activeEditorHasCommentingRange.set(true); + status(nls.localize('hasCommentRanges', "Editor has commenting ranges.")); + } else { + this._activeEditorHasCommentingRange.set(false); + } } }); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index a370ecbb48f..6384fe1e2cd 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -12,14 +12,15 @@ import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ICommentService, WorkspaceHasCommenting } from 'vs/workbench/contrib/comments/browser/commentService'; +import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ActiveCursorHasCommentingRange, CommentController, ID } from 'vs/workbench/contrib/comments/browser/commentsController'; +import { CommentController, ID } from 'vs/workbench/contrib/comments/browser/commentsController'; import { IRange, Range } from 'vs/editor/common/core/range'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export class NextCommentThreadAction extends EditorAction { constructor() { @@ -73,7 +74,7 @@ export class NextCommentingRangeAction extends EditorAction { id: 'editor.action.goToNextCommentingRange', label: nls.localize('goToNextCommentingRange', "Go to Next Commenting Range"), alias: 'Go to Next Commenting Range', - precondition: WorkspaceHasCommenting, + precondition: CommentContextKeys.WorkspaceHasCommenting, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow), @@ -94,7 +95,7 @@ export class PreviousCommentingRangeAction extends EditorAction { id: 'editor.action.goToPreviousCommentingRange', label: nls.localize('goToPreviousCommentingRange', "Go to Previous Commenting Range"), alias: 'Go to Next Commenting Range', - precondition: WorkspaceHasCommenting, + precondition: CommentContextKeys.WorkspaceHasCommenting, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow), @@ -128,7 +129,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: nls.localize('comments.toggleCommenting', "Toggle Editor Commenting"), category: 'Comments', }, - when: WorkspaceHasCommenting + when: CommentContextKeys.WorkspaceHasCommenting }); const ADD_COMMENT_COMMAND = 'workbench.action.addComment'; @@ -164,7 +165,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: nls.localize('comments.addCommand', "Add Comment on Current Selection"), category: 'Comments' }, - when: ActiveCursorHasCommentingRange + when: CommentContextKeys.activeCursorHasCommentingRange }); const COLLAPSE_ALL_COMMENT_COMMAND = 'workbench.action.collapseAllComments'; @@ -181,7 +182,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: nls.localize('comments.collapseAll', "Collapse All Comments"), category: 'Comments' }, - when: WorkspaceHasCommenting + when: CommentContextKeys.WorkspaceHasCommenting }); const EXPAND_ALL_COMMENT_COMMAND = 'workbench.action.expandAllComments'; @@ -198,7 +199,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: nls.localize('comments.expandAll', "Expand All Comments"), category: 'Comments' }, - when: WorkspaceHasCommenting + when: CommentContextKeys.WorkspaceHasCommenting }); const EXPAND_UNRESOLVED_COMMENT_COMMAND = 'workbench.action.expandUnresolvedComments'; @@ -215,7 +216,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { title: nls.localize('comments.expandUnresolved', "Expand Unresolved Comments"), category: 'Comments' }, - when: WorkspaceHasCommenting + when: CommentContextKeys.WorkspaceHasCommenting }); KeybindingsRegistry.registerCommandAndKeybindingRule({ diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts index c4b32556b31..f792163c118 100644 --- a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -8,6 +8,31 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export namespace CommentContextKeys { + + /** + * A context key that is set when the active cursor is in a commenting range. + */ + export const activeCursorHasCommentingRange = new RawContextKey('activeCursorHasCommentingRange', false, { + description: nls.localize('hasCommentingRange', "Whether the position at the active cursor has a commenting range"), + type: 'boolean' + }); + + /** + * A context key that is set when the active editor has commenting ranges. + */ + export const activeEditorHasCommentingRange = new RawContextKey('activeEditorHasCommentingRange', false, { + description: nls.localize('editorHasCommentingRange', "Whether the active editor has a commenting range"), + type: 'boolean' + }); + + /** + * A context key that is set when the workspace has either comments or commenting ranges. + */ + export const WorkspaceHasCommenting = new RawContextKey('workspaceHasCommenting', false, { + description: nls.localize('hasCommentingProvider', "Whether the open workspace has either comments or commenting ranges."), + type: 'boolean' + }); + /** * A context key that is set when the comment thread has no comments. */ From db6150c8f522d37de3b3c7e3b43957d49d3f2bcd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 19 Sep 2023 08:28:01 -0700 Subject: [PATCH 086/133] input: use faster model access (#193476) For #193427 Doesn't really matter here since it's just a search input, but use best practices anyway. --- .../browser/suggestEnabledInput/suggestEnabledInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 85bb0e71779..32ac5cb4dec 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -444,7 +444,7 @@ export class ContextScopedSuggestEnabledInputWithHistory extends SuggestEnabledI this._register(this.inputWidget.onDidChangeCursorPosition(({ position }) => { const viewModel = this.inputWidget._getViewModel()!; const lastLineNumber = viewModel.getLineCount(); - const lastLineCol = viewModel.getLineContent(lastLineNumber).length + 1; + const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1; const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position); historyNavigationBackwardsEnablement.set(viewPosition.lineNumber === 1 && viewPosition.column === 1); historyNavigationForwardsEnablement.set(viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol); From 060e385ef5f88da056e574c0c161cc3132b7feb0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Sep 2023 08:28:28 -0700 Subject: [PATCH 087/133] Add typescript.preferences.autoImportFileExcludePatterns to repo (#193477) For https://github.com/microsoft/TypeScript/issues/55781 --- .vscode/settings.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6925e3ed8c6..0069fee3d12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -151,4 +151,12 @@ "application.experimental.rendererProfiling": true, "editor.experimental.asyncTokenization": true, "editor.experimental.asyncTokenizationVerification": true, + "typescript.preferences.autoImportFileExcludePatterns": [ + "xterm", + "xterm-headless", + "node-pty", + "vscode-notebook-renderer", + "src/vs/workbench/workbench.web.main.ts", + "src/vs/workbench/api/common/extHostTypes.ts" + ] } From ca7f35426073034e331fbe733b644d06da0fa68a Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:31:24 -0700 Subject: [PATCH 088/133] Do not allow code actions to run for autosave after delay (#193479) disable code actions specifically for autosave after delay: --- .../workbench/contrib/codeEditor/browser/saveParticipants.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index fc93eb31ecd..d29cbedb139 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -285,6 +285,11 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { if (!setting) { return undefined; } + + if (env.reason === SaveReason.AUTO) { + return undefined; + } + const convertedSetting: { [kind: string]: string } = {}; for (const key in setting) { if (typeof setting[key] === 'boolean') { From de43dd95426007f5d12769d8be166ee9a676a8cc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Sep 2023 08:44:54 -0700 Subject: [PATCH 089/133] Mark most DisposableStore properties readonly (#193482) This prevents a common cause of leaks: reassigning the property without first disposing of the old value --- src/vs/base/browser/ui/grid/gridview.ts | 2 +- src/vs/base/browser/ui/sash/sash.ts | 4 ++-- src/vs/base/browser/ui/tree/abstractTree.ts | 5 ++--- src/vs/base/parts/ipc/common/ipc.ts | 2 +- src/vs/base/parts/ipc/test/common/ipc.test.ts | 2 +- .../codelens/browser/codelensController.ts | 2 +- .../snippet/browser/snippetController2.ts | 2 +- src/vs/platform/list/browser/listService.ts | 2 +- .../workbench/api/common/extHostCodeInsets.ts | 2 +- src/vs/workbench/api/node/extHostSearch.ts | 2 +- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../browser/outline/documentSymbolsOutline.ts | 2 +- .../contrib/debug/browser/debugService.ts | 2 +- .../contrib/debug/browser/debugSession.ts | 2 +- .../browser/inlayHintsAccessibilty.ts | 2 +- .../browser/languageDetection.contribution.ts | 2 +- .../browser/languageStatus.contribution.ts | 2 +- .../browser/contrib/outline/notebookOutline.ts | 2 +- .../workbench/contrib/scm/browser/activity.ts | 8 ++++---- .../contrib/scm/browser/dirtydiffDecorator.ts | 5 ++--- src/vs/workbench/contrib/scm/browser/menus.ts | 2 +- .../contrib/scm/browser/scmSyncViewPane.ts | 2 +- .../contrib/scm/browser/scmViewPane.ts | 18 ++++++++---------- .../contrib/scm/browser/scmViewService.ts | 2 +- .../workbench/contrib/scm/common/scmService.ts | 2 +- .../update/browser/releaseNotesEditor.ts | 2 +- 26 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index bdc630dbcf7..f73e7a8ce7a 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -778,7 +778,7 @@ class LeafNode implements ISplitView, IDisposable { private _onDidViewChange: Event; readonly onDidChange: Event; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( readonly view: IView, diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index b82b4508211..a69b863f21d 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -173,7 +173,7 @@ interface IPointerEventFactory { class MouseEventFactory implements IPointerEventFactory { - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); @memoize get onPointerMove(): Event { @@ -192,7 +192,7 @@ class MouseEventFactory implements IPointerEventFactory { class GestureEventFactory implements IPointerEventFactory { - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); @memoize get onPointerMove(): Event { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 5376fe98462..07a22c43e91 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1027,7 +1027,7 @@ class FindController implements IDisposable { private readonly _onDidChangeOpenState = new Emitter(); readonly onDidChangeOpenState = this._onDidChangeOpenState.event; - private enabledDisposables = new DisposableStore(); + private readonly enabledDisposables = new DisposableStore(); private readonly disposables = new DisposableStore(); constructor( @@ -1085,8 +1085,7 @@ class FindController implements IDisposable { this._history = this.widget.getHistory(); this.widget = undefined; - this.enabledDisposables.dispose(); - this.enabledDisposables = new DisposableStore(); + this.enabledDisposables.clear(); this.previousPattern = this.pattern; this.onDidChangeValue(''); diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index db61aed4ff1..38d7e585713 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -798,7 +798,7 @@ export class IPCServer implements IChannelServer, I private readonly _onDidRemoveConnection = new Emitter>(); readonly onDidRemoveConnection: Event> = this._onDidRemoveConnection.event; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); get connections(): Connection[] { const result: Connection[] = []; diff --git a/src/vs/base/parts/ipc/test/common/ipc.test.ts b/src/vs/base/parts/ipc/test/common/ipc.test.ts index 35184c08217..0515d24a49c 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -113,7 +113,7 @@ interface ITestService { class TestService implements ITestService { - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private readonly _onPong = new Emitter(); readonly onPong = this._onPong.event; diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index f260a892cca..2cba641d116 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -39,7 +39,7 @@ export class CodeLensContribution implements IEditorContribution { private readonly _resolveCodeLensesScheduler: RunOnceScheduler; private _getCodeLensModelPromise: CancelablePromise | undefined; - private _oldCodeLensModels = new DisposableStore(); + private readonly _oldCodeLensModels = new DisposableStore(); private _currentCodeLensModel: CodeLensModel | undefined; private _resolveCodeLensesPromise: CancelablePromise | undefined; diff --git a/src/vs/editor/contrib/snippet/browser/snippetController2.ts b/src/vs/editor/contrib/snippet/browser/snippetController2.ts index 87d27b48d5c..0290c450113 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetController2.ts @@ -62,7 +62,7 @@ export class SnippetController2 implements IEditorContribution { private readonly _hasPrevTabstop: IContextKey; private _session?: SnippetSession; - private _snippetListener = new DisposableStore(); + private readonly _snippetListener = new DisposableStore(); private _modelVersionId: number = -1; private _currentChoice?: Choice; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 75b13f0a8fc..f0d944ddf06 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -54,7 +54,7 @@ export class ListService implements IListService { declare readonly _serviceBrand: undefined; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private lists: IRegisteredList[] = []; private _lastFocusedWidget: WorkbenchListWidget | undefined = undefined; private _hasCreatedStyleController: boolean = false; diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 79f86de8eb8..bf6758e93ae 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -15,7 +15,7 @@ import { ExtHostEditorInsetsShape, MainThreadEditorInsetsShape } from './extHost export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private _handlePool = 0; - private _disposables = new DisposableStore(); + private readonly _disposables = new DisposableStore(); private _insets = new Map }>(); constructor( diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index ef4f2b68e69..45cab9925c5 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -29,7 +29,7 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { private _registeredEHSearchProvider = false; - private _disposables = new DisposableStore(); + private readonly _disposables = new DisposableStore(); constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 11f2a5447c0..4f5e7f34ed2 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -105,7 +105,7 @@ class ViewWelcomeController { return visibleItems.map(v => v.descriptor); } - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( private id: string, diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 91aafac3650..b5d88f047c5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -112,7 +112,7 @@ class DocumentSymbolsOutline implements IOutline { readonly onDidChange: Event = this._onDidChange.event; private _outlineModel?: OutlineModel; - private _outlineDisposables = new DisposableStore(); + private readonly _outlineDisposables = new DisposableStore(); private readonly _breadcrumbsDataSource: DocumentSymbolBreadcrumbsSource; diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 1bb8acebf52..ad2783183b9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -70,7 +70,7 @@ export class DebugService implements IDebugService { private taskRunner: DebugTaskRunner; private configurationManager: ConfigurationManager; private adapterManager: AdapterManager; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private debugType!: IContextKey; private debugState!: IContextKey; private inDebugMode!: IContextKey; diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 01c4ef4ce44..ef7b5d7b28c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -52,7 +52,7 @@ export class DebugSession implements IDebugSession, IDisposable { private threads = new Map(); private threadIds: number[] = []; private cancellationMap = new Map(); - private rawListeners = new DisposableStore(); + private readonly rawListeners = new DisposableStore(); private fetchThreadsScheduler: RunOnceScheduler | undefined; private passFocusScheduler: RunOnceScheduler; private lastContinuedThreadId: number | undefined; diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 7bd383b7932..3f5e22cb5cc 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -35,7 +35,7 @@ export class InlayHintsAccessibility implements IEditorContribution { private readonly _ariaElement: HTMLSpanElement; private readonly _ctxIsReading: IContextKey; - private _sessionDispoosables = new DisposableStore(); + private readonly _sessionDispoosables = new DisposableStore(); constructor( private readonly _editor: ICodeEditor, diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts index 9211cf9643f..ea29b76b146 100644 --- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts +++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts @@ -35,7 +35,7 @@ class LanguageDetectionStatusContribution implements IWorkbenchContribution { private readonly _disposables = new DisposableStore(); private _combinedEntry?: IStatusbarEntryAccessor; private _delayer = new ThrottledDelayer(1000); - private _renderDisposables = new DisposableStore(); + private readonly _renderDisposables = new DisposableStore(); constructor( @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService, diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 3dd004897d2..c5900bbf721 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -72,7 +72,7 @@ class EditorStatusContribution implements IWorkbenchContribution { private _model?: LanguageStatusViewModel; private _combinedEntry?: IStatusbarEntryAccessor; private _dedicatedEntries = new Map(); - private _renderDisposables = new DisposableStore(); + private readonly _renderDisposables = new DisposableStore(); constructor( @ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService, 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 96903926179..4305c75e301 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -211,7 +211,7 @@ export class NotebookCellOutline implements IOutline { } private _outlineProvider: NotebookCellOutlineProvider | undefined; - private _localDisposables = new DisposableStore(); + private readonly _localDisposables = new DisposableStore(); constructor( private readonly _editor: INotebookEditorPane, diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 400c9af2d00..b33088511af 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -33,7 +33,7 @@ export class SCMStatusController implements IWorkbenchContribution { private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private readonly badgeDisposable = new MutableDisposable(); - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private repositoryDisposables = new Set(); constructor( @@ -207,7 +207,7 @@ export class SCMStatusController implements IWorkbenchContribution { this.focusDisposable.dispose(); this.statusBarDisposable.dispose(); this.badgeDisposable.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); dispose(this.repositoryDisposables.values()); this.repositoryDisposables.clear(); } @@ -217,7 +217,7 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu private activeResourceHasChangesContextKey: IContextKey; private activeResourceRepositoryContextKey: IContextKey; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private repositoryDisposables = new Set(); constructor( @@ -281,7 +281,7 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); dispose(this.repositoryDisposables.values()); this.repositoryDisposables.clear(); } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index b858e0fc16c..f278f782373 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -714,7 +714,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; - private gutterActionDisposables = new DisposableStore(); + private readonly gutterActionDisposables = new DisposableStore(); private stylesheet: HTMLStyleElement; constructor( @@ -741,8 +741,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu private onDidChangeGutterAction(): void { const gutterAction = this.configurationService.getValue<'diff' | 'none'>('scm.diffDecorationsGutterAction'); - this.gutterActionDisposables.dispose(); - this.gutterActionDisposables = new DisposableStore(); + this.gutterActionDisposables.clear(); if (gutterAction === 'diff') { this.gutterActionDisposables.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index f1a041e1a7a..9918414d4c0 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -33,7 +33,7 @@ export class SCMTitleMenu implements IDisposable { readonly onDidChangeTitle = this._onDidChangeTitle.event; readonly menu: IMenu; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( @IMenuService menuService: IMenuService, diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index bd4f27878c8..ad69941cc4d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -410,7 +410,7 @@ class SCMSyncPaneViewModel { private repositories = new Map(); private alwaysShowRepositories = false; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( private readonly tree: WorkbenchAsyncDataTree, diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 420b7c808ba..47801632b7d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -453,7 +453,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer(); constructor( @@ -1036,7 +1036,7 @@ class RepositoryVisibilityActionController { private items = new Map(); private repositoryCountContextKey: IContextKey; private repositoryVisibilityCountContextKey: IContextKey; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); constructor( @ISCMViewService private scmViewService: ISCMViewService, @@ -1178,12 +1178,12 @@ class ViewModel { } private items = new Map(); - private visibilityDisposables = new DisposableStore(); + private readonly visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private alwaysShowRepositories = false; private showActionButton = false; private firstVisible = true; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private modeContextKey: IContextKey; private sortKeyContextKey: IContextKey; @@ -1352,7 +1352,6 @@ class ViewModel { setVisible(visible: boolean): void { if (visible) { - this.visibilityDisposables = new DisposableStore(); this.scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.visibilityDisposables); this._onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); @@ -1366,7 +1365,7 @@ class ViewModel { } else { this.updateViewState(); - this.visibilityDisposables.dispose(); + this.visibilityDisposables.clear(); this._onDidChangeVisibleRepositories({ added: Iterable.empty(), removed: [...this.items.keys()] }); this.scrollTop = this.tree.scrollTop; } @@ -1807,11 +1806,11 @@ class SCMInputWidget { private editorContainer: HTMLElement; private placeholderTextContainer: HTMLElement; private inputEditor: CodeEditorWidget; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private model: { readonly input: ISCMInput; textModelRef?: IReference } | undefined; private repositoryIdContextKey: IContextKey; - private repositoryDisposables = new DisposableStore(); + private readonly repositoryDisposables = new DisposableStore(); private validation: IInputValidation | undefined; private validationDisposable: IDisposable = Disposable.None; @@ -1837,8 +1836,7 @@ class SCMInputWidget { this.clearValidation(); this.editorContainer.classList.remove('synthetic-focus'); - this.repositoryDisposables.dispose(); - this.repositoryDisposables = new DisposableStore(); + this.repositoryDisposables.clear(); this.repositoryIdContextKey.set(input?.repository.id); if (!input) { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index 67ff52a110e..e931a6d4fd9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -57,7 +57,7 @@ export class SCMViewService implements ISCMViewService { private didFinishLoading: boolean = false; private didSelectRepository: boolean = false; private previousState: ISCMViewServiceState | undefined; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private _repositories: ISCMRepositoryView[] = []; diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 4740f81653f..b8a34af87b1 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -213,7 +213,7 @@ class WillSaveHistoryEvent { class SCMInputHistory { - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); private readonly histories = new Map>>(); private readonly _onWillSaveHistory = this.disposables.add(new Emitter()); diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 044a7a24227..411c6105a73 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -37,7 +37,7 @@ export class ReleaseNotesManager { private _currentReleaseNotes: WebviewInput | undefined = undefined; private _lastText: string | undefined; - private disposables = new DisposableStore(); + private readonly disposables = new DisposableStore(); public constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, From 1cfe1ecf3f140f6269a4776e803c7d0b6f69abae Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 08:52:37 -0700 Subject: [PATCH 090/133] fix #193475 --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 2 ++ src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts | 3 +++ src/vs/workbench/contrib/tasks/common/taskService.ts | 1 + 3 files changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index a252d8e4814..81cb217e4b6 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -231,6 +231,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public onDidChangeTaskSystemInfo: Event = this._onDidChangeTaskSystemInfo.event; private _onDidReconnectToTasks: Emitter = new Emitter(); public onDidReconnectToTasks: Event = this._onDidReconnectToTasks.event; + public get isReconnected(): boolean { return this._tasksReconnected; } constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -349,6 +350,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._attemptTaskReconnection(); } else { this._tasksReconnected = true; + this._onDidReconnectToTasks.fire(); } }); } diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 55ec14e22a0..bf078c50a7c 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -33,6 +33,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut this._tryRunTasks(); } })); + if (this._taskService.isReconnected) { + this._tryRunTasks(); + } this._register(this._workspaceTrustManagementService.onDidChangeTrust(async trusted => { if (trusted) { await this._tryRunTasks(); diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index c96cba1125d..5799f23fd49 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -64,6 +64,7 @@ export interface IWorkspaceFolderTaskResult extends IWorkspaceTaskResult { export interface ITaskService { readonly _serviceBrand: undefined; onDidStateChange: Event; + isReconnected: boolean; onDidReconnectToTasks: Event; supportsMultipleTaskExecutions: boolean; From 52b0ec4478aa23c79442f2b77c019023050bb1bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 08:54:55 -0700 Subject: [PATCH 091/133] rm redundant check --- .../workbench/contrib/tasks/browser/runAutomaticTasks.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index bf078c50a7c..88c773aa7ee 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -28,11 +28,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService private readonly _logService: ILogService) { super(); - this._taskService.onDidReconnectToTasks((() => { - if (this._workspaceTrustManagementService.isWorkspaceTrusted()) { - this._tryRunTasks(); - } - })); + this._taskService.onDidReconnectToTasks((() => this._tryRunTasks())); if (this._taskService.isReconnected) { this._tryRunTasks(); } @@ -44,6 +40,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } private async _tryRunTasks() { + if (!this._workspaceTrustManagementService.isWorkspaceTrusted()) { + return; + } if (this._hasRunTasks || this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') { return; } From f89f293320c486fb0c2953785a8349f351d7a6ab Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 09:05:55 -0700 Subject: [PATCH 092/133] fix #193454 --- .../workbench/contrib/terminal/browser/terminalActions.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 3b01c31dff1..325d7988c89 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -491,7 +491,11 @@ export function registerTerminalActions() { return; } c.service.setActiveInstance(instance); - return c.groupService.showPanel(true); + if (instance.target === TerminalLocation.Panel) { + c.groupService.showPanel(true); + } else { + c.editorService.activeInstance?.focus(); + } } }); From b0dc4ef749e1f6a9f30fd3208419ea4ceb82d9a7 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 19 Sep 2023 09:06:05 -0700 Subject: [PATCH 093/133] fix: don't load model value for empty editor hint (#193484) --- .../browser/emptyTextEditorHint/emptyTextEditorHint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 987bcadb81f..346f0fef8cd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -178,7 +178,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget { } private onDidChangeModelContent(): void { - if (this.editor.getValue() === '') { + if (!this.editor.getModel()?.getValueLength()) { this.editor.addContentWidget(this); this.isVisible = true; } else { From b1ecc637a6b5ef30498d10ab360131ded3b43bc4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 18:25:22 +0200 Subject: [PATCH 094/133] Comments: Faster TextModel/TextBuffer access (#193487) Part of #193427 --- src/vs/workbench/contrib/comments/browser/commentNode.ts | 2 +- .../workbench/contrib/comments/browser/simpleCommentEditor.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 4a859f036a4..7c98a5622c1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -488,7 +488,7 @@ export class CommentNode extends Disposable { }); const lastLine = this._commentEditorModel.getLineCount(); - const lastColumn = this._commentEditorModel.getLineContent(lastLine).length + 1; + const lastColumn = this._commentEditorModel.getLineLength(lastLine) + 1; this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn)); const commentThread = this.commentThread; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index f087642f26b..0ff28e47388 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -62,12 +62,12 @@ export class SimpleCommentEditor extends CodeEditorWidget { this._commentEditorFocused = ctxCommentEditorFocused.bindTo(scopedContextKeyService); this._commentEditorEmpty = CommentContextKeys.commentIsEmpty.bindTo(scopedContextKeyService); - this._commentEditorEmpty.set(!this.getValue()); + this._commentEditorEmpty.set(!this.getModel()?.getValueLength()); this._parentThread = parentThread; this._register(this.onDidFocusEditorWidget(_ => this._commentEditorFocused.set(true))); - this._register(this.onDidChangeModelContent(e => this._commentEditorEmpty.set(!this.getValue()))); + this._register(this.onDidChangeModelContent(e => this._commentEditorEmpty.set(!this.getModel()?.getValueLength()))); this._register(this.onDidBlurEditorWidget(_ => this._commentEditorFocused.reset())); } From a16d8d0a3e2062678c599134e78f8aa7b124363b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 18:33:30 +0200 Subject: [PATCH 095/133] Update shellscript grammar (#193488) --- extensions/shellscript/cgmanifest.json | 2 +- .../shellscript/syntaxes/shell-unix-bash.tmLanguage.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/shellscript/cgmanifest.json b/extensions/shellscript/cgmanifest.json index f246d45fe21..dbb4301b62c 100644 --- a/extensions/shellscript/cgmanifest.json +++ b/extensions/shellscript/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jeff-hykin/better-shell-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-shell-syntax", - "commitHash": "ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91" + "commitHash": "a3de7b32f1537194a83ee848838402fbf4b67424" } }, "license": "MIT", diff --git a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index 68055eb7b29..9950c577c48 100644 --- a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/shell-unix-bash.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/jeff-hykin/better-shell-syntax/commit/ce62ea59e8e522f8a07d8d8a2d1f992c6c600b91", + "version": "https://github.com/jeff-hykin/better-shell-syntax/commit/a3de7b32f1537194a83ee848838402fbf4b67424", "name": "Shell Script", "scopeName": "source.shell", "patterns": [ @@ -268,7 +268,7 @@ "patterns": [ { "begin": "(?=\\S)", - "end": ";;", + "end": ";;&?|;&", "endCaptures": { "0": { "name": "punctuation.terminator.case-clause.shell" @@ -311,7 +311,7 @@ }, { "begin": "(?<=\\))", - "end": "(?=;;)", + "end": "(?=;;&?|;&)", "name": "meta.scope.case-clause-body.shell", "patterns": [ { From e123234b405ebe7b97a508530899c4501300b272 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 19 Sep 2023 18:34:27 +0200 Subject: [PATCH 096/133] Simple file dialog filter does not work for multiple extension files (e.g. .tar.gz) (#193485) Fixes #191887 --- .../services/dialogs/browser/simpleFileDialog.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index fc6b15a756b..9d3f6a883d2 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -999,26 +999,12 @@ export class SimpleFileDialog implements ISimpleFileDialog { return sorted; } - private extname(file: URI): string { - const ext = resources.extname(file); - if (ext.length === 0) { - const basename = resources.basename(file); - if (basename.startsWith('.')) { - return basename; - } - } else { - return ext; - } - return ''; - } - private filterFile(file: URI): boolean { if (this.options.filters) { - const ext = this.extname(file); for (let i = 0; i < this.options.filters.length; i++) { for (let j = 0; j < this.options.filters[i].extensions.length; j++) { const testExt = this.options.filters[i].extensions[j]; - if ((testExt === '*') || (ext === ('.' + testExt))) { + if ((testExt === '*') || (file.path.endsWith('.' + testExt))) { return true; } } From 5ba8000cd34fad805a7f7ae9f3bdb55acc937201 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 19 Sep 2023 09:40:25 -0700 Subject: [PATCH 097/133] Avoid TextMode#getValue (#193495) --- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 97a2b1ee4ea..eacb6649ac4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -225,7 +225,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Only allow history navigation when the input is empty. // (If this model change happened as a result of a history navigation, this is canceled out by a call in this.navigateHistory) const model = this._inputEditor.getModel(); - const inputHasText = !!model && model.getValue() !== ''; + const inputHasText = !!model && model.getValueLength() > 0; this.setHistoryNavigationEnablement(!inputHasText); this.inputEditorHasText.set(inputHasText); })); From 3aa6d81f18bf3606a40e62a62b66ce65b6218fdf Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 19 Sep 2023 09:54:14 -0700 Subject: [PATCH 098/133] :lipstick: Reduce getValue in chatInput (#193497) * Re #193427. Reduce model.getValue(). * :lipstick: Reduce getValue in chatInput --- .../editor/contrib/linesOperations/browser/linesOperations.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 11a5c6c4f27..74d7849587e 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -708,7 +708,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { if (selection.isEmpty()) { if (selection.startColumn === 1) { const deleteFromLine = Math.max(1, selection.startLineNumber - 1); - const deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineContent(deleteFromLine).length + 1; + const deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineLength(deleteFromLine) + 1; return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1); } else { return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); @@ -864,7 +864,7 @@ export class JoinLinesAction extends EditorAction { let endLineNumber: number, endColumn: number; - const selectionEndPositionOffset = model.getLineContent(selection.endLineNumber).length - selection.endColumn; + const selectionEndPositionOffset = model.getLineLength(selection.endLineNumber) - selection.endColumn; if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) { const position = selection.getStartPosition(); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index ebb17cc541f..4b52a3a647b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -31,7 +31,7 @@ import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/ed import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; -import { ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/browser/bracketMatching'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; @@ -996,7 +996,7 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart { } private setText(newText: string): void { - const currentText = this.textModel.getLinesContent().join('\n'); + const currentText = this.textModel.getValue(EndOfLinePreference.LF); if (newText === currentText) { return; } From 4a7982b7b42c21b96cf4bae24768defa438eaac8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 19 Sep 2023 10:50:08 -0700 Subject: [PATCH 099/133] fix: don't overlap chat hint and other decorations (#193503) --- .../emptyTextEditorHint/emptyTextEditorHint.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 346f0fef8cd..13ae0ab4aed 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -34,7 +34,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; import { LOG_MODE_ID, OUTPUT_MODE_ID } from 'vs/workbench/services/output/common/output'; import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search'; -import { GHOST_TEXT_DESCRIPTION } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; const $ = dom.$; @@ -75,6 +74,7 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { this.toDispose = []; this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); this.toDispose.push(this.editor.onDidChangeModelLanguage(() => this.update())); + this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.update())); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(emptyTextEditorHintSetting)) { this.update(); @@ -102,14 +102,19 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { return false; } - const hasGhostText = this.editor.getLineDecorations(0)?.find((d) => d.options.description === GHOST_TEXT_DESCRIPTION); - if (hasGhostText) { + const model = this.editor.getModel(); + const languageId = model?.getLanguageId(); + if (!model || languageId === OUTPUT_MODE_ID || languageId === LOG_MODE_ID || languageId === SEARCH_RESULT_LANGUAGE_ID) { return false; } - const model = this.editor.getModel(); - const languageId = model?.getLanguageId(); - if (languageId === OUTPUT_MODE_ID || languageId === LOG_MODE_ID || languageId === SEARCH_RESULT_LANGUAGE_ID) { + const conflictingDecoration = this.editor.getLineDecorations(1)?.find((d) => + d.options.beforeContentClassName + || d.options.afterContentClassName + || d.options.before?.content + || d.options.after?.content + ); + if (conflictingDecoration) { return false; } From 1f20a0268905f49b414dc0fc4485ad695d744e22 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 19 Sep 2023 19:51:15 +0200 Subject: [PATCH 100/133] Change Settings Sync terminology (#193494) #140117 Change Settings Sync terminology --- .../browser/preferences.contribution.ts | 8 +++---- .../preferences/browser/settingsEditor2.ts | 9 ++++++-- .../browser/commands/configureSnippets.ts | 4 ++-- .../tasks/browser/task.contribution.ts | 4 ++-- .../themes/browser/themes.contribution.ts | 4 ++-- .../browser/userDataProfile.ts | 4 ++-- .../userDataSync/browser/userDataSync.ts | 22 +++++++++---------- 7 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index ab3b550f8b1..6bf05c15a82 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -204,11 +204,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon menu: [{ id: MenuId.GlobalActivity, group: '2_configuration', - order: 1 + order: 2 }, { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', - order: 1 + order: 2 }], }); } @@ -773,7 +773,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon { id: MenuId.GlobalActivity, group: '2_configuration', - order: 3 + order: 4 } ] }); @@ -789,7 +789,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon title: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), }, group: '2_configuration', - order: 3 + order: 4 })); registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 44bf36ad369..91eed4d33fc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -1800,7 +1800,8 @@ class SyncControls extends Disposable { container: HTMLElement, @ICommandService private readonly commandService: ICommandService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService + @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @ITelemetryService telemetryService: ITelemetryService, ) { super(); @@ -1811,10 +1812,14 @@ class SyncControls extends Disposable { DOM.hide(this.lastSyncedLabel); this.turnOnSyncButton.enabled = true; - this.turnOnSyncButton.label = localize('turnOnSyncButton', "Turn on Settings Sync"); + this.turnOnSyncButton.label = localize('turnOnSyncButton', "Backup and Sync Settings"); DOM.hide(this.turnOnSyncButton.element); this._register(this.turnOnSyncButton.onDidClick(async () => { + telemetryService.publicLog2<{}, { + owner: 'sandy081'; + comment: 'This event tracks whenever settings sync is turned on from settings editor.'; + }>('sync/turnOnSyncFromSettings'); await this.commandService.executeCommand('workbench.userDataSync.actions.turnOn'); })); diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 9d2a199fe18..396482865cc 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -236,8 +236,8 @@ export class ConfigureSnippetsAction extends SnippetsAction { }, f1: true, menu: [ - { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, - { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, + { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 5 }, + { id: MenuId.GlobalActivity, group: '2_configuration', order: 5 }, ] }); } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 8ab974d4cbe..f4388813f7c 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -365,7 +365,7 @@ class UserTasksGlobalActionContribution extends Disposable implements IWorkbench }, when: TaskExecutionSupportedContext, group: '2_configuration', - order: 4 + order: 6 })); this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { command: { @@ -374,7 +374,7 @@ class UserTasksGlobalActionContribution extends Disposable implements IWorkbench }, when: TaskExecutionSupportedContext, group: '2_configuration', - order: 4 + order: 6 })); } } diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 2dcb37944d7..090221014d8 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -723,13 +723,13 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { title: localize('themes', "Themes"), submenu: ThemesSubMenu, group: '2_configuration', - order: 6 + order: 7 }); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { title: localize({ key: 'miSelectTheme', comment: ['&& denotes a mnemonic'] }, "&&Theme"), submenu: ThemesSubMenu, group: '2_configuration', - order: 6 + order: 7 }); MenuRegistry.appendMenuItem(ThemesSubMenu, { diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 769878ced7d..bf6cc44b591 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -95,7 +95,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return getProfilesTitle(); }, submenu: ProfilesMenu, - group: '1_profiles', + group: '2_configuration', order: 1, }); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { @@ -103,7 +103,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return getProfilesTitle(); }, submenu: ProfilesMenu, - group: '1_profiles', + group: '2_configuration', order: 1, when: PROFILES_ENABLEMENT_CONTEXT, }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 1549fc0631e..186b50b58cd 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -727,20 +727,20 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: 'workbench.userDataSync.actions.turnOn', - title: { value: localize('global activity turn on sync', "Turn on Settings Sync..."), original: 'Turn on Settings Sync...' }, + title: { value: localize('global activity turn on sync', "Backup and Sync Settings..."), original: 'Backup and Sync Settings...' }, category: { value: SYNC_TITLE, original: `Settings Sync` }, f1: true, precondition: when, menu: [{ - group: '1_profiles', + group: '3_settings_sync', id: MenuId.GlobalActivity, when, - order: 2 + order: 1 }, { - group: '1_profiles', + group: '3_settings_sync', id: MenuId.MenubarPreferencesMenu, when, - order: 2 + order: 1 }, { group: '1_settings', id: MenuId.AccountsContext, @@ -764,7 +764,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('turnin on sync', "Turning on Settings Sync..."), precondition: ContextKeyExpr.false(), menu: [{ - group: '1_profiles', + group: '3_settings_sync', id: MenuId.GlobalActivity, when, order: 2 @@ -811,7 +811,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: 'workbench.userData.actions.signin', title: localize('sign in global', "Sign in to Sync Settings"), menu: { - group: '1_profiles', + group: '3_settings_sync', id: MenuId.GlobalActivity, when, order: 2 @@ -851,12 +851,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo f1: true, precondition: CONTEXT_HAS_CONFLICTS, menu: [{ - group: '1_profiles', + group: '3_settings_sync', id: MenuId.GlobalActivity, when: CONTEXT_HAS_CONFLICTS, order: 2 }, { - group: '1_profiles', + group: '3_settings_sync', id: MenuId.MenubarPreferencesMenu, when: CONTEXT_HAS_CONFLICTS, order: 2 @@ -881,13 +881,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo menu: [ { id: MenuId.GlobalActivity, - group: '1_profiles', + group: '3_settings_sync', when, order: 2 }, { id: MenuId.MenubarPreferencesMenu, - group: '1_profiles', + group: '3_settings_sync', when, order: 2, }, From a24cf7a6f9d3640f8d877eb8fddd756f176d8c6e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 19 Sep 2023 10:51:31 -0700 Subject: [PATCH 101/133] Remove unused chatAgents props (#193492) --- src/vscode-dts/vscode.proposed.chatAgents.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatAgents.d.ts b/src/vscode-dts/vscode.proposed.chatAgents.d.ts index e668dbbdd2c..6f52c6498df 100644 --- a/src/vscode-dts/vscode.proposed.chatAgents.d.ts +++ b/src/vscode-dts/vscode.proposed.chatAgents.d.ts @@ -27,8 +27,6 @@ declare module 'vscode' { fullName?: string; icon?: Uri; subCommands: ChatAgentCommand[]; - requireCommand?: boolean; // Do some agents not have a default action? - isImplicit?: boolean; // Only @workspace. slash commands get promoted to the top-level and this agent is invoked when those are used } export interface ChatAgent { From a40ee0e2fa08d749d39cffab1824a29efbf27e66 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 19 Sep 2023 18:40:38 +0200 Subject: [PATCH 102/133] Fixes #193370 --- .../linesSliceCharSequence.ts | 2 +- .../diffing/fixtures/import-shifting/1.tst | 3 +++ .../diffing/fixtures/import-shifting/2.tst | 3 +++ .../advanced.expected.diff.json | 22 +++++++++++++++++++ .../import-shifting/legacy.expected.diff.json | 22 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/vs/editor/test/node/diffing/fixtures/import-shifting/1.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/import-shifting/2.tst create mode 100644 src/vs/editor/test/node/diffing/fixtures/import-shifting/advanced.expected.diff.json create mode 100644 src/vs/editor/test/node/diffing/fixtures/import-shifting/legacy.expected.diff.json diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts index ca515f2cbbe..822a6310bf0 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts @@ -96,7 +96,7 @@ export class LinesSliceCharSequence implements ISequence { let score = 0; if (prevCategory !== nextCategory) { score += 10; - if (nextCategory === CharBoundaryCategory.WordUpper) { + if (prevCategory === CharBoundaryCategory.WordLower && nextCategory === CharBoundaryCategory.WordUpper) { score += 1; } } diff --git a/src/vs/editor/test/node/diffing/fixtures/import-shifting/1.tst b/src/vs/editor/test/node/diffing/fixtures/import-shifting/1.tst new file mode 100644 index 00000000000..ff1f7ccada9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/import-shifting/1.tst @@ -0,0 +1,3 @@ +import { RuntimeMode } from './runtimeMode'; +import { PromiseQueue } from './telemetry'; +import { TestNotificationSender, TestUrlOpener } from './testHelpers'; diff --git a/src/vs/editor/test/node/diffing/fixtures/import-shifting/2.tst b/src/vs/editor/test/node/diffing/fixtures/import-shifting/2.tst new file mode 100644 index 00000000000..3955d074aa9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/import-shifting/2.tst @@ -0,0 +1,3 @@ +import { RuntimeMode } from './runtimeMode'; +import { PromiseQueue, TestPromiseQueue } from './telemetry'; +import { TestNotificationSender, TestUrlOpener } from './testHelpers'; diff --git a/src/vs/editor/test/node/diffing/fixtures/import-shifting/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/import-shifting/advanced.expected.diff.json new file mode 100644 index 00000000000..adbfd5208d5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/import-shifting/advanced.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue, TestPromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,22 -> 2,22]", + "modifiedRange": "[2,22 -> 2,40]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/import-shifting/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/import-shifting/legacy.expected.diff.json new file mode 100644 index 00000000000..adbfd5208d5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/import-shifting/legacy.expected.diff.json @@ -0,0 +1,22 @@ +{ + "original": { + "content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "import { RuntimeMode } from './runtimeMode';\nimport { PromiseQueue, TestPromiseQueue } from './telemetry';\nimport { TestNotificationSender, TestUrlOpener } from './testHelpers';\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,22 -> 2,22]", + "modifiedRange": "[2,22 -> 2,40]" + } + ] + } + ] +} \ No newline at end of file From 3e31b71648f86e0c1883cf3a038362555281c6e7 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 19 Sep 2023 21:00:13 +0200 Subject: [PATCH 103/133] SCM - more cleanup of the SCMHistoryProvider API (#193509) --- extensions/git/src/historyProvider.ts | 16 +++++++++++++--- src/vs/workbench/api/browser/mainThreadSCM.ts | 13 +++++++++---- src/vs/workbench/api/common/extHost.protocol.ts | 3 ++- src/vs/workbench/api/common/extHostSCM.ts | 9 +++++++-- .../contrib/scm/browser/scmSyncViewPane.ts | 14 ++++++++++---- src/vs/workbench/contrib/scm/common/history.ts | 3 ++- .../vscode.proposed.scmHistoryProvider.d.ts | 4 ++-- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 3e3862ae65f..3a5541152ae 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -106,12 +106,22 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos })); } - async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { - refId2 = refId2 ?? (await this.repository.getDefaultBranch()).name ?? ''; - if (refId2 === '') { + async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { + // TODO - support for all history item groups + if (historyItemGroupId !== this.currentHistoryItemGroup?.id) { return undefined; } + if (this.currentHistoryItemGroup?.upstream) { + return this.currentHistoryItemGroup.upstream; + } + + // Default branch + const defaultBranch = await this.repository.getDefaultBranch(); + return defaultBranch.name ? { id: `refs/heads/${defaultBranch.name}`, label: defaultBranch.name } : undefined; + } + + async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { const ancestor = await this.repository.getMergeBase(refId1, refId2); if (ancestor === '') { return undefined; diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 40e92bf942c..62644a0e1ce 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -16,7 +16,7 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryOptions, ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; function getSCMHistoryItemIcon(historyItem: SCMHistoryItemDto): URI | { light: URI; dark: URI } | ThemeIcon | undefined { if (!historyItem.icon) { @@ -201,9 +201,10 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._historyProvider = { actionButton: () => this._historyProviderActionButton ?? undefined, currentHistoryItemGroup: () => this._historyProviderCurrentHistoryItemGroup ?? undefined, - provideHistoryItems: (historyItemGroupId, options) => this.provideHistoryItems(historyItemGroupId, options), + provideHistoryItems: (historyItemGroupId: string, options: ISCMHistoryOptions) => this.provideHistoryItems(historyItemGroupId, options), provideHistoryItemChanges: (historyItemId: string) => this.provideHistoryItemChanges(historyItemId), - resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1, historyItemGroupId2) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), + resolveHistoryItemGroupBase: (historyItemGroupId: string) => this.resolveHistoryItemGroupBase(historyItemGroupId), + resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1: string, historyItemGroupId2: string) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), }; } else if (features.hasHistoryProvider === false && this._historyProvider) { this._historyProvider = undefined; @@ -327,7 +328,11 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { this._onDidChangeHistoryProviderCurrentHistoryItemGroup.fire(); } - async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> { + async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { + return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None); + } + + async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5591666698c..2299e4b4a2c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2159,7 +2159,8 @@ export interface ExtHostSCMShape { $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise; - $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; + $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; + $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; } export interface ExtHostQuickDiffShape { diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 307ff297dc7..561592baf84 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemGroupDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import type * as vscode from 'vscode'; @@ -952,7 +952,12 @@ export class ExtHostSCM implements ExtHostSCMShape { return Promise.resolve(undefined); } - async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { + async $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + return await historyProvider?.resolveHistoryItemGroupBase(historyItemGroupId, token) ?? undefined; + } + + async $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined> { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2, token) ?? undefined; } diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index ad69941cc4d..a81f6516c3e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -516,15 +516,21 @@ class SCMSyncDataSource implements IAsyncDataSource { } as ISCMActionButton); } + // History item group base + const historyItemGroupBase = await historyProvider.resolveHistoryItemGroupBase(historyItemGroup.id); + if (!historyItemGroupBase) { + return children; + } + // Common ancestor, ahead, behind - const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroup.upstream?.id); + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(historyItemGroup.id, historyItemGroupBase.id); // Incoming - if (historyItemGroup?.upstream) { + if (historyItemGroupBase) { children.push({ - id: historyItemGroup.upstream.id, + id: historyItemGroupBase.id, label: localize('incoming', "$(cloud-download) Incoming Changes"), - description: historyItemGroup.upstream.label, + description: historyItemGroupBase.label, ancestor: ancestor?.id, count: ancestor?.behind ?? 0, repository: element, diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index 7fd225a37cd..e1f130600e5 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -12,7 +12,8 @@ export interface ISCMHistoryProvider { currentHistoryItemGroup: () => ISCMHistoryItemGroup | undefined; provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; provideHistoryItemChanges(historyItemId: string): Promise; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined>; + resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; } export interface ISCMHistoryOptions { diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 5fad4bd5da4..5d81cf2a1f4 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -31,9 +31,9 @@ declare module 'vscode' { provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, token: CancellationToken): ProviderResult; - resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string | undefined, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; - // resolveHistoryItemGroup(historyItemGroupId: string, token: CancellationToken): ProviderResult; + resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult; + resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId: string, token: CancellationToken): ProviderResult<{ id: string; ahead: number; behind: number }>; } export interface SourceControlHistoryOptions { From 65edfbaca0fa5569c73d9e753f0e545d29220b3f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Sep 2023 21:03:02 +0200 Subject: [PATCH 104/133] editors - check model for being too large (#193427) (#193510) --- src/vs/workbench/browser/dnd.ts | 4 ++-- .../contrib/files/browser/editors/fileEditorInput.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 52afb8083d8..03550d2d646 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -294,8 +294,8 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito editor.encoding = textFileModel.getEncoding(); } - // contents (only if dirty) - if (typeof editor.contents !== 'string' && textFileModel.isDirty()) { + // contents (only if dirty and not too large) + if (typeof editor.contents !== 'string' && textFileModel.isDirty() && !textFileModel.textEditorModel.isTooLargeForHeapOperation()) { editor.contents = textFileModel.textEditorModel.getValue(); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index faf84eafbf9..d6c151043f0 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -433,8 +433,8 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements untypedInput.languageId = this.getLanguageId(); untypedInput.contents = (() => { const model = this.textFileService.files.get(this.resource); - if (model?.isDirty()) { - return model.textEditorModel.getValue(); // only if dirty + if (model?.isDirty() && !model.textEditorModel.isTooLargeForHeapOperation()) { + return model.textEditorModel.getValue(); // only if dirty and not too large } return undefined; From 82be874706c22b94b02c4c622038df0b6dc7bec8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Sep 2023 21:03:21 +0200 Subject: [PATCH 105/133] Allow text in error message popups to be copied or at least selected (fix #193243) (#193511) --- .../workbench/browser/parts/editor/media/editorplaceholder.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css b/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css index ad0d6936909..d4db8eaff00 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css +++ b/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css @@ -44,6 +44,8 @@ max-width: 450px; text-align: center; word-break: break-word; + user-select: text; + -webkit-user-select: text; } .monaco-editor-pane-placeholder .editor-placeholder-buttons-container { From fdf7563f9e9c94804ad498b51b28eb5f539b7f50 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Sep 2023 21:08:34 +0200 Subject: [PATCH 106/133] Fuzzy scorer tests may have mismatched casing (fix #193396) (#193514) --- src/vs/base/test/common/fuzzyScorer.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 03417dea597..86dfd034418 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -102,7 +102,7 @@ const NullAccessor = new NullAccessorClass(); suite('Fuzzy Scorer', () => { test('score (fuzzy)', function () { - const target = 'HeLlo-World'; + const target = 'HelLo-World'; const scores: FuzzyScore[] = []; scores.push(_doScore(target, 'HelLo-World', true)); // direct case match @@ -134,7 +134,7 @@ suite('Fuzzy Scorer', () => { }); test('score (non fuzzy)', function () { - const target = 'HeLlo-World'; + const target = 'HelLo-World'; assert.ok(_doScore(target, 'HelLo-World', false)[0] > 0); assert.strictEqual(_doScore(target, 'HelLo-World', false)[1].length, 'HelLo-World'.length); @@ -1161,10 +1161,10 @@ suite('Fuzzy Scorer', () => { }); test('fuzzyScore2 (matching)', function () { - const target = 'HeLlo-World'; + const target = 'HelLo-World'; for (const offset of [0, 3]) { - let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HeLlo-World', offset); + let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HelLo-World', offset); assert.ok(score); assert.strictEqual(matches.length, 1); @@ -1183,7 +1183,7 @@ suite('Fuzzy Scorer', () => { }); test('fuzzyScore2 (multiple queries)', function () { - const target = 'HeLlo-World'; + const target = 'HelLo-World'; const [firstSingleScore, firstSingleMatches] = _doScore2(target, 'HelLo'); const [secondSingleScore, secondSingleMatches] = _doScore2(target, 'World'); From 34648687295a48cffca22cb87b5c8356f6ec2628 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 12:26:32 -0700 Subject: [PATCH 107/133] use helper --- .../workbench/contrib/terminal/browser/terminalActions.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 325d7988c89..0c707050a73 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -491,11 +491,7 @@ export function registerTerminalActions() { return; } c.service.setActiveInstance(instance); - if (instance.target === TerminalLocation.Panel) { - c.groupService.showPanel(true); - } else { - c.editorService.activeInstance?.focus(); - } + c.service.focusActiveInstance(); } }); From 33bbfac7ec646d0cceccf07ee291ae19ed999d95 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 12:28:54 -0700 Subject: [PATCH 108/133] better fix --- src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 88c773aa7ee..6197b74a44d 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -28,9 +28,10 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService private readonly _logService: ILogService) { super(); - this._taskService.onDidReconnectToTasks((() => this._tryRunTasks())); if (this._taskService.isReconnected) { this._tryRunTasks(); + } else { + this._register(Event.once(this._taskService.onDidReconnectToTasks)(() => this._tryRunTasks())); } this._register(this._workspaceTrustManagementService.onDidChangeTrust(async trusted => { if (trusted) { From 5c0aac4bc44e3dd04abcc375d8837bc572ffe34e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 12:30:44 -0700 Subject: [PATCH 109/133] await --- .../workbench/contrib/tasks/browser/runAutomaticTasks.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 6197b74a44d..a4b4239b43c 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -31,13 +31,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut if (this._taskService.isReconnected) { this._tryRunTasks(); } else { - this._register(Event.once(this._taskService.onDidReconnectToTasks)(() => this._tryRunTasks())); + this._register(Event.once(this._taskService.onDidReconnectToTasks)(async () => await this._tryRunTasks())); } - this._register(this._workspaceTrustManagementService.onDidChangeTrust(async trusted => { - if (trusted) { - await this._tryRunTasks(); - } - })); + this._register(this._workspaceTrustManagementService.onDidChangeTrust(async () => await this._tryRunTasks())); } private async _tryRunTasks() { From a3c288eea5d20e9627d09c33abb797b019322fe5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Sep 2023 21:46:37 +0200 Subject: [PATCH 110/133] debt - keep main services in `electron-main` (#193516) --- src/vs/code/electron-main/app.ts | 2 +- .../externalTerminal/common/externalTerminal.ts | 6 ------ .../externalTerminal.ts} | 3 --- .../electron-sandbox/externalTerminalService.ts | 16 ++++++++++++++++ .../node/externalTerminalService.ts | 8 ++++---- .../externalTerminal.contribution.ts | 6 +++--- 6 files changed, 24 insertions(+), 17 deletions(-) rename src/vs/platform/externalTerminal/{electron-sandbox/externalTerminalMainService.ts => electron-main/externalTerminal.ts} (81%) create mode 100644 src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 5307987e657..06ecbdd6396 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -45,7 +45,7 @@ import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/commo import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter'; -import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-main/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient'; import { IFileService } from 'vs/platform/files/common/files'; diff --git a/src/vs/platform/externalTerminal/common/externalTerminal.ts b/src/vs/platform/externalTerminal/common/externalTerminal.ts index 290bb20fca3..6d8da336419 100644 --- a/src/vs/platform/externalTerminal/common/externalTerminal.ts +++ b/src/vs/platform/externalTerminal/common/externalTerminal.ts @@ -35,9 +35,3 @@ export interface IExternalTerminalConfiguration { } export const DEFAULT_TERMINAL_OSX = 'Terminal.app'; - -export const IExternalTerminalMainService = createDecorator('externalTerminal'); - -export interface IExternalTerminalMainService extends IExternalTerminalService { - readonly _serviceBrand: undefined; -} diff --git a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts b/src/vs/platform/externalTerminal/electron-main/externalTerminal.ts similarity index 81% rename from src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts rename to src/vs/platform/externalTerminal/electron-main/externalTerminal.ts index 4f2ec5e3941..af6925563c7 100644 --- a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService.ts +++ b/src/vs/platform/externalTerminal/electron-main/externalTerminal.ts @@ -5,12 +5,9 @@ import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; export const IExternalTerminalMainService = createDecorator('externalTerminal'); export interface IExternalTerminalMainService extends IExternalTerminalService { readonly _serviceBrand: undefined; } - -registerMainProcessRemoteService(IExternalTerminalMainService, 'externalTerminal'); diff --git a/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts b/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.ts new file mode 100644 index 00000000000..209c24d072b --- /dev/null +++ b/src/vs/platform/externalTerminal/electron-sandbox/externalTerminalService.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 { IExternalTerminalService as ICommonExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; + +export const IExternalTerminalService = createDecorator('externalTerminal'); + +export interface IExternalTerminalService extends ICommonExternalTerminalService { + readonly _serviceBrand: undefined; +} + +registerMainProcessRemoteService(IExternalTerminalService, 'externalTerminal'); diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 9b0b2ba548c..3dcc1c49d0a 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -11,7 +11,7 @@ import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; import * as pfs from 'vs/base/node/pfs'; import * as processes from 'vs/base/node/processes'; import * as nls from 'vs/nls'; -import { DEFAULT_TERMINAL_OSX, IExternalTerminalMainService, IExternalTerminalSettings, ITerminalForPlatform } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { DEFAULT_TERMINAL_OSX, IExternalTerminalService, IExternalTerminalSettings, ITerminalForPlatform } from 'vs/platform/externalTerminal/common/externalTerminal'; import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -28,7 +28,7 @@ abstract class ExternalTerminalService { } } -export class WindowsExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { +export class WindowsExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService { private static readonly CMD = 'cmd.exe'; private static _DEFAULT_TERMINAL_WINDOWS: string; @@ -125,7 +125,7 @@ export class WindowsExternalTerminalService extends ExternalTerminalService impl } } -export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { +export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService { private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise { @@ -215,7 +215,7 @@ export class MacExternalTerminalService extends ExternalTerminalService implemen } } -export class LinuxExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService { +export class LinuxExternalTerminalService extends ExternalTerminalService implements IExternalTerminalService { private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index 92be8da593c..7b36ebba216 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/path'; -import { DEFAULT_TERMINAL_OSX, IExternalTerminalService, IExternalTerminalSettings } from 'vs/platform/externalTerminal/common/externalTerminal'; +import { DEFAULT_TERMINAL_OSX, IExternalTerminalSettings } from 'vs/platform/externalTerminal/common/externalTerminal'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; import { IConfigurationRegistry, Extensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-sandbox/externalTerminalMainService'; +import { IExternalTerminalService } from 'vs/platform/externalTerminal/electron-sandbox/externalTerminalService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -84,7 +84,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { export class ExternalTerminalContribution implements IWorkbenchContribution { public _serviceBrand: undefined; - constructor(@IExternalTerminalMainService private readonly _externalTerminalService: IExternalTerminalMainService) { + constructor(@IExternalTerminalService private readonly _externalTerminalService: IExternalTerminalService) { this._updateConfiguration(); } From b8c1929f961e5cf8c53de4268a917c6cc922757e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 19 Sep 2023 12:48:11 -0700 Subject: [PATCH 111/133] Update src/vs/workbench/contrib/terminal/browser/terminalActions.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0c707050a73..83fe8c2d7c0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -491,7 +491,7 @@ export function registerTerminalActions() { return; } c.service.setActiveInstance(instance); - c.service.focusActiveInstance(); + focusActiveInstance(instance, c); } }); From c44e774e116d36b4187ef98a267e5c85a612fa4a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 19 Sep 2023 13:15:11 -0700 Subject: [PATCH 112/133] fix --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 83fe8c2d7c0..d14a577769d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -491,7 +491,7 @@ export function registerTerminalActions() { return; } c.service.setActiveInstance(instance); - focusActiveInstance(instance, c); + focusActiveTerminal(instance, c); } }); From 6bd11d0301bebf455f3fe505770d50cbc2ec01fe Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:52:40 -0700 Subject: [PATCH 113/133] Added placeholder for extensions dropdown (#193504) * disable code actions specifically for autosave after delay: * added fix for placeholder * cleanup and removed empty option * code cleanup * specify htmlelement type Co-authored-by: Tyler James Leonhardt --------- Co-authored-by: Tyler James Leonhardt --- src/vs/code/electron-sandbox/issue/issueReporterService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index 89fcc9d0e75..e095b3d6d03 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -1052,10 +1052,11 @@ export class IssueReporter extends Disposable { }, extension.name); }; - const extensionsSelector = this.getElementById('extension-selector'); + const extensionsSelector = this.getElementById('extension-selector'); if (extensionsSelector) { const { selectedExtension } = this.issueReporterModel.getData(); - reset(extensionsSelector, $('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); + reset(extensionsSelector, this.makeOption('', localize('selectExtension', "Select extension"), true), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); + extensionsSelector.selectedIndex = 0; this.addEventListener('extension-selector', 'change', (e: Event) => { const selectedExtensionId = (e.target).value; From a18ef5588e48c3ec192a5a0f43509f75267ba718 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Sep 2023 00:14:53 +0200 Subject: [PATCH 114/133] SCM - extracted code into MainThreadSCMHistoryProvider (#193520) --- src/vs/workbench/api/browser/mainThreadSCM.ts | 104 +++++++++++------- .../contrib/scm/browser/scmSyncViewPane.ts | 25 ++++- .../workbench/contrib/scm/common/history.ts | 13 ++- src/vs/workbench/contrib/scm/common/scm.ts | 3 +- 4 files changed, 94 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 62644a0e1ce..5ddf13e573e 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -104,6 +104,55 @@ class MainThreadSCMResource implements ISCMResource { } } +class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { + + private _onDidChangeActionButton = new Emitter(); + readonly onDidChangeActionButton = this._onDidChangeActionButton.event; + + private _onDidChangeCurrentHistoryItemGroup = new Emitter(); + readonly onDidChangeCurrentHistoryItemGroup = this._onDidChangeCurrentHistoryItemGroup.event; + + private _actionButton: ISCMActionButtonDescriptor | undefined; + get actionButton(): ISCMActionButtonDescriptor | undefined { return this._actionButton; } + set actionButton(actionButton: ISCMActionButtonDescriptor | undefined) { + this._actionButton = actionButton; + this._onDidChangeActionButton.fire(); + } + + private _currentHistoryItemGroup: ISCMHistoryItemGroup | undefined; + get currentHistoryItemGroup(): ISCMHistoryItemGroup | undefined { return this._currentHistoryItemGroup; } + set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined) { + this._currentHistoryItemGroup = historyItemGroup; + this._onDidChangeCurrentHistoryItemGroup.fire(); + } + + constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } + + async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { + return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None); + } + + async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { + return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); + } + + async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise { + const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None); + return historyItems?.map(historyItem => ({ ...historyItem, icon: getSCMHistoryItemIcon(historyItem), })); + } + + async provideHistoryItemChanges(historyItemId: string): Promise { + const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, CancellationToken.None); + return changes?.map(change => ({ + uri: URI.revive(change.uri), + originalUri: change.originalUri && URI.revive(change.originalUri), + modifiedUri: change.modifiedUri && URI.revive(change.modifiedUri), + renameUri: change.renameUri && URI.revive(change.renameUri) + })); + } + +} + class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { private static ID_HANDLE = 0; @@ -147,11 +196,8 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { private readonly _onDidChangeStatusBarCommands = new Emitter(); get onDidChangeStatusBarCommands(): Event { return this._onDidChangeStatusBarCommands.event; } - private readonly _onDidChangeHistoryProviderActionButton = new Emitter(); - readonly onDidChangeHistoryProviderActionButton: Event = this._onDidChangeHistoryProviderActionButton.event; - - private readonly _onDidChangeHistoryProviderCurrentHistoryItemGroup = new Emitter(); - readonly onDidChangeHistoryProviderCurrentHistoryItemGroup: Event = this._onDidChangeHistoryProviderCurrentHistoryItemGroup.event; + private readonly _onDidChangeHistoryProvider = new Emitter(); + readonly onDidChangeHistoryProvider: Event = this._onDidChangeHistoryProvider.event; private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; @@ -160,8 +206,6 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { public readonly isSCM: boolean = true; private _historyProvider: ISCMHistoryProvider | undefined; - private _historyProviderActionButton: SCMActionButtonDto | undefined | null; - private _historyProviderCurrentHistoryItemGroup: SCMHistoryItemGroupDto | undefined; constructor( private readonly proxy: ExtHostSCMShape, @@ -198,16 +242,11 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { } if (features.hasHistoryProvider && !this._historyProvider) { - this._historyProvider = { - actionButton: () => this._historyProviderActionButton ?? undefined, - currentHistoryItemGroup: () => this._historyProviderCurrentHistoryItemGroup ?? undefined, - provideHistoryItems: (historyItemGroupId: string, options: ISCMHistoryOptions) => this.provideHistoryItems(historyItemGroupId, options), - provideHistoryItemChanges: (historyItemId: string) => this.provideHistoryItemChanges(historyItemId), - resolveHistoryItemGroupBase: (historyItemGroupId: string) => this.resolveHistoryItemGroupBase(historyItemGroupId), - resolveHistoryItemGroupCommonAncestor: (historyItemGroupId1: string, historyItemGroupId2: string) => this.resolveHistoryItemGroupCommonAncestor(historyItemGroupId1, historyItemGroupId2), - }; + this._historyProvider = new MainThreadSCMHistoryProvider(this.proxy, this.handle); + this._onDidChangeHistoryProvider.fire(); } else if (features.hasHistoryProvider === false && this._historyProvider) { this._historyProvider = undefined; + this._onDidChangeHistoryProvider.fire(); } } @@ -319,36 +358,19 @@ class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { } $onDidChangeHistoryProviderActionButton(actionButton?: SCMActionButtonDto | null): void { - this._historyProviderActionButton = actionButton; - this._onDidChangeHistoryProviderActionButton.fire(); + if (!this._historyProvider) { + return; + } + + this._historyProvider.actionButton = actionButton ?? undefined; } $onDidChangeHistoryProviderCurrentHistoryItemGroup(currentHistoryItemGroup?: SCMHistoryItemGroupDto): void { - this._historyProviderCurrentHistoryItemGroup = currentHistoryItemGroup; - this._onDidChangeHistoryProviderCurrentHistoryItemGroup.fire(); - } + if (!this._historyProvider) { + return; + } - async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { - return this.proxy.$resolveHistoryItemGroupBase(this.handle, historyItemGroupId, CancellationToken.None); - } - - async resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { - return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupId1, historyItemGroupId2, CancellationToken.None); - } - - async provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise { - const historyItems = await this.proxy.$provideHistoryItems(this.handle, historyItemGroupId, options, CancellationToken.None); - return historyItems?.map(historyItem => ({ ...historyItem, icon: getSCMHistoryItemIcon(historyItem), })); - } - - async provideHistoryItemChanges(historyItemId: string): Promise { - const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, CancellationToken.None); - return changes?.map(change => ({ - uri: URI.revive(change.uri), - originalUri: change.originalUri && URI.revive(change.originalUri), - modifiedUri: change.modifiedUri && URI.revive(change.modifiedUri), - renameUri: change.renameUri && URI.revive(change.renameUri) - })); + this._historyProvider.currentHistoryItemGroup = currentHistoryItemGroup ?? undefined; } toJSON(): any { diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index a81f6516c3e..e02a5117ae4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -408,6 +408,8 @@ export class SCMSyncViewPane extends ViewPane { class SCMSyncPaneViewModel { private repositories = new Map(); + private historyProviders = new Map(); + private alwaysShowRepositories = false; private readonly disposables = new DisposableStore(); @@ -434,10 +436,8 @@ class SCMSyncPaneViewModel { private _onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { for (const repository of added) { - const repositoryDisposable: IDisposable = combinedDisposable( - repository.provider.onDidChangeHistoryProviderActionButton(() => this.refresh(repository)), - repository.provider.onDidChangeHistoryProviderCurrentHistoryItemGroup(() => this.refresh(repository)) - ); + const repositoryDisposable = repository.provider.onDidChangeHistoryProvider(() => this._onDidChangeHistoryProvider(repository)); + this._onDidChangeHistoryProvider(repository); this.repositories.set(repository, { dispose() { repositoryDisposable.dispose(); } }); } @@ -450,6 +450,19 @@ class SCMSyncPaneViewModel { this.refresh(); } + private _onDidChangeHistoryProvider(repository: ISCMRepository): void { + if (repository.provider.historyProvider) { + const historyProviderDisposable = combinedDisposable( + repository.provider.historyProvider.onDidChangeActionButton(() => this.refresh(repository)), + repository.provider.historyProvider.onDidChangeCurrentHistoryItemGroup(() => this.refresh(repository))); + + this.historyProviders.set(repository, historyProviderDisposable); + } else { + this.historyProviders.get(repository)?.dispose(); + this.historyProviders.delete(repository); + } + } + private async refresh(repository?: ISCMRepository): Promise { if (this.repositories.size === 0) { return; @@ -500,14 +513,14 @@ class SCMSyncDataSource implements IAsyncDataSource { } else if (isSCMRepository(element)) { const scmProvider = element.provider; const historyProvider = scmProvider.historyProvider; - const historyItemGroup = historyProvider?.currentHistoryItemGroup(); + const historyItemGroup = historyProvider?.currentHistoryItemGroup; if (!historyProvider || !historyItemGroup) { return children; } // Action Button - const actionButton = historyProvider.actionButton(); + const actionButton = historyProvider.actionButton; if (actionButton) { children.push({ type: 'actionButton', diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index e1f130600e5..b9f6792c9ea 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -3,13 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm'; export interface ISCMHistoryProvider { - actionButton: () => ISCMActionButtonDescriptor | undefined; - currentHistoryItemGroup: () => ISCMHistoryItemGroup | undefined; + + readonly onDidChangeActionButton: Event; + readonly onDidChangeCurrentHistoryItemGroup: Event; + + get actionButton(): ISCMActionButtonDescriptor | undefined; + set actionButton(button: ISCMActionButtonDescriptor | undefined); + + get currentHistoryItemGroup(): ISCMHistoryItemGroup | undefined; + set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined); + provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; provideHistoryItemChanges(historyItemId: string): Promise; resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 52d9d7637c7..e7d39bfecbb 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -67,8 +67,7 @@ export interface ISCMProvider extends IDisposable { readonly commitTemplate: string; readonly historyProvider?: ISCMHistoryProvider; readonly onDidChangeCommitTemplate: Event; - readonly onDidChangeHistoryProviderActionButton: Event; - readonly onDidChangeHistoryProviderCurrentHistoryItemGroup: Event; + readonly onDidChangeHistoryProvider: Event; readonly onDidChangeStatusBarCommands?: Event; readonly acceptInputCommand?: Command; readonly actionButton?: ISCMActionButtonDescriptor; From 17726b2b0cb6b8c259980a76cd0a711a8d9607f0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Sep 2023 00:27:48 +0200 Subject: [PATCH 115/133] icon selection widget (#193518) * icon selection widget * fix scrolling --- .../base/browser/ui/icons/iconSelectBox.css | 30 +++ src/vs/base/browser/ui/icons/iconSelectBox.ts | 217 ++++++++++++++++++ src/vs/base/common/codicons.ts | 2 +- src/vs/workbench/browser/iconSelectBox.ts | 98 ++++++++ src/vs/workbench/workbench.common.main.ts | 1 + 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 src/vs/base/browser/ui/icons/iconSelectBox.css create mode 100644 src/vs/base/browser/ui/icons/iconSelectBox.ts create mode 100644 src/vs/workbench/browser/iconSelectBox.ts diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.css b/src/vs/base/browser/ui/icons/iconSelectBox.css new file mode 100644 index 00000000000..2a1ed82f10e --- /dev/null +++ b/src/vs/base/browser/ui/icons/iconSelectBox.css @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.icon-select-box>.icon-select-box-container { + height: 100%; +} + +.icon-select-box .icon-select-icons-container { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + height: 100%; +} + +.icon-select-box .icon-select-icons-container > .icon-container { + display: inline-flex; + cursor: pointer; + font-size: 20px; + align-items: center; + justify-content: center; + border-radius: 5px; +} + +.icon-select-box .icon-select-icons-container > .icon-container.focused { + outline: 1px dashed var(--vscode-toolbar-hoverOutline); + outline-offset: -1px; + background-color: var(--vscode-toolbar-hoverBackground); +} diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts new file mode 100644 index 00000000000..529da9eb154 --- /dev/null +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -0,0 +1,217 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./iconSelectBox'; +import * as dom from 'vs/base/browser/dom'; +import { IInputBoxStyles, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { Emitter } from 'vs/base/common/event'; +import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { localize } from 'vs/nls'; +import { IMatch } from 'vs/base/common/filters'; + +export interface IIconSelectBoxOptions { + readonly icons: ThemeIcon[]; + readonly inputBoxStyles: IInputBoxStyles; +} + +export class IconSelectBox extends Disposable { + + readonly domNode: HTMLElement; + + private _onDidSelect = this._register(new Emitter()); + readonly onDidSelect = this._onDidSelect.event; + + private renderedIcons: [ThemeIcon, HTMLElement][] = []; + + private focusedItemIndex: number = 0; + private numberOfElementsPerRow: number = 1; + + private inputBox: InputBox | undefined; + private scrollableElement: DomScrollableElement | undefined; + private readonly iconContainerWidth = 36; + private readonly iconContainerHeight = 32; + + constructor( + private readonly options: IIconSelectBoxOptions, + ) { + super(); + this.domNode = dom.$('.icon-select-box'); + this._register(this.create()); + } + + private create(): IDisposable { + const disposables = new DisposableStore(); + + const iconSelectBoxContainer = dom.append(this.domNode, dom.$('.icon-select-box-container')); + iconSelectBoxContainer.style.margin = '10px 15px'; + + const iconSelectInputContainer = dom.append(iconSelectBoxContainer, dom.$('.icon-select-input-container')); + iconSelectInputContainer.style.paddingBottom = '10px'; + this.inputBox = disposables.add(new InputBox(iconSelectInputContainer, undefined, { + placeholder: localize('iconSelect.placeholder', "Search icons"), + inputBoxStyles: this.options.inputBoxStyles, + })); + + const iconsContainer = dom.$('.icon-select-icons-container'); + iconsContainer.style.paddingRight = '10px'; + this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, { useShadows: false })); + dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode()); + + const iconsDisposables = disposables.add(new MutableDisposable()); + iconsDisposables.value = this.renderIcons(this.options.icons, iconsContainer); + this.scrollableElement.scanDomNode(); + + disposables.add(this.inputBox.onDidChange(value => { + const icons = this.options.icons.filter(icon => { + return this.matchesContiguous(value, icon.id); + }); + iconsDisposables.value = this.renderIcons(icons, iconsContainer); + this.scrollableElement?.scanDomNode(); + })); + + return disposables; + } + + private renderIcons(icons: ThemeIcon[], container: HTMLElement): IDisposable { + const disposables = new DisposableStore(); + dom.clearNode(container); + const focusedIcon = this.renderedIcons[this.focusedItemIndex]?.[0]; + let focusedIconIndex = 0; + const renderedIcons: [ThemeIcon, HTMLElement][] = []; + for (let index = 0; index < icons.length; index++) { + const icon = icons[index]; + const iconContainer = dom.append(container, dom.$('.icon-container')); + iconContainer.style.width = `${this.iconContainerWidth}px`; + iconContainer.style.height = `${this.iconContainerHeight}px`; + iconContainer.tabIndex = -1; + iconContainer.role = 'button'; + iconContainer.title = icon.id; + dom.append(iconContainer, dom.$(ThemeIcon.asCSSSelector(icon))); + renderedIcons.push([icon, iconContainer]); + + disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.CLICK, (e: MouseEvent) => { + e.stopPropagation(); + this.setSelection(index); + })); + + disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.MOUSE_OVER, (e: MouseEvent) => { + this.focusIcon(index); + })); + + if (icon === focusedIcon) { + focusedIconIndex = index; + } + } + + this.renderedIcons.splice(0, this.renderedIcons.length, ...renderedIcons); + this.focusIcon(focusedIconIndex); + + return disposables; + } + + private focusIcon(index: number): void { + const existing = this.renderedIcons[this.focusedItemIndex]; + if (existing) { + existing[1].classList.remove('focused'); + } + + this.focusedItemIndex = index; + const icon = this.renderedIcons[index]?.[1]; + if (icon) { + icon.classList.add('focused'); + } + + this.reveal(index); + } + + private reveal(index: number): void { + if (!this.scrollableElement) { + return; + } + if (index < 0 || index >= this.renderedIcons.length) { + return; + } + const icon = this.renderedIcons[index][1]; + if (!icon) { + return; + } + const { height } = this.scrollableElement.getScrollDimensions(); + const { scrollTop } = this.scrollableElement.getScrollPosition(); + if (icon.offsetTop + this.iconContainerHeight > scrollTop + height) { + this.scrollableElement.setScrollPosition({ scrollTop: icon.offsetTop + this.iconContainerHeight - height }); + } else if (icon.offsetTop < scrollTop) { + this.scrollableElement.setScrollPosition({ scrollTop: icon.offsetTop }); + } + } + + private matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null { + const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase()); + if (matchIndex !== -1) { + return [{ start: matchIndex, end: matchIndex + word.length }]; + } + return null; + } + + layout(dimension: dom.Dimension): void { + this.domNode.style.width = `${dimension.width}px`; + this.domNode.style.height = `${dimension.height}px`; + if (this.scrollableElement) { + this.scrollableElement.getDomNode().style.height = `${dimension.height - 46}px`; + this.scrollableElement.scanDomNode(); + } + + const iconsContainerWidth = dimension.width - 40; + this.numberOfElementsPerRow = Math.floor(iconsContainerWidth / this.iconContainerWidth); + if (this.numberOfElementsPerRow === 0) { + throw new Error('Insufficient width'); + } + + const extraSpace = iconsContainerWidth % this.iconContainerWidth; + const margin = Math.floor(extraSpace / this.numberOfElementsPerRow); + for (const [, icon] of this.renderedIcons) { + icon.style.marginRight = `${margin}px`; + } + } + + getFocus(): number[] { + return [this.focusedItemIndex]; + } + + setSelection(index: number): void { + if (index < 0 || index >= this.renderedIcons.length) { + throw new Error(`Invalid index ${index}`); + } + this.focusIcon(index); + this._onDidSelect.fire(this.renderedIcons[index][0]); + } + + focus(): void { + this.inputBox?.focus(); + this.focusIcon(0); + } + + focusNext(): void { + this.focusIcon((this.focusedItemIndex + 1) % this.renderedIcons.length); + } + + focusPrevious(): void { + this.focusIcon((this.focusedItemIndex - 1 + this.renderedIcons.length) % this.renderedIcons.length); + } + + focusNextRow(): void { + this.focusIcon((this.focusedItemIndex + this.numberOfElementsPerRow) % this.renderedIcons.length); + } + + focusPreviousRow(): void { + this.focusIcon((this.focusedItemIndex - this.numberOfElementsPerRow + this.renderedIcons.length) % this.renderedIcons.length); + } + + getFocusedIcon(): ThemeIcon { + return this.renderedIcons[this.focusedItemIndex][0]; + } + +} diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 0c5fd5a01e8..f45ae37d78f 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -324,7 +324,7 @@ export const Codicon = { note: register('note', 0xeb26), octoface: register('octoface', 0xeb27), openPreview: register('open-preview', 0xeb28), - package_: register('package', 0xeb29), + package: register('package', 0xeb29), paintcan: register('paintcan', 0xeb2a), pin: register('pin', 0xeb2b), play: register('play', 0xeb2c), diff --git a/src/vs/workbench/browser/iconSelectBox.ts b/src/vs/workbench/browser/iconSelectBox.ts new file mode 100644 index 00000000000..0e38a01d30f --- /dev/null +++ b/src/vs/workbench/browser/iconSelectBox.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IIconSelectBoxOptions, IconSelectBox } from 'vs/base/browser/ui/icons/iconSelectBox'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; + +export const WorkbenchIconSelectBoxFocusContextKey = new RawContextKey('iconSelectBoxFocus', true); + +export class WorkbenchIconSelectBox extends IconSelectBox { + + private static focusedWidget: WorkbenchIconSelectBox | undefined; + static getFocusedWidget(): WorkbenchIconSelectBox | undefined { + return WorkbenchIconSelectBox.focusedWidget; + } + + constructor( + options: IIconSelectBoxOptions, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(options); + WorkbenchIconSelectBoxFocusContextKey.bindTo(this._register(contextKeyService.createScoped(this.domNode))); + } + + override focus(): void { + super.focus(); + WorkbenchIconSelectBox.focusedWidget = this; + } + +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'iconSelectBox.focusUp', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchIconSelectBoxFocusContextKey, + primary: KeyCode.UpArrow, + handler: (accessor, arg2) => { + const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); + if (selectBox) { + selectBox.focusPreviousRow(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'iconSelectBox.focusDown', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchIconSelectBoxFocusContextKey, + primary: KeyCode.DownArrow, + handler: (accessor, arg2) => { + const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); + if (selectBox) { + selectBox.focusNextRow(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'iconSelectBox.focusNext', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchIconSelectBoxFocusContextKey, + primary: KeyCode.RightArrow, + handler: (accessor, arg2) => { + const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); + if (selectBox) { + selectBox.focusNext(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'iconSelectBox.focusPrevious', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchIconSelectBoxFocusContextKey, + primary: KeyCode.LeftArrow, + handler: (accessor, arg2) => { + const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); + if (selectBox) { + selectBox.focusPrevious(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'iconSelectBox.selectFocused', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchIconSelectBoxFocusContextKey, + primary: KeyCode.Enter, + handler: (accessor, arg2) => { + const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); + if (selectBox) { + selectBox.setSelection(selectBox.getFocus()[0]); + } + } +}); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 1f6253a29b6..b4ad16270af 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -47,6 +47,7 @@ import 'vs/workbench/browser/parts/paneCompositePart'; import 'vs/workbench/browser/parts/banner/bannerPart'; import 'vs/workbench/browser/parts/statusbar/statusbarPart'; import 'vs/workbench/browser/parts/views/viewsService'; +import 'vs/workbench/browser/iconSelectBox'; //#endregion From e520251188ad336dafb604980c34550c220385cf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Sep 2023 01:47:34 +0200 Subject: [PATCH 116/133] some fixes to icon selection widget (#193536) * some fixes to icon selection widget * fix padding --- .../base/browser/ui/icons/iconSelectBox.css | 11 ++- src/vs/base/browser/ui/icons/iconSelectBox.ts | 81 +++++++++++++------ 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.css b/src/vs/base/browser/ui/icons/iconSelectBox.css index 2a1ed82f10e..a5a9b767478 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.css +++ b/src/vs/base/browser/ui/icons/iconSelectBox.css @@ -3,14 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.icon-select-box>.icon-select-box-container { +.icon-select-box > .icon-select-box-container { height: 100%; } .icon-select-box .icon-select-icons-container { - display: flex; - flex-wrap: wrap; - box-sizing: border-box; height: 100%; } @@ -28,3 +25,9 @@ outline-offset: -1px; background-color: var(--vscode-toolbar-hoverBackground); } + +.icon-select-box .icon-select-id-container .icon-select-id-label { + height: 24px; + padding: 10px; + opacity: .8; +} diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index 529da9eb154..421893899b1 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -5,6 +5,7 @@ import 'vs/css!./iconSelectBox'; import * as dom from 'vs/base/browser/dom'; +import { alert } from 'vs/base/browser/ui/aria/aria'; import { IInputBoxStyles, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Emitter } from 'vs/base/common/event'; @@ -12,6 +13,7 @@ import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/ import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { IMatch } from 'vs/base/common/filters'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; export interface IIconSelectBoxOptions { readonly icons: ThemeIcon[]; @@ -32,6 +34,7 @@ export class IconSelectBox extends Disposable { private inputBox: InputBox | undefined; private scrollableElement: DomScrollableElement | undefined; + private iconIdElement: HTMLElement | undefined; private readonly iconContainerWidth = 36; private readonly iconContainerHeight = 32; @@ -58,8 +61,12 @@ export class IconSelectBox extends Disposable { const iconsContainer = dom.$('.icon-select-icons-container'); iconsContainer.style.paddingRight = '10px'; - this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, { useShadows: false })); + this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, { + useShadows: false, + horizontal: ScrollbarVisibility.Hidden, + })); dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode()); + this.iconIdElement = dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')); const iconsDisposables = disposables.add(new MutableDisposable()); iconsDisposables.value = this.renderIcons(this.options.icons, iconsContainer); @@ -82,29 +89,35 @@ export class IconSelectBox extends Disposable { const focusedIcon = this.renderedIcons[this.focusedItemIndex]?.[0]; let focusedIconIndex = 0; const renderedIcons: [ThemeIcon, HTMLElement][] = []; - for (let index = 0; index < icons.length; index++) { - const icon = icons[index]; - const iconContainer = dom.append(container, dom.$('.icon-container')); - iconContainer.style.width = `${this.iconContainerWidth}px`; - iconContainer.style.height = `${this.iconContainerHeight}px`; - iconContainer.tabIndex = -1; - iconContainer.role = 'button'; - iconContainer.title = icon.id; - dom.append(iconContainer, dom.$(ThemeIcon.asCSSSelector(icon))); - renderedIcons.push([icon, iconContainer]); + if (icons.length) { + for (let index = 0; index < icons.length; index++) { + const icon = icons[index]; + const iconContainer = dom.append(container, dom.$('.icon-container')); + iconContainer.style.width = `${this.iconContainerWidth}px`; + iconContainer.style.height = `${this.iconContainerHeight}px`; + iconContainer.tabIndex = -1; + iconContainer.role = 'button'; + iconContainer.title = icon.id; + dom.append(iconContainer, dom.$(ThemeIcon.asCSSSelector(icon))); + renderedIcons.push([icon, iconContainer]); - disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.CLICK, (e: MouseEvent) => { - e.stopPropagation(); - this.setSelection(index); - })); + disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.CLICK, (e: MouseEvent) => { + e.stopPropagation(); + this.setSelection(index); + })); - disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.MOUSE_OVER, (e: MouseEvent) => { - this.focusIcon(index); - })); + disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.MOUSE_OVER, (e: MouseEvent) => { + this.focusIcon(index); + })); - if (icon === focusedIcon) { - focusedIconIndex = index; + if (icon === focusedIcon) { + focusedIconIndex = index; + } } + } else { + const noResults = localize('iconSelect.noResults', "No results"); + dom.append(container, dom.$('.icon-no-results', undefined, noResults)); + alert(noResults); } this.renderedIcons.splice(0, this.renderedIcons.length, ...renderedIcons); @@ -125,6 +138,10 @@ export class IconSelectBox extends Disposable { icon.classList.add('focused'); } + if (this.iconIdElement) { + this.iconIdElement.textContent = this.renderedIcons[index]?.[0].id; + } + this.reveal(index); } @@ -159,10 +176,6 @@ export class IconSelectBox extends Disposable { layout(dimension: dom.Dimension): void { this.domNode.style.width = `${dimension.width}px`; this.domNode.style.height = `${dimension.height}px`; - if (this.scrollableElement) { - this.scrollableElement.getDomNode().style.height = `${dimension.height - 46}px`; - this.scrollableElement.scanDomNode(); - } const iconsContainerWidth = dimension.width - 40; this.numberOfElementsPerRow = Math.floor(iconsContainerWidth / this.iconContainerWidth); @@ -175,6 +188,11 @@ export class IconSelectBox extends Disposable { for (const [, icon] of this.renderedIcons) { icon.style.marginRight = `${margin}px`; } + + if (this.scrollableElement) { + this.scrollableElement.getDomNode().style.height = `${dimension.height - 80}px`; + this.scrollableElement.scanDomNode(); + } } getFocus(): number[] { @@ -203,11 +221,22 @@ export class IconSelectBox extends Disposable { } focusNextRow(): void { - this.focusIcon((this.focusedItemIndex + this.numberOfElementsPerRow) % this.renderedIcons.length); + let nextRowIndex = this.focusedItemIndex + this.numberOfElementsPerRow; + if (nextRowIndex >= this.renderedIcons.length) { + nextRowIndex = (nextRowIndex % this.numberOfElementsPerRow) + 1; + nextRowIndex = nextRowIndex === this.numberOfElementsPerRow ? 0 : nextRowIndex; + } + this.focusIcon(nextRowIndex); } focusPreviousRow(): void { - this.focusIcon((this.focusedItemIndex - this.numberOfElementsPerRow + this.renderedIcons.length) % this.renderedIcons.length); + let previousRowIndex = this.focusedItemIndex - this.numberOfElementsPerRow; + if (previousRowIndex < 0) { + const numberOfRows = Math.floor(this.renderedIcons.length / this.numberOfElementsPerRow); + previousRowIndex = this.focusedItemIndex + (this.numberOfElementsPerRow * numberOfRows) - 1; + previousRowIndex = previousRowIndex >= this.renderedIcons.length ? previousRowIndex - this.numberOfElementsPerRow : previousRowIndex; + } + this.focusIcon(previousRowIndex); } getFocusedIcon(): ThemeIcon { From 417a129502723cb4525a203c44e3839e9e241230 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 19 Sep 2023 17:06:43 -0700 Subject: [PATCH 117/133] Basic markdown decorations for variables in requests (#193533) --- .../contrib/chat/browser/chatListRenderer.ts | 18 +++++++--- .../browser/chatVariableReferenceRenderer.ts | 36 +++++++++++++++++++ .../contrib/chat/browser/media/chat.css | 8 +++++ .../contrib/chat/common/chatServiceImpl.ts | 19 +++++----- 4 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer.ts diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 4b52a3a647b..9a48e8ea78c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -14,6 +14,7 @@ import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTre import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; +import { distinct } from 'vs/base/common/arrays'; import { IntervalTimer } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; @@ -52,6 +53,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; @@ -61,7 +63,9 @@ import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/a import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; +import { fixVariableReferences, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer'; import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; @@ -70,9 +74,6 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; import { IFilesConfiguration } from 'vs/workbench/contrib/files/common/files'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { distinct } from 'vs/base/common/arrays'; -import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel'; const $ = dom.$; @@ -137,7 +138,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { @@ -642,6 +648,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } + if (isRequestVM(element)) { + walkTreeAndAnnotateResourceLinks(result.element); + } + if (usedSlashCommand) { const slashCommandElement = $('span.interactive-slash-command', { title: usedSlashCommand.detail }, `/${usedSlashCommand.command} `); if (result.element.firstChild?.nodeName.toLowerCase() === 'p') { diff --git a/src/vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer.ts new file mode 100644 index 00000000000..1a7ad12b7b8 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; + +const variableRefUrlPrefix = 'http://vscodeVar_'; + +export function fixVariableReferences(markdown: IMarkdownString): IMarkdownString { + const fixedMarkdownSource = markdown.value.replace(/\]\(values:(.*)/g, `](${variableRefUrlPrefix}_$1`); + return new MarkdownString(fixedMarkdownSource, { isTrusted: markdown.isTrusted, supportThemeIcons: markdown.supportThemeIcons, supportHtml: markdown.supportHtml }); +} + +export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void { + element.querySelectorAll('a').forEach(a => { + const href = a.getAttribute('data-href'); + if (href) { + if (href.startsWith(variableRefUrlPrefix)) { + a.parentElement!.replaceChild( + renderResourceWidget(a.textContent!), + a); + } + } + + walkTreeAndAnnotateResourceLinks(a as HTMLElement); + }); +} + +function renderResourceWidget(name: string): HTMLElement { + const container = dom.$('span.chat-resource-widget'); + const alias = dom.$('span', undefined, name); + container.appendChild(alias); + return container; +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index eb1c33b9fa6..4283d10304e 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -469,3 +469,11 @@ border-radius: 4px; width: auto; } + +.interactive-item-container .chat-resource-widget { + background-color: var(--vscode-chat-slashCommandBackground); + color: var(--vscode-chat-slashCommandForeground); + border-radius: 3px; + white-space: nowrap; + padding: 1px; +} diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 54dc2b94444..8bdc8b21459 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,7 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; @@ -436,7 +436,7 @@ export class ChatService extends Disposable implements IChatService { private async _sendRequestAsync(model: ChatModel, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise { const resolvedAgent = typeof message === 'string' ? this.resolveAgent(message) : undefined; - const request = model.addRequest(message, resolvedAgent); + let request: ChatRequestModel; const resolvedCommand = typeof message === 'string' && message.startsWith('/') ? await this.handleSlashCommand(model.sessionId, message) : message; @@ -492,6 +492,7 @@ export class ChatService extends Disposable implements IChatService { let slashCommandFollowups: IChatFollowup[] | void = []; if (typeof message === 'string' && resolvedAgent) { + request = model.addRequest(message); const history: IChatMessage[] = []; for (const request of model.getRequests()) { if (typeof request.message !== 'string' || !request.response) { @@ -510,6 +511,7 @@ export class ChatService extends Disposable implements IChatService { slashCommandFollowups = agentResult?.followUp; rawResponse = { session: model.session! }; } else if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) { + request = model.addRequest(message); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; @@ -531,19 +533,20 @@ export class ChatService extends Disposable implements IChatService { rawResponse = { session: model.session! }; } else { - const request: IChatRequest = { + const requestProps: IChatRequest = { session: model.session!, message: resolvedCommand, variables: {} }; - if (typeof request.message === 'string') { - const varResult = await this.chatVariablesService.resolveVariables(request.message, model, token); - request.variables = varResult.variables; - request.message = varResult.prompt; + if (typeof requestProps.message === 'string') { + const varResult = await this.chatVariablesService.resolveVariables(requestProps.message, model, token); + requestProps.variables = varResult.variables; + requestProps.message = varResult.prompt; } + request = model.addRequest(requestProps.message); - rawResponse = await provider.provideReply(request, progressCallback, token); + rawResponse = await provider.provideReply(requestProps, progressCallback, token); } if (token.isCancellationRequested) { From 70139e827a07c4c1d075544b8c19e087df058d5d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Sep 2023 17:25:11 -0700 Subject: [PATCH 118/133] Pick up latest TS nightly for building VS Code (#193534) Required fixing a real typing error --- build/darwin/create-universal-app.js | 6 ++-- build/lib/layersChecker.js | 16 ++++----- build/linux/debian/dep-lists.js | 8 ++--- build/linux/rpm/dep-lists.js | 4 +-- package.json | 2 +- .../common/standalone/standaloneEnums.ts | 36 +++++++++---------- src/vs/monaco.d.ts | 36 +++++++++---------- .../workbench/api/common/extHost.protocol.ts | 18 +++++----- yarn.lock | 8 ++--- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index a01c222c35e..08380169335 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -32,8 +32,8 @@ async function main(buildDir) { 'Credits.rtf', 'CodeResources', 'fsevents.node', - 'Info.plist', - 'MainMenu.nib', + 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds + 'MainMenu.nib', // Generated sequence is not deterministic with Xcode 13 '.npmrc' ], outAppPath, @@ -57,4 +57,4 @@ if (require.main === module) { process.exit(1); }); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXVuaXZlcnNhbC1hcHAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGUtdW5pdmVyc2FsLWFwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsdUVBQTREO0FBQzVELHFFQUFvRDtBQUVwRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxLQUFLLFVBQVUsSUFBSSxDQUFDLFFBQWlCO0lBQ3BDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7SUFFeEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztLQUMxQztJQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQ3JGLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDO0lBQzFDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLHFCQUFxQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLG1CQUFtQixDQUFDLENBQUM7SUFDL0YsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUNuRyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxpQkFBaUIsSUFBSSxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDekUsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFFakcsTUFBTSxJQUFBLDJDQUFnQixFQUFDO1FBQ3RCLFVBQVU7UUFDVixZQUFZO1FBQ1osV0FBVztRQUNYLGFBQWE7UUFDYixXQUFXLEVBQUU7WUFDWixjQUFjO1lBQ2QsYUFBYTtZQUNiLGVBQWU7WUFDZixlQUFlO1lBQ2YsWUFBWTtZQUNaLGNBQWM7WUFDZCxRQUFRO1NBQ1I7UUFDRCxVQUFVO1FBQ1YsS0FBSyxFQUFFLElBQUk7S0FDWCxDQUFDLENBQUM7SUFFSCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsZUFBZSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDekUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUU7UUFDMUIsc0JBQXNCLEVBQUUsa0JBQWtCO0tBQzFDLENBQUMsQ0FBQztJQUNILEVBQUUsQ0FBQyxhQUFhLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsV0FBVyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRTNFLGtEQUFrRDtJQUNsRCxNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUEsMkJBQUssRUFBQyxNQUFNLEVBQUUsQ0FBQyxVQUFVLEVBQUUsT0FBTyxFQUFFLGVBQWUsQ0FBQyxDQUFDLENBQUM7SUFDL0UsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFBLDJCQUFLLEVBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNsRixJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxLQUFLLGNBQWMsRUFBRTtRQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixVQUFVLEVBQUUsQ0FBQyxDQUFDO0tBQ3JEO0FBQ0YsQ0FBQztBQUVELElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUU7UUFDakMsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pCLENBQUMsQ0FBQyxDQUFDO0NBQ0gifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXVuaXZlcnNhbC1hcHAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGUtdW5pdmVyc2FsLWFwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsdUVBQTREO0FBQzVELHFFQUFvRDtBQUVwRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxLQUFLLFVBQVUsSUFBSSxDQUFDLFFBQWlCO0lBQ3BDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7SUFFeEMsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztLQUMxQztJQUVELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQ3JGLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDO0lBQzFDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLG1CQUFtQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLHFCQUFxQixFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3pFLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLG1CQUFtQixDQUFDLENBQUM7SUFDL0YsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUNuRyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxpQkFBaUIsSUFBSSxFQUFFLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDekUsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFFakcsTUFBTSxJQUFBLDJDQUFnQixFQUFDO1FBQ3RCLFVBQVU7UUFDVixZQUFZO1FBQ1osV0FBVztRQUNYLGFBQWE7UUFDYixXQUFXLEVBQUU7WUFDWixjQUFjO1lBQ2QsYUFBYTtZQUNiLGVBQWU7WUFDZixlQUFlO1lBQ2YsWUFBWSxFQUFFLHlEQUF5RDtZQUN2RSxjQUFjLEVBQUUsd0RBQXdEO1lBQ3hFLFFBQVE7U0FDUjtRQUNELFVBQVU7UUFDVixLQUFLLEVBQUUsSUFBSTtLQUNYLENBQUMsQ0FBQztJQUVILE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUN6RSxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUMxQixzQkFBc0IsRUFBRSxrQkFBa0I7S0FDMUMsQ0FBQyxDQUFDO0lBQ0gsRUFBRSxDQUFDLGFBQWEsQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxXQUFXLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFM0Usa0RBQWtEO0lBQ2xELE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBQSwyQkFBSyxFQUFDLE1BQU0sRUFBRSxDQUFDLFVBQVUsRUFBRSxPQUFPLEVBQUUsZUFBZSxDQUFDLENBQUMsQ0FBQztJQUMvRSxNQUFNLFVBQVUsR0FBRyxNQUFNLElBQUEsMkJBQUssRUFBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2xGLElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssY0FBYyxFQUFFO1FBQ3JELE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLFVBQVUsRUFBRSxDQUFDLENBQUM7S0FDckQ7QUFDRixDQUFDO0FBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLE1BQU0sRUFBRTtJQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNqQyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDakIsQ0FBQyxDQUFDLENBQUM7Q0FDSCJ9 \ No newline at end of file diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 618c736b30a..9cae94895e9 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -23,7 +23,7 @@ const minimatch_1 = require("minimatch"); // Types we assume are present in all implementations of JS VMs (node.js, browsers) // Feel free to add more core types as you see needed if present in node.js and browsers const CORE_TYPES = [ - 'require', + 'require', // from our AMD loader 'setTimeout', 'clearTimeout', 'setInterval', @@ -91,7 +91,7 @@ const RULES = [ ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -101,7 +101,7 @@ const RULES = [ allowedTypes: CORE_TYPES, disallowedTypes: [ /* Ignore native types that are defined from here */], disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -111,7 +111,7 @@ const RULES = [ allowedTypes: CORE_TYPES, disallowedTypes: [ /* Ignore native types that are defined from here */], disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -121,7 +121,7 @@ const RULES = [ allowedTypes: CORE_TYPES, disallowedTypes: [ /* Ignore native types that are defined from here */], disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -135,7 +135,7 @@ const RULES = [ ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -145,7 +145,7 @@ const RULES = [ allowedTypes: CORE_TYPES, disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ - 'lib.dom.d.ts', + 'lib.dom.d.ts', // no DOM '@types/node' // no node.js ] }, @@ -288,4 +288,4 @@ for (const sourceFile of program.getSourceFiles()) { if (hasErrors) { process.exit(1); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF5ZXJzQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxheWVyc0NoZWNrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBaUM7QUFDakMsMkJBQThDO0FBQzlDLCtCQUE4QztBQUM5Qyx5Q0FBa0M7QUFFbEMsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBQ0YsK0ZBQStGO0FBQy9GLG1EQUFtRDtBQUNuRCw0RUFBNEU7QUFDNUUsaUVBQWlFO0FBQ2pFLEVBQUU7QUFDRixnR0FBZ0c7QUFDaEcsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBRUYsbUZBQW1GO0FBQ25GLHdGQUF3RjtBQUN4RixNQUFNLFVBQVUsR0FBRztJQUNsQixTQUFTO0lBQ1QsWUFBWTtJQUNaLGNBQWM7SUFDZCxhQUFhO0lBQ2IsZUFBZTtJQUNmLFNBQVM7SUFDVCxTQUFTO0lBQ1QsT0FBTztJQUNQLGtCQUFrQjtJQUNsQixRQUFRO0lBQ1IsYUFBYTtJQUNiLGFBQWE7SUFDYixNQUFNO0lBQ04sZ0JBQWdCO0lBQ2hCLE9BQU87SUFDUCxZQUFZO0lBQ1osYUFBYTtJQUNiLGFBQWE7SUFDYixXQUFXO0lBQ1gsWUFBWTtJQUNaLFlBQVk7SUFDWixjQUFjO0lBQ2QsY0FBYztJQUNkLG1CQUFtQjtJQUNuQixnQkFBZ0I7SUFDaEIsZUFBZTtJQUNmLE1BQU07SUFDTixNQUFNO0lBQ04saUJBQWlCO0lBQ2pCLGFBQWE7SUFDYixnQkFBZ0I7SUFDaEIsYUFBYTtJQUNiLEtBQUs7SUFDTCxpQkFBaUI7SUFDakIsZUFBZTtJQUNmLE9BQU87SUFDUCxhQUFhO0lBQ2Isa0JBQWtCO0lBQ2xCLGFBQWE7SUFDYixNQUFNO0NBQ04sQ0FBQztBQUVGLG9FQUFvRTtBQUNwRSxvRUFBb0U7QUFDcEUsTUFBTSxZQUFZLEdBQUc7SUFDcEIsa0JBQWtCO0lBQ2xCLDJCQUEyQjtJQUMzQixrQ0FBa0M7SUFDbEMsNEJBQTRCO0lB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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF5ZXJzQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxheWVyc0NoZWNrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBaUM7QUFDakMsMkJBQThDO0FBQzlDLCtCQUE4QztBQUM5Qyx5Q0FBa0M7QUFFbEMsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBQ0YsK0ZBQStGO0FBQy9GLG1EQUFtRDtBQUNuRCw0RUFBNEU7QUFDNUUsaUVBQWlFO0FBQ2pFLEVBQUU7QUFDRixnR0FBZ0c7QUFDaEcsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBRUYsbUZBQW1GO0FBQ25GLHdGQUF3RjtBQUN4RixNQUFNLFVBQVUsR0FBRztJQUNsQixTQUFTLEVBQUUsc0JBQXNCO0lBQ2pDLFlBQVk7SUFDWixjQUFjO0lBQ2QsYUFBYTtJQUNiLGVBQWU7SUFDZixTQUFTO0lBQ1QsU0FBUztJQUNULE9BQU87SUFDUCxrQkFBa0I7SUFDbEIsUUFBUTtJQUNSLGFBQWE7SUFDYixhQUFhO0lBQ2IsTUFBTTtJQUNOLGdCQUFnQjtJQUNoQixPQUFPO0lBQ1AsWUFBWTtJQUNaLGFBQWE7SUFDYixhQUFhO0lBQ2IsV0FBVztJQUNYLFlBQVk7SUFDWixZQUFZO0lBQ1osY0FBYztJQUNkLGNBQWM7SUFDZCxtQkFBbUI7SUFDbkIsZ0JBQWdCO0lBQ2hCLGVBQWU7SUFDZixNQUFNO0lBQ04sTUFBTTtJQUNOLGlCQUFpQjtJQUNqQixhQUFhO0lBQ2IsZ0JBQWdCO0lBQ2hCLGFBQWE7SUFDYixLQUFLO0lBQ0wsaUJBQWlCO0lBQ2pCLGVBQWU7SUFDZixPQUFPO0lBQ1AsYUFBYTtJQUNiLGtCQUFrQjtJQUNsQixhQUFhO0lBQ2IsTUFBTTtDQUNOLENBQUM7QUFFRixvRUFBb0U7QUFDcEUsb0VBQW9FO0FBQ3BFLE1BQU0sWUFBWSxHQUFHO0lBQ3BCLGtCQUFrQjtJQUNsQiwyQkFBMkI7SUFDM0Isa0NBQWtDO0lBQ2xDLDRCQUE0QjtJQUM1QiwwQkFBMEI7SUFDMUIsb0JBQW9CO0lBQ3BCLHFCQUFxQjtDQUNyQixDQUFDO0FBRUYsTUFBTSxLQUFLLEdBQVk7SUFFdEIsY0FBYztJQUNkO1FBQ0MsTUFBTSxFQUFFLGtCQUFrQjtRQUMxQixJQUFJLEVBQUUsSUFBSSxDQUFDLHlCQUF5QjtLQUNwQztJQUVELHFDQUFxQztJQUNyQztRQUNDLE1BQU0sRUFBRSwrQkFBK0I7UUFDdkMsWUFBWSxFQUFFO1lBQ2IsR0FBRyxVQUFVO1lBRWIsMkNBQTJDO1lBQzNDLGNBQWM7U0FDZDtRQUNELGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCwyQ0FBMkM7SUFDM0M7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw4Q0FBOEM7SUFDOUM7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw4Q0FBOEM7SUFDOUM7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw2REFBNkQ7SUFDN0Q7UUFDQyxNQUFNLEVBQUUsdURBQXVEO1FBQy9ELFlBQVksRUFBRTtZQUNiLEdBQUcsVUFBVTtZQUViLHdCQUF3QjtZQUN4QixRQUFRO1NBQ1I7UUFDRCxlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLEVBQUUsU0FBUztZQUN6QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsU0FBUztJQUNUO1FBQ0MsTUFBTSxFQUFFLG9CQUFvQjtRQUM1QixZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLEVBQUUsU0FBUztZQUN6QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsVUFBVTtJQUNWO1FBQ0MsTUFBTSxFQUFFLHFCQUFxQjtRQUM3QixZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixrQkFBa0IsRUFBRTtZQUNuQixtQ0FBbUMsQ0FBQyxzRkFBc0Y7U0FDMUg7UUFDRCxxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsMkJBQTJCO0lBQzNCO1FBQ0MsTUFBTSxFQUFFLDZCQUE2QjtRQUNyQyxZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsVUFBVTtJQUNWO1FBQ0MsTUFBTSxFQUFFLGtCQUFrQjtRQUMxQixZQUFZLEVBQUUsVUFBVTtRQUN4QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLENBQUMsU0FBUztTQUN4QjtLQUNEO0lBRUQscUJBQXFCO0lBQ3JCO1FBQ0MsTUFBTSxFQUFFLDhCQUE4QjtRQUN0QyxZQUFZLEVBQUUsVUFBVTtRQUN4QixxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsa0JBQWtCO0lBQ2xCO1FBQ0MsTUFBTSxFQUFFLDJCQUEyQjtRQUNuQyxZQUFZLEVBQUU7WUFDYixHQUFHLFVBQVU7WUFFYixnRUFBZ0U7WUFDaEUsT0FBTztZQUNQLFNBQVM7U0FDVDtRQUNELGVBQWUsRUFBRTtZQUNoQixTQUFTLENBQUMsNENBQTRDO1NBQ3REO1FBQ0QscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxDQUFDLFNBQVM7U0FDeEI7S0FDRDtDQUNELENBQUM7QUFFRixNQUFNLGNBQWMsR0FBRyxJQUFBLFdBQUksRUFBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxlQUFlLENBQUMsQ0FBQztBQVd6RSxJQUFJLFNBQVMsR0FBRyxLQUFLLENBQUM7QUFFdEIsU0FBUyxTQUFTLENBQUMsT0FBbUIsRUFBRSxVQUF5QixFQUFFLElBQVc7SUFDN0UsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBRXRCLFNBQVMsU0FBUyxDQUFDLElBQWE7UUFDL0IsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsVUFBVSxFQUFFO1lBQzNDLE9BQU8sRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxlQUFlO1NBQ3hEO1FBRUQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVqRCxJQUFJLENBQUMsTUFBTSxFQUFFO1lBQ1osT0FBTztTQUNQO1FBRUQsSUFBSSxhQUFhLEdBQVEsTUFBTSxDQUFDO1FBRWhDLE9BQU8sYUFBYSxDQUFDLE1BQU0sRUFBRTtZQUM1QixhQUFhLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQztTQUNyQztRQUVELE1BQU0sWUFBWSxHQUFHLGFBQTBCLENBQUM7UUFDaEQsTUFBTSxJQUFJLEdBQUcsWUFBWSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBRXBDLElBQUksSUFBSSxDQUFDLFlBQVksRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEtBQUssSUFBSSxDQUFDLEVBQUU7WUFDekQsT0FBTyxDQUFDLFdBQVc7U0FDbkI7UUFFRCxJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQyxFQUFFO1lBQ2xFLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLEdBQUcsVUFBVSxDQUFDLDZCQUE2QixDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1lBQ3RGLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0RBQW9ELElBQUkscUJBQXFCLElBQUksQ0FBQyxNQUFNLE1BQU0sVUFBVSxDQUFDLFFBQVEsS0FBSyxJQUFJLEdBQUcsQ0FBQyxJQUFJLFNBQVMsR0FBRyxDQUFDLHdIQUF3SCxDQUFDLENBQUM7WUFFclIsU0FBUyxHQUFHLElBQUksQ0FBQztZQUNqQixPQUFPO1NBQ1A7UUFFRCxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDO1FBQ3pDLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsRUFBRTtZQUNoQyxlQUFlLEVBQUUsS0FBSyxNQUFNLFdBQVcsSUFBSSxZQUFZLEVBQUU7Z0JBQ3hELElBQUksV0FBVyxFQUFFO29CQUNoQixNQUFNLE1BQU0sR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDO29CQUNsQyxJQUFJLE1BQU0sRUFBRTt3QkFDWCxNQUFNLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxhQUFhLEVBQUUsQ0FBQzt3QkFDaEQsSUFBSSxnQkFBZ0IsRUFBRTs0QkFDckIsTUFBTSxrQkFBa0IsR0FBRyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUM7NEJBQ3JELElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFO2dDQUM1QixLQUFLLE1BQU0saUJBQWlCLElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFO29DQUN4RCxJQUFJLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFBRTt3Q0FDdkQsU0FBUyxlQUFlLENBQUM7cUNBQ3pCO2lDQUNEOzZCQUNEOzRCQUNELElBQUksSUFBSSxDQUFDLHFCQUFxQixFQUFFO2dDQUMvQixLQUFLLE1BQU0sb0JBQW9CLElBQUksSUFBSSxDQUFDLHFCQUFxQixFQUFFO29DQUM5RCxJQUFJLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsRUFBRTt3Q0FDMUQsTUFBTSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsR0FBRyxVQUFVLENBQUMsNkJBQTZCLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7d0NBRXRGLE9BQU8sQ0FBQyxHQUFHLENBQUMsc0RBQXNELElBQUksV0FBVyxvQkFBb0IscUJBQXFCLElBQUksQ0FBQyxNQUFNLE1BQU0sVUFBVSxDQUFDLFFBQVEsS0FBSyxJQUFJLEdBQUcsQ0FBQyxJQUFJLFNBQVMsR0FBRyxDQUFDLHVIQUF1SCxDQUFDLENBQUM7d0NBRXJULFNBQVMsR0FBRyxJQUFJLENBQUM7d0NBQ2pCLE9BQU87cUNBQ1A7aUNBQ0Q7NkJBQ0Q7eUJBQ0Q7cUJBQ0Q7aUJBQ0Q7YUFDRDtTQUNEO0lBQ0YsQ0FBQztBQUNGLENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxZQUFvQjtJQUMxQyxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRWxFLE1BQU0sZ0JBQWdCLEdBQXVCLEVBQUUsVUFBVSxFQUFFLGVBQVUsRUFBRSxhQUFhLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBQSxpQkFBWSxFQUFDLElBQUksRUFBRSxNQUFNLENBQUMsRUFBRSx5QkFBeUIsRUFBRSxPQUFPLENBQUMsUUFBUSxLQUFLLE9BQU8sRUFBRSxDQUFDO0lBQ3BOLE1BQU0sY0FBYyxHQUFHLEVBQUUsQ0FBQywwQkFBMEIsQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLGdCQUFnQixFQUFFLElBQUEsY0FBTyxFQUFDLElBQUEsY0FBTyxFQUFDLFlBQVksQ0FBQyxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUUxSSxNQUFNLFlBQVksR0FBRyxFQUFFLENBQUMsa0JBQWtCLENBQUMsY0FBYyxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV6RSxPQUFPLEVBQUUsQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUFDLFNBQVMsRUFBRSxjQUFjLENBQUMsT0FBTyxFQUFFLFlBQVksQ0FBQyxDQUFDO0FBQ3pGLENBQUM7QUFFRCxFQUFFO0FBQ0Ysb0NBQW9DO0FBQ3BDLEVBQUU7QUFDRixNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsY0FBYyxDQUFDLENBQUM7QUFFOUMsS0FBSyxNQUFNLFVBQVUsSUFBSSxPQUFPLENBQUMsY0FBYyxFQUFFLEVBQUU7SUFDbEQsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLEVBQUU7UUFDekIsSUFBSSxJQUFBLGlCQUFLLEVBQUMsQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUU7WUFDekQsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUU7Z0JBQ2YsU0FBUyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDckM7WUFFRCxNQUFNO1NBQ047S0FDRDtDQUNEO0FBRUQsSUFBSSxTQUFTLEVBQUU7SUFDZCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2hCIn0= \ No newline at end of file diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index e60b340b45e..16e27eedf01 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -8,11 +8,11 @@ exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additio // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps // Additional dependencies not in the dpkg-shlibdeps output. exports.additionalDeps = [ - 'ca-certificates', + 'ca-certificates', // Make sure users have SSL certificates. 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', 'libnss3 (>= 3.26)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'xdg-utils (>= 1.0.2)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. + 'xdg-utils (>= 1.0.2)', // OS integration 'libgssapi-krb5-2', 'libkrb5-3', ]; @@ -140,4 +140,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils (>= 1.0.2)' ] }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNEO0lBQ3RELHNCQUFzQjtJQUN0QixrQkFBa0I7SUFDbEIsV0FBVztDQUNYLENBQUM7QUFFRixvSEFBb0g7QUFDcEgsMENBQTBDO0FBQzFDLDhEQUE4RDtBQUNqRCxRQUFBLGVBQWUsR0FBRztJQUM5QixZQUFZLENBQUMseUVBQXlFO0NBQ3RGLENBQUM7QUFFVyxRQUFBLDRCQUE0QixHQUFHO0lBQzNDLE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsaUJBQWlCO1FBQ2pCLGdCQUFnQjtRQUNoQixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsa0JBQWtCO1FBQ2xCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsV0FBVztRQUNYLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1QixtQkFBbUI7UUFDbkIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQixVQUFVO1FBQ1YsMEJBQTBCO1FBQzFCLG9CQUFvQjtRQUNwQiwrQkFBK0I7UUFDL0Isd0JBQXdCO1FBQ3hCLFVBQVU7UUFDVixZQUFZO1FBQ1osMEJBQTBCO1FBQzFCLGFBQWE7UUFDYixZQUFZO1FBQ1osc0JBQXNCO0tBQ3RCO0NBQ0QsQ0FBQyJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCLEVBQUUseUNBQXlDO0lBQzVELHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNELEVBQUUsOEJBQThCO0lBQ3RGLHNCQUFzQixFQUFFLGlCQUFpQjtJQUN6QyxrQkFBa0I7SUFDbEIsV0FBVztDQUNYLENBQUM7QUFFRixvSEFBb0g7QUFDcEgsMENBQTBDO0FBQzFDLDhEQUE4RDtBQUNqRCxRQUFBLGVBQWUsR0FBRztJQUM5QixZQUFZLENBQUMseUVBQXlFO0NBQ3RGLENBQUM7QUFFVyxRQUFBLDRCQUE0QixHQUFHO0lBQzNDLE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsaUJBQWlCO1FBQ2pCLGdCQUFnQjtRQUNoQixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQixrQkFBa0I7UUFDbEIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyxXQUFXO1FBQ1gsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsa0JBQWtCO1FBQ2xCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsV0FBVztRQUNYLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1QixtQkFBbUI7UUFDbkIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQixVQUFVO1FBQ1YsMEJBQTBCO1FBQzFCLG9CQUFvQjtRQUNwQiwrQkFBK0I7UUFDL0Isd0JBQXdCO1FBQ3hCLFVBQVU7UUFDVixZQUFZO1FBQ1osMEJBQTBCO1FBQzFCLGFBQWE7UUFDYixZQUFZO1FBQ1osc0JBQXNCO0tBQ3RCO0NBQ0QsQ0FBQyJ9 \ No newline at end of file diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index 667d207ca9d..c97574a083b 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -8,7 +8,7 @@ exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps // Additional dependencies not in the rpm find-requires output. exports.additionalDeps = [ - 'ca-certificates', + 'ca-certificates', // Make sure users have SSL certificates. 'libgtk-3.so.0()(64bit)', 'libnss3.so(NSS_3.22)(64bit)', 'libssl3.so(NSS_3.28)(64bit)', @@ -302,4 +302,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils' ] }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLCtHQUErRztBQUMvRywrREFBK0Q7QUFDbEQsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4Qiw2QkFBNkI7SUFDN0IsNkJBQTZCO0lBQzdCLGdDQUFnQztJQUNoQyx5QkFBeUI7SUFDekIsdUJBQXVCO0lBQ3ZCLFdBQVcsQ0FBQyxpQkFBaUI7Q0FDN0IsQ0FBQztBQUVXLFFBQUEsNEJBQTRCLEdBQUc7SUFDM0MsUUFBUSxFQUFFO1FBQ1QsaUJBQWlCO1FBQ2pCLCtCQUErQjtRQUMvQiwwQ0FBMEM7UUFDMUMsd0NBQXdDO1FBQ3hDLHNCQUFzQjtRQUN0Qiw2QkFBNkI7UUFDN0IsMEJBQTBCO1FBQzFCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIseUJBQXlCO1FBQ3pCLHlCQUF5QjtRQUN6QixpQ0FBaUM7UUFDakMsc0NBQXNDO1FBQ3RDLDBCQUEwQjtRQUMxQixpQ0FBaUM7UUFDakMsd0JBQXdCO1FBQ3hCLG9CQUFvQjtRQUNwQiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5Qiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5QiwrQkFBK0I7UUFDL0IsNkJBQTZCO1FBQzdCLCtCQUErQjtRQUMvQiwrQkFBK0I7UUFDL0IsK0JBQStCO1FBQy9CLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0IsNkJBQTZCO1FBQzdCLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0Isd0JBQXdCO1FBQ3hCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIscUJBQXFCO1FBQ3JCLGdDQUFnQztRQUNoQyxzQkFBc0I7UUFDdEIsd0JBQXdCO1FBQ3hCLHNCQUFzQjtRQUN0Qix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLDBCQUEwQjtR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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLCtHQUErRztBQUMvRywrREFBK0Q7QUFDbEQsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCLEVBQUUseUNBQXlDO0lBQzVELHdCQUF3QjtJQUN4Qiw2QkFBNkI7SUFDN0IsNkJBQTZCO0lBQzdCLGdDQUFnQztJQUNoQyx5QkFBeUI7SUFDekIsdUJBQXVCO0lBQ3ZCLFdBQVcsQ0FBQyxpQkFBaUI7Q0FDN0IsQ0FBQztBQUVXLFFBQUEsNEJBQTRCLEdBQUc7SUFDM0MsUUFBUSxFQUFFO1FBQ1QsaUJBQWlCO1FBQ2pCLCtCQUErQjtRQUMvQiwwQ0FBMEM7UUFDMUMsd0NBQXdDO1FBQ3hDLHNCQUFzQjtRQUN0Qiw2QkFBNkI7UUFDN0IsMEJBQTBCO1FBQzFCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIseUJBQXlCO1FBQ3pCLHlCQUF5QjtRQUN6QixpQ0FBaUM7UUFDakMsc0NBQXNDO1FBQ3RDLDBCQUEwQjtRQUMxQixpQ0FBaUM7UUFDakMsd0JBQXdCO1FBQ3hCLG9CQUFvQjtRQUNwQiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5Qiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5QiwrQkFBK0I7UUFDL0IsNkJBQTZCO1FBQzdCLCtCQUErQjtRQUMvQiwrQkFBK0I7UUFDL0IsK0JBQStCO1FBQy9CLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0IsNkJBQTZCO1FBQzdCLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0Isd0JBQXdCO1FBQ3hCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIscUJBQXFCO1FBQ3JCLGdDQUFnQztRQUNoQyxzQkFBc0I7UUFDdEIsd0JBQXdCO1FBQ3hCLHNCQUFzQjtRQUN0Qix3QkFBd0I7UUFDeEIsK0JBQStCO1FB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 \ No newline at end of file diff --git a/package.json b/package.json index cc298739be3..4d055ed5e9c 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.3.0-dev.20230911", + "typescript": "^5.3.0-dev.20230919", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index ec6cf691492..44f9fb9b434 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -572,28 +572,28 @@ export enum KeyCode { * Either the angle bracket key or the backslash key on the RT 102-key keyboard. */ IntlBackslash = 97, - Numpad0 = 98, - Numpad1 = 99, - Numpad2 = 100, - Numpad3 = 101, - Numpad4 = 102, - Numpad5 = 103, - Numpad6 = 104, - Numpad7 = 105, - Numpad8 = 106, - Numpad9 = 107, - NumpadMultiply = 108, - NumpadAdd = 109, - NUMPAD_SEPARATOR = 110, - NumpadSubtract = 111, - NumpadDecimal = 112, - NumpadDivide = 113, + Numpad0 = 98,// VK_NUMPAD0, 0x60, Numeric keypad 0 key + Numpad1 = 99,// VK_NUMPAD1, 0x61, Numeric keypad 1 key + Numpad2 = 100,// VK_NUMPAD2, 0x62, Numeric keypad 2 key + Numpad3 = 101,// VK_NUMPAD3, 0x63, Numeric keypad 3 key + Numpad4 = 102,// VK_NUMPAD4, 0x64, Numeric keypad 4 key + Numpad5 = 103,// VK_NUMPAD5, 0x65, Numeric keypad 5 key + Numpad6 = 104,// VK_NUMPAD6, 0x66, Numeric keypad 6 key + Numpad7 = 105,// VK_NUMPAD7, 0x67, Numeric keypad 7 key + Numpad8 = 106,// VK_NUMPAD8, 0x68, Numeric keypad 8 key + Numpad9 = 107,// VK_NUMPAD9, 0x69, Numeric keypad 9 key + NumpadMultiply = 108,// VK_MULTIPLY, 0x6A, Multiply key + NumpadAdd = 109,// VK_ADD, 0x6B, Add key + NUMPAD_SEPARATOR = 110,// VK_SEPARATOR, 0x6C, Separator key + NumpadSubtract = 111,// VK_SUBTRACT, 0x6D, Subtract key + NumpadDecimal = 112,// VK_DECIMAL, 0x6E, Decimal key + NumpadDivide = 113,// VK_DIVIDE, 0x6F, /** * Cover all key codes when IME is processing input. */ KEY_IN_COMPOSITION = 114, - ABNT_C1 = 115, - ABNT_C2 = 116, + ABNT_C1 = 115,// Brazilian (ABNT) Keyboard + ABNT_C2 = 116,// Brazilian (ABNT) Keyboard AudioVolumeMute = 117, AudioVolumeUp = 118, AudioVolumeDown = 119, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2a6218c8de5..ab0e838f808 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -419,28 +419,28 @@ declare namespace monaco { * Either the angle bracket key or the backslash key on the RT 102-key keyboard. */ IntlBackslash = 97, - Numpad0 = 98, - Numpad1 = 99, - Numpad2 = 100, - Numpad3 = 101, - Numpad4 = 102, - Numpad5 = 103, - Numpad6 = 104, - Numpad7 = 105, - Numpad8 = 106, - Numpad9 = 107, - NumpadMultiply = 108, - NumpadAdd = 109, - NUMPAD_SEPARATOR = 110, - NumpadSubtract = 111, - NumpadDecimal = 112, - NumpadDivide = 113, + Numpad0 = 98,// VK_NUMPAD0, 0x60, Numeric keypad 0 key + Numpad1 = 99,// VK_NUMPAD1, 0x61, Numeric keypad 1 key + Numpad2 = 100,// VK_NUMPAD2, 0x62, Numeric keypad 2 key + Numpad3 = 101,// VK_NUMPAD3, 0x63, Numeric keypad 3 key + Numpad4 = 102,// VK_NUMPAD4, 0x64, Numeric keypad 4 key + Numpad5 = 103,// VK_NUMPAD5, 0x65, Numeric keypad 5 key + Numpad6 = 104,// VK_NUMPAD6, 0x66, Numeric keypad 6 key + Numpad7 = 105,// VK_NUMPAD7, 0x67, Numeric keypad 7 key + Numpad8 = 106,// VK_NUMPAD8, 0x68, Numeric keypad 8 key + Numpad9 = 107,// VK_NUMPAD9, 0x69, Numeric keypad 9 key + NumpadMultiply = 108,// VK_MULTIPLY, 0x6A, Multiply key + NumpadAdd = 109,// VK_ADD, 0x6B, Add key + NUMPAD_SEPARATOR = 110,// VK_SEPARATOR, 0x6C, Separator key + NumpadSubtract = 111,// VK_SUBTRACT, 0x6D, Subtract key + NumpadDecimal = 112,// VK_DECIMAL, 0x6E, Decimal key + NumpadDivide = 113,// VK_DIVIDE, 0x6F, /** * Cover all key codes when IME is processing input. */ KEY_IN_COMPOSITION = 114, - ABNT_C1 = 115, - ABNT_C2 = 116, + ABNT_C1 = 115,// Brazilian (ABNT) Keyboard + ABNT_C2 = 116,// Brazilian (ABNT) Keyboard AudioVolumeMute = 117, AudioVolumeUp = 118, AudioVolumeDown = 119, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2299e4b4a2c..ea5ccab44fc 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -26,7 +26,6 @@ import * as languages from 'vs/editor/common/languages'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/languages/languageConfiguration'; import { EndOfLineSequence } from 'vs/editor/common/model'; import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; -import { IInlineChatRequest, IInlineChatResponse, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -50,8 +49,13 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { SaveReason } from 'vs/workbench/common/editor'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; -import { IChatResponseErrorDetails, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, 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'; @@ -61,7 +65,8 @@ import { IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; -import { IAuthenticationCreateSessionOptions, AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/workbench/services/authentication/common/authentication'; +import { RelatedInformationResult, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; +import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationCreateSessionOptions } from 'vs/workbench/services/authentication/common/authentication'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IExtensionDescriptionDelta, IStaticWorkspaceData } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; @@ -73,11 +78,6 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { 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'; -import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; -import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; -import { RelatedInformationResult, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; -import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; @@ -1180,7 +1180,7 @@ export interface MainThreadInlineChatShape extends IDisposable { $unregisterInteractiveEditorProvider(handle: number): Promise; } -export type IInlineChatResponseDto = Dto; +export type IInlineChatResponseDto = Dto & { edits: IWorkspaceEditDto } | IInlineChatMessageResponse>; export interface ExtHostInlineChatShape { $prepareSession(handle: number, uri: UriComponents, range: ISelection, token: CancellationToken): Promise; diff --git a/yarn.lock b/yarn.lock index a7b290ff3cd..1cbe44fc1c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10008,10 +10008,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.3.0-dev.20230911: - version "5.3.0-dev.20230911" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.0-dev.20230911.tgz#7f60e82ee86e381655ddc63141408eb90b6ca31d" - integrity sha512-2iI2l7OuGvU668gBje+JQKE8bsf7SH8w8ScwUkENHCcrbaDpXa/Oqfuwq5gdFM7SfVfp5p6c8kHZRMvL+kabJg== +typescript@^5.3.0-dev.20230919: + version "5.3.0-dev.20230919" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.0-dev.20230919.tgz#ee93ccb19d16a89b562bcd851d31e20e545b2ed0" + integrity sha512-FU6DZhzId38aY/dX2gHp7phaYkbNJkCx8G//VVs0nVzZv0qjWGggLkMXoMipphO8Hv0TvZu30Zwdt6nzFIbcBQ== typical@^4.0.0: version "4.0.0" From 2ad860b576023e50c27b1118c4f037a409a14d79 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 19 Sep 2023 19:59:11 -0700 Subject: [PATCH 119/133] Add stemming support to TF-IDF implementation (#193531) --- src/vs/base/common/tfIdf.ts | 252 ++++++++++++++++++++++++-- src/vs/base/test/common/tfIdf.test.ts | 25 +++ 2 files changed, 260 insertions(+), 17 deletions(-) diff --git a/src/vs/base/common/tfIdf.ts b/src/vs/base/common/tfIdf.ts index 993f41b5a78..38cfb1980d7 100644 --- a/src/vs/base/common/tfIdf.ts +++ b/src/vs/base/common/tfIdf.ts @@ -6,22 +6,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; type SparseEmbedding = Record; -type TermFrequencies = Map; +type TermFrequencies = Map; type DocumentOccurrences = Map; -function countMapFrom(values: Iterable): Map { - const map = new Map(); - for (const value of values) { - map.set(value, (map.get(value) ?? 0) + 1); - } - return map; -} - interface DocumentChunkEntry { readonly text: string; readonly tf: TermFrequencies; } +interface Term { + readonly term: string; + readonly weight: number; +} + export interface TfIdfDocument { readonly key: string; readonly textChunks: readonly string[]; @@ -75,26 +72,53 @@ export class TfIdfCalculator { * Count how many times each term (word) appears in a string. */ private static termFrequencies(input: string): TermFrequencies { - return countMapFrom(TfIdfCalculator.splitTerms(input)); + const map = new Map(); + for (const value of TfIdfCalculator.splitTerms(input)) { + const existing = map.get(value.term); + if (existing) { + existing.occurrences++; + } else { + map.set(value.term, { weight: value.weight, occurrences: 1 }); + } + } + return map; } /** * Break a string into terms (words). + * + * TODO: confirm that when we break up a word or generate stems, we likely accidentally over-weight its terms. + * For instance, if the document is: `cats wear hats` and the user searches `cats wear`, we could end up giving too + * much weight to `cats` since the document would be broken into: `[cats, cat, wear, hats, hat]` while the query + * would be broken into `[cats, cat, wear]`. This means that terms derived from `cats` end up being matched on multiple + * times, which isn't really right. + * + * Maybe we need to generate a tree of terms for the document where we stop searching once a match has been found: */ - private static *splitTerms(input: string): Iterable { + private static *splitTerms(input: string): Iterable { const normalize = (word: string) => word.toLowerCase(); // Only match on words that are at least 3 characters long and start with a letter for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) { - yield normalize(word); + yield { term: normalize(word), weight: 1 }; - // eslint-disable-next-line local/code-no-look-behind-regex - const camelParts = word.split(/(?<=[a-z])(?=[A-Z])/g); + // Include both the original term and the stemmed version + const stemmedTerm = stem(word); + if (stemmedTerm !== word) { + yield { term: normalize(stemmedTerm), weight: 0.75 }; + } + + const camelParts = word.split(/(?=[A-Z])/g); if (camelParts.length > 1) { for (const part of camelParts) { // Require at least 3 letters in the parts of a camel case word if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) { - yield normalize(part); + yield { term: normalize(part), weight: 0.75 }; + + const stemmedPart = stem(part); + if (stemmedPart !== part && stemmedPart.length > 2) { + yield { term: normalize(stemmedPart), weight: 0.5 }; + } } } } @@ -186,7 +210,7 @@ export class TfIdfCalculator { idfCache.set(term, chunkIdf); } - const chunkTfidf = chunkTf * chunkIdf; + const chunkTfidf = chunkTf.weight * chunkTf.occurrences * chunkIdf; sum += chunkTfidf * termTfidf; } return sum; @@ -209,7 +233,7 @@ export class TfIdfCalculator { for (const [word, occurrences] of termFrequencies) { const idf = this.computeIdf(word); if (idf > 0) { - embedding[word] = occurrences * idf; + embedding[word] = occurrences.weight * occurrences.occurrences * idf; } } return embedding; @@ -239,3 +263,197 @@ export function normalizeTfIdfScores(scores: TfIdfScore[]): NormalizedTfIdfScore return result as TfIdfScore[]; } + +// https://github.com/maxxxxxdlp/porter-stemming + +/** + * TypeScript implementation of the Porter-Stemmer algorithm + */ +export function stem(raw: string): string { + if (raw.length < minLength) { return raw; } + + let word = raw; + const firstCharacter = word[0]; + if (firstCharacter === 'y') { word = firstCharacter.toUpperCase() + word.slice(1); } + + word = steps.reduce((word, step) => step(word), word); + + // Turn initial Y back to y + if (firstCharacter === 'y') { word = firstCharacter.toLowerCase() + word.slice(1); } + + return word; +} + +const minLength = 3; +const vowel = '[aeiouy]'; +const consonant = '[^aeiou]'; +const consonantSequence = `${consonant}[^aeiouy]*`; +const o = new RegExp(`^${consonantSequence}${vowel}[^aeiouwxy]$`, 'u'); + +/** + * Try to match a word against a rule + */ +const replace = + ( + replacements: Readonly< + Record< + string, + | string + | readonly [condition: (word: string) => boolean, replacement: string] + > + > + ) => + (word: string): string => { + const entries = Object.entries(replacements).sort( + ([left], [right]) => right.length - left.length + ); + for (const [suffix, replacement] of entries) { + if (!word.endsWith(suffix)) { continue; } + if ( + Array.isArray(replacement) && + !replacement[0](word.slice(0, -suffix.length)) + ) { break; } + return `${word.slice(0, -suffix.length)}${Array.isArray(replacement) ? replacement[1] : replacement + }`; + } + return word; + }; + +const calculateMeasure = (word: string): number => + sum( + Array.from(word.split(''), (_, index) => + !isConsonant(word, index) && + index + 1 < word.length && + isConsonant(word, index + 1) + ? 1 + : 0 + ) + ); + +const sum = (array: readonly number[]): number => + array.reduce((sum, value) => sum + value, 0); + +const measure = + (min: number) => + (word: string): boolean => + calculateMeasure(word) > min; + +function isConsonant(word: string, index: number): boolean { + const vowels = 'aeiou'; + if (vowels.includes(word[index])) { return false; } + if (word[index] === 'y') { return index === 0 ? true : !isConsonant(word, index - 1); } + else { return true; } +} + +const hasVowel = (word: string): boolean => + Array.from(word.split('')).some((_, index) => !isConsonant(word, index)); + +const steps: readonly ((word: string) => string)[] = [ + // Step 1a + replace({ + sses: 'ss', + ies: 'i', + ss: 'ss', + s: '', + }), + // Step 1b + (word): string => { + if (word.endsWith('eed')) { return replace({ eed: [measure(0), 'ee'] })(word); } + const updated = replace({ ed: [hasVowel, ''], ing: [hasVowel, ''] })(word); + if (updated === word) { return word; } + const replaced = replace({ + at: 'ate', + bl: 'ble', + iz: 'ize', + })(updated); + if (replaced !== updated) { return replaced; } + + if ( + replaced.at(-1) === replaced.at(-'dd'.length) && + isConsonant(replaced, replaced.length - 1) && + !['l', 's', 'z'].some((letter) => replaced.endsWith(letter)) + ) { return replaced.slice(0, -1); } + + if (calculateMeasure(replaced) === 1 && o.test(replaced)) { return `${replaced}e`; } + return replaced; + }, + // Step 1c + replace({ + y: [hasVowel, 'i'], + }), + // Step 2 + replace({ + ational: [measure(0), 'ate'], + tional: [measure(0), 'tion'], + enci: [measure(0), 'ence'], + anci: [measure(0), 'ance'], + izer: [measure(0), 'ize'], + abli: [measure(0), 'able'], + alli: [measure(0), 'al'], + entli: [measure(0), 'ent'], + eli: [measure(0), 'e'], + ousli: [measure(0), 'ous'], + ization: [measure(0), 'ize'], + ation: [measure(0), 'ate'], + ator: [measure(0), 'ate'], + alism: [measure(0), 'al'], + iveness: [measure(0), 'ive'], + fulness: [measure(0), 'ful'], + ousness: [measure(0), 'ous'], + aliti: [measure(0), 'al'], + iviti: [measure(0), 'ive'], + biliti: [measure(0), 'ble'], + logi: [measure(0), 'log'], + bli: [measure(0), 'ble'], + }), + // Step 3 + replace({ + icate: [measure(0), 'ic'], + ative: [measure(0), ''], + alize: [measure(0), 'al'], + iciti: [measure(0), 'ic'], + ical: [measure(0), 'ic'], + ful: [measure(0), ''], + ness: [measure(0), ''], + }), + // Step 4 + (word): string => { + const newWord = replace({ + al: [measure(1), ''], + ance: [measure(1), ''], + ence: [measure(1), ''], + er: [measure(1), ''], + ic: [measure(1), ''], + able: [measure(1), ''], + ible: [measure(1), ''], + ant: [measure(1), ''], + ement: [measure(1), ''], + ment: [measure(1), ''], + ent: [measure(1), ''], + ou: [measure(1), ''], + ism: [measure(1), ''], + ate: [measure(1), ''], + iti: [measure(1), ''], + ous: [measure(1), ''], + ive: [measure(1), ''], + ize: [measure(1), ''], + })(word); + if (newWord !== word) { return newWord; } + return (word.endsWith('tion') || word.endsWith('sion')) && + measure(1)(word.slice(0, -'ion'.length)) + ? word.slice(0, -'ion'.length) + : word; + }, + // Step 5a + (word): string => { + if (!word.endsWith('e')) { return word; } + const stem = word.slice(0, -1); + const measure = calculateMeasure(stem); + return measure > 1 || (measure === 1 && !o.test(stem)) ? stem : word; + }, + // Step 5b + (word): string => + word.endsWith('ll') && measure(1)(word.slice(0, -1)) + ? word.slice(0, -1) + : word, +]; diff --git a/src/vs/base/test/common/tfIdf.test.ts b/src/vs/base/test/common/tfIdf.test.ts index 69c094759eb..38ffa884ba3 100644 --- a/src/vs/base/test/common/tfIdf.test.ts +++ b/src/vs/base/test/common/tfIdf.test.ts @@ -163,6 +163,19 @@ suite('TF-IDF Calculator', function () { } }); + test('Should weigh exact match higher than camelCase match', () => { + for (const docs of permutate([ + makeDocument('/A', 'catDog'), + makeDocument('/B', 'cat cat cat fish'), + makeDocument('/C', 'dog dog cat rat'), + makeDocument('/D', 'pig'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('catDog', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/C', '/B']); + } + }); + test('Should not match document after delete', () => { const docA = makeDocument('/A', 'cat dog cat'); const docB = makeDocument('/B', 'cat fish'); @@ -184,6 +197,18 @@ suite('TF-IDF Calculator', function () { scores = tfidf.calculateScores('cat', CancellationToken.None); assertScoreOrdersEqual(scores, []); }); + + test('Should find stemmed words', () => { + for (const docs of permutate([ + makeDocument('/A', 'cats'), + makeDocument('/B', 'dogs cat'), + makeDocument('/D', 'pig'), + ])) { + const tfidf = new TfIdfCalculator().updateDocuments(docs); + const scores = tfidf.calculateScores('cats', CancellationToken.None); + assertScoreOrdersEqual(scores, ['/A', '/B']); + } + }); }); function makeDocument(key: string, content: string | string[]): TfIdfDocument { From 4ea2c19b2afbddd59fde6b4b5957c46802bbc005 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Tue, 19 Sep 2023 22:58:14 -0700 Subject: [PATCH 120/133] Revert "Add stemming support to TF-IDF implementation (#193531)" This reverts commit 2ad860b576023e50c27b1118c4f037a409a14d79. --- src/vs/base/common/tfIdf.ts | 252 ++------------------------ src/vs/base/test/common/tfIdf.test.ts | 25 --- 2 files changed, 17 insertions(+), 260 deletions(-) diff --git a/src/vs/base/common/tfIdf.ts b/src/vs/base/common/tfIdf.ts index 38cfb1980d7..993f41b5a78 100644 --- a/src/vs/base/common/tfIdf.ts +++ b/src/vs/base/common/tfIdf.ts @@ -6,19 +6,22 @@ import { CancellationToken } from 'vs/base/common/cancellation'; type SparseEmbedding = Record; -type TermFrequencies = Map; +type TermFrequencies = Map; type DocumentOccurrences = Map; +function countMapFrom(values: Iterable): Map { + const map = new Map(); + for (const value of values) { + map.set(value, (map.get(value) ?? 0) + 1); + } + return map; +} + interface DocumentChunkEntry { readonly text: string; readonly tf: TermFrequencies; } -interface Term { - readonly term: string; - readonly weight: number; -} - export interface TfIdfDocument { readonly key: string; readonly textChunks: readonly string[]; @@ -72,53 +75,26 @@ export class TfIdfCalculator { * Count how many times each term (word) appears in a string. */ private static termFrequencies(input: string): TermFrequencies { - const map = new Map(); - for (const value of TfIdfCalculator.splitTerms(input)) { - const existing = map.get(value.term); - if (existing) { - existing.occurrences++; - } else { - map.set(value.term, { weight: value.weight, occurrences: 1 }); - } - } - return map; + return countMapFrom(TfIdfCalculator.splitTerms(input)); } /** * Break a string into terms (words). - * - * TODO: confirm that when we break up a word or generate stems, we likely accidentally over-weight its terms. - * For instance, if the document is: `cats wear hats` and the user searches `cats wear`, we could end up giving too - * much weight to `cats` since the document would be broken into: `[cats, cat, wear, hats, hat]` while the query - * would be broken into `[cats, cat, wear]`. This means that terms derived from `cats` end up being matched on multiple - * times, which isn't really right. - * - * Maybe we need to generate a tree of terms for the document where we stop searching once a match has been found: */ - private static *splitTerms(input: string): Iterable { + private static *splitTerms(input: string): Iterable { const normalize = (word: string) => word.toLowerCase(); // Only match on words that are at least 3 characters long and start with a letter for (const [word] of input.matchAll(/\b\p{Letter}[\p{Letter}\d]{2,}\b/gu)) { - yield { term: normalize(word), weight: 1 }; + yield normalize(word); - // Include both the original term and the stemmed version - const stemmedTerm = stem(word); - if (stemmedTerm !== word) { - yield { term: normalize(stemmedTerm), weight: 0.75 }; - } - - const camelParts = word.split(/(?=[A-Z])/g); + // eslint-disable-next-line local/code-no-look-behind-regex + const camelParts = word.split(/(?<=[a-z])(?=[A-Z])/g); if (camelParts.length > 1) { for (const part of camelParts) { // Require at least 3 letters in the parts of a camel case word if (part.length > 2 && /\p{Letter}{3,}/gu.test(part)) { - yield { term: normalize(part), weight: 0.75 }; - - const stemmedPart = stem(part); - if (stemmedPart !== part && stemmedPart.length > 2) { - yield { term: normalize(stemmedPart), weight: 0.5 }; - } + yield normalize(part); } } } @@ -210,7 +186,7 @@ export class TfIdfCalculator { idfCache.set(term, chunkIdf); } - const chunkTfidf = chunkTf.weight * chunkTf.occurrences * chunkIdf; + const chunkTfidf = chunkTf * chunkIdf; sum += chunkTfidf * termTfidf; } return sum; @@ -233,7 +209,7 @@ export class TfIdfCalculator { for (const [word, occurrences] of termFrequencies) { const idf = this.computeIdf(word); if (idf > 0) { - embedding[word] = occurrences.weight * occurrences.occurrences * idf; + embedding[word] = occurrences * idf; } } return embedding; @@ -263,197 +239,3 @@ export function normalizeTfIdfScores(scores: TfIdfScore[]): NormalizedTfIdfScore return result as TfIdfScore[]; } - -// https://github.com/maxxxxxdlp/porter-stemming - -/** - * TypeScript implementation of the Porter-Stemmer algorithm - */ -export function stem(raw: string): string { - if (raw.length < minLength) { return raw; } - - let word = raw; - const firstCharacter = word[0]; - if (firstCharacter === 'y') { word = firstCharacter.toUpperCase() + word.slice(1); } - - word = steps.reduce((word, step) => step(word), word); - - // Turn initial Y back to y - if (firstCharacter === 'y') { word = firstCharacter.toLowerCase() + word.slice(1); } - - return word; -} - -const minLength = 3; -const vowel = '[aeiouy]'; -const consonant = '[^aeiou]'; -const consonantSequence = `${consonant}[^aeiouy]*`; -const o = new RegExp(`^${consonantSequence}${vowel}[^aeiouwxy]$`, 'u'); - -/** - * Try to match a word against a rule - */ -const replace = - ( - replacements: Readonly< - Record< - string, - | string - | readonly [condition: (word: string) => boolean, replacement: string] - > - > - ) => - (word: string): string => { - const entries = Object.entries(replacements).sort( - ([left], [right]) => right.length - left.length - ); - for (const [suffix, replacement] of entries) { - if (!word.endsWith(suffix)) { continue; } - if ( - Array.isArray(replacement) && - !replacement[0](word.slice(0, -suffix.length)) - ) { break; } - return `${word.slice(0, -suffix.length)}${Array.isArray(replacement) ? replacement[1] : replacement - }`; - } - return word; - }; - -const calculateMeasure = (word: string): number => - sum( - Array.from(word.split(''), (_, index) => - !isConsonant(word, index) && - index + 1 < word.length && - isConsonant(word, index + 1) - ? 1 - : 0 - ) - ); - -const sum = (array: readonly number[]): number => - array.reduce((sum, value) => sum + value, 0); - -const measure = - (min: number) => - (word: string): boolean => - calculateMeasure(word) > min; - -function isConsonant(word: string, index: number): boolean { - const vowels = 'aeiou'; - if (vowels.includes(word[index])) { return false; } - if (word[index] === 'y') { return index === 0 ? true : !isConsonant(word, index - 1); } - else { return true; } -} - -const hasVowel = (word: string): boolean => - Array.from(word.split('')).some((_, index) => !isConsonant(word, index)); - -const steps: readonly ((word: string) => string)[] = [ - // Step 1a - replace({ - sses: 'ss', - ies: 'i', - ss: 'ss', - s: '', - }), - // Step 1b - (word): string => { - if (word.endsWith('eed')) { return replace({ eed: [measure(0), 'ee'] })(word); } - const updated = replace({ ed: [hasVowel, ''], ing: [hasVowel, ''] })(word); - if (updated === word) { return word; } - const replaced = replace({ - at: 'ate', - bl: 'ble', - iz: 'ize', - })(updated); - if (replaced !== updated) { return replaced; } - - if ( - replaced.at(-1) === replaced.at(-'dd'.length) && - isConsonant(replaced, replaced.length - 1) && - !['l', 's', 'z'].some((letter) => replaced.endsWith(letter)) - ) { return replaced.slice(0, -1); } - - if (calculateMeasure(replaced) === 1 && o.test(replaced)) { return `${replaced}e`; } - return replaced; - }, - // Step 1c - replace({ - y: [hasVowel, 'i'], - }), - // Step 2 - replace({ - ational: [measure(0), 'ate'], - tional: [measure(0), 'tion'], - enci: [measure(0), 'ence'], - anci: [measure(0), 'ance'], - izer: [measure(0), 'ize'], - abli: [measure(0), 'able'], - alli: [measure(0), 'al'], - entli: [measure(0), 'ent'], - eli: [measure(0), 'e'], - ousli: [measure(0), 'ous'], - ization: [measure(0), 'ize'], - ation: [measure(0), 'ate'], - ator: [measure(0), 'ate'], - alism: [measure(0), 'al'], - iveness: [measure(0), 'ive'], - fulness: [measure(0), 'ful'], - ousness: [measure(0), 'ous'], - aliti: [measure(0), 'al'], - iviti: [measure(0), 'ive'], - biliti: [measure(0), 'ble'], - logi: [measure(0), 'log'], - bli: [measure(0), 'ble'], - }), - // Step 3 - replace({ - icate: [measure(0), 'ic'], - ative: [measure(0), ''], - alize: [measure(0), 'al'], - iciti: [measure(0), 'ic'], - ical: [measure(0), 'ic'], - ful: [measure(0), ''], - ness: [measure(0), ''], - }), - // Step 4 - (word): string => { - const newWord = replace({ - al: [measure(1), ''], - ance: [measure(1), ''], - ence: [measure(1), ''], - er: [measure(1), ''], - ic: [measure(1), ''], - able: [measure(1), ''], - ible: [measure(1), ''], - ant: [measure(1), ''], - ement: [measure(1), ''], - ment: [measure(1), ''], - ent: [measure(1), ''], - ou: [measure(1), ''], - ism: [measure(1), ''], - ate: [measure(1), ''], - iti: [measure(1), ''], - ous: [measure(1), ''], - ive: [measure(1), ''], - ize: [measure(1), ''], - })(word); - if (newWord !== word) { return newWord; } - return (word.endsWith('tion') || word.endsWith('sion')) && - measure(1)(word.slice(0, -'ion'.length)) - ? word.slice(0, -'ion'.length) - : word; - }, - // Step 5a - (word): string => { - if (!word.endsWith('e')) { return word; } - const stem = word.slice(0, -1); - const measure = calculateMeasure(stem); - return measure > 1 || (measure === 1 && !o.test(stem)) ? stem : word; - }, - // Step 5b - (word): string => - word.endsWith('ll') && measure(1)(word.slice(0, -1)) - ? word.slice(0, -1) - : word, -]; diff --git a/src/vs/base/test/common/tfIdf.test.ts b/src/vs/base/test/common/tfIdf.test.ts index 38ffa884ba3..69c094759eb 100644 --- a/src/vs/base/test/common/tfIdf.test.ts +++ b/src/vs/base/test/common/tfIdf.test.ts @@ -163,19 +163,6 @@ suite('TF-IDF Calculator', function () { } }); - test('Should weigh exact match higher than camelCase match', () => { - for (const docs of permutate([ - makeDocument('/A', 'catDog'), - makeDocument('/B', 'cat cat cat fish'), - makeDocument('/C', 'dog dog cat rat'), - makeDocument('/D', 'pig'), - ])) { - const tfidf = new TfIdfCalculator().updateDocuments(docs); - const scores = tfidf.calculateScores('catDog', CancellationToken.None); - assertScoreOrdersEqual(scores, ['/A', '/C', '/B']); - } - }); - test('Should not match document after delete', () => { const docA = makeDocument('/A', 'cat dog cat'); const docB = makeDocument('/B', 'cat fish'); @@ -197,18 +184,6 @@ suite('TF-IDF Calculator', function () { scores = tfidf.calculateScores('cat', CancellationToken.None); assertScoreOrdersEqual(scores, []); }); - - test('Should find stemmed words', () => { - for (const docs of permutate([ - makeDocument('/A', 'cats'), - makeDocument('/B', 'dogs cat'), - makeDocument('/D', 'pig'), - ])) { - const tfidf = new TfIdfCalculator().updateDocuments(docs); - const scores = tfidf.calculateScores('cats', CancellationToken.None); - assertScoreOrdersEqual(scores, ['/A', '/B']); - } - }); }); function makeDocument(key: string, content: string | string[]): TfIdfDocument { From 9ab55b6f141ded2fad13f10d9635678b8b860be9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:36:44 +0200 Subject: [PATCH 121/133] SCM - clean up the history provider listeners when removing a repository (#193550) --- src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index e02a5117ae4..f50b0aa2603 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -439,10 +439,13 @@ class SCMSyncPaneViewModel { const repositoryDisposable = repository.provider.onDidChangeHistoryProvider(() => this._onDidChangeHistoryProvider(repository)); this._onDidChangeHistoryProvider(repository); - this.repositories.set(repository, { dispose() { repositoryDisposable.dispose(); } }); + this.repositories.set(repository, repositoryDisposable); } for (const repository of removed) { + this.historyProviders.get(repository)?.dispose(); + this.historyProviders.delete(repository); + this.repositories.get(repository)?.dispose(); this.repositories.delete(repository); } From e882412e3904fb43e63b2b674f62ebc7eddcae20 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:17:37 +0200 Subject: [PATCH 122/133] SCM - add aria labels to the SCMSyncViewPane (#193552) --- .../contrib/scm/browser/scmSyncViewPane.ts | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index f50b0aa2603..26df810e221 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -36,6 +36,10 @@ import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup } from 'vs import { localize } from 'vs/nls'; import { Iterable } from 'vs/base/common/iterator'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { basename, dirname } from 'vs/base/common/resources'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { stripIcons } from 'vs/base/common/iconLabels'; type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement; @@ -261,8 +265,49 @@ class HistoryItemChangeRenderer implements ITreeRenderer { + constructor( + @ILabelService private readonly labelService: ILabelService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService + ) { } + getAriaLabel(element: TreeElement): string { - // TODO - add aria labels + if (isSCMRepository(element)) { + let folderName = ''; + if (element.provider.rootUri) { + const folder = this.workspaceContextService.getWorkspaceFolder(element.provider.rootUri); + + if (folder?.uri.toString() === element.provider.rootUri.toString()) { + folderName = folder.name; + } else { + folderName = basename(element.provider.rootUri); + } + } + return `${folderName} ${element.provider.label}`; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return `${stripIcons(element.label).trim()}${element.description ? `, ${element.description}` : ''}`; + } else if (isSCMActionButton(element)) { + return element.button?.command.title ?? ''; + } else if (isSCMHistoryItemTreeElement(element)) { + return `${stripIcons(element.label).trim()}${element.description ? `, ${element.description}` : ''}`; + } else if (isSCMHistoryItemChangeTreeElement(element)) { + const result: string[] = []; + + result.push(basename(element.uri)); + + // TODO - add decoration + // if (element.decorations.tooltip) { + // result.push(element.decorations.tooltip); + // } + + const path = this.labelService.getUriLabel(dirname(element.uri), { relative: true, noPrefix: true }); + + if (path) { + result.push(path); + } + + return result.join(', '); + } + return ''; } getWidgetAriaLabel(): string { @@ -373,9 +418,9 @@ export class SCMSyncViewPane extends ViewPane { this.instantiationService.createInstance(SCMSyncDataSource), { horizontalScrolling: false, - accessibilityProvider: new SCMSyncViewPaneAccessibilityProvider(), - identityProvider: new SCMSyncViewPaneTreeIdentityProvider(), - sorter: new SCMSyncViewPaneTreeSorter(), + accessibilityProvider: this.instantiationService.createInstance(SCMSyncViewPaneAccessibilityProvider), + identityProvider: this.instantiationService.createInstance(SCMSyncViewPaneTreeIdentityProvider), + sorter: this.instantiationService.createInstance(SCMSyncViewPaneTreeSorter), }) as WorkbenchAsyncDataTree; this._register(this._tree); From 616dc3a31163a49c3b88d1f39a095f1cc1ecd740 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 20 Sep 2023 01:31:12 -0700 Subject: [PATCH 123/133] Fix #193151. Add IFileSystemProviderWithOpenReadWriteCloseCapability to FileUserDataProvider (#193228) * Fix #193151. Add IFileSystemProviderWithOpenReadWriteCloseCapability to FileUserDataProvider * Update src/vs/platform/files/common/inMemoryFilesystemProvider.ts Co-authored-by: Benjamin Pasero * implement and adopt new enforce methods for atomicity * :lipstick: * :lipstick: * actually add tests and fix issues! --------- Co-authored-by: Benjamin Pasero --- src/vs/platform/files/common/fileService.ts | 65 ++++++++---- src/vs/platform/files/common/files.ts | 3 + .../common/inMemoryFilesystemProvider.ts | 77 ++++++++++++-- .../files/test/browser/fileService.test.ts | 100 +++++++++++++++++- .../test/common/nullFileSystemProvider.ts | 19 ++-- .../userData/common/fileUserDataProvider.ts | 97 ++++++++++------- .../test/browser/fileUserDataProvider.test.ts | 12 ++- .../test/browser/workbenchTestServices.ts | 2 +- 8 files changed, 295 insertions(+), 80 deletions(-) diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index b65c4e8705a..8a8d6a57bda 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -18,7 +18,7 @@ import { basename, dirname, extUri, extUriIgnorePathCase, IExtUri, isAbsolutePat import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError } from 'vs/platform/files/common/files'; +import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError, hasFileAtomicDeleteCapability, hasFileAtomicWriteCapability } from 'vs/platform/files/common/files'; import { readFileIntoStream } from 'vs/platform/files/common/io'; import { ILogService } from 'vs/platform/log/common/log'; import { ErrorNoTelemetry } from 'vs/base/common/errors'; @@ -359,10 +359,18 @@ export class FileService extends Disposable implements IFileService { const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource); const { providerExtUri } = this.getExtUri(provider); + let writeFileOptions = options; + if (hasFileAtomicWriteCapability(provider) && !writeFileOptions?.atomic) { + const enforcedAtomicWrite = provider.enforceAtomicWriteFile?.(resource); + if (enforcedAtomicWrite) { + writeFileOptions = { ...options, atomic: enforcedAtomicWrite }; + } + } + try { // validate write - const stat = await this.validateWriteFile(provider, resource, options); + const stat = await this.validateWriteFile(provider, resource, writeFileOptions); // mkdir recursively as needed if (!stat) { @@ -389,9 +397,13 @@ export class FileService extends Disposable implements IFileService { bufferOrReadableOrStreamOrBufferedStream = bufferOrReadableOrStream; } - // write file: unbuffered (only if data to write is a buffer, or the provider has no buffered write capability) - if (!hasOpenReadWriteCloseCapability(provider) || (hasReadWriteCapability(provider) && bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer)) { - await this.doWriteUnbuffered(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream); + // write file: unbuffered + if ( + !hasOpenReadWriteCloseCapability(provider) || // buffered writing is unsupported + (hasReadWriteCapability(provider) && bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer) || // data is a full buffer already + (hasReadWriteCapability(provider) && hasFileAtomicWriteCapability(provider) && writeFileOptions?.atomic) // atomic write forces unbuffered write if the provider supports it + ) { + await this.doWriteUnbuffered(provider, resource, writeFileOptions, bufferOrReadableOrStreamOrBufferedStream); } // write file: buffered @@ -399,20 +411,20 @@ export class FileService extends Disposable implements IFileService { const contents = bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream; // atomic write - if (options?.atomic !== false && options?.atomic?.postfix) { - await this.doWriteBufferedAtomic(provider, resource, joinPath(dirname(resource), `${basename(resource)}${options.atomic.postfix}`), options, contents); + if (writeFileOptions?.atomic !== false && writeFileOptions?.atomic?.postfix) { + await this.doWriteBufferedAtomic(provider, resource, joinPath(dirname(resource), `${basename(resource)}${writeFileOptions.atomic.postfix}`), writeFileOptions, contents); } // non-atomic write else { - await this.doWriteBuffered(provider, resource, options, contents); + await this.doWriteBuffered(provider, resource, writeFileOptions, contents); } } // events this._onDidRunOperation.fire(new FileOperationEvent(resource, FileOperation.WRITE)); } catch (error) { - throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), writeFileOptions); } return this.resolve(resource, { resolveMetadata: true }); @@ -535,8 +547,13 @@ export class FileService extends Disposable implements IFileService { // cancellation of the read operation. const cancellableSource = new CancellationTokenSource(token); + let readFileOptions = options; + if (hasFileAtomicReadCapability(provider) && provider.enforceAtomicReadFile?.(resource)) { + readFileOptions = { ...options, atomic: true }; + } + // validate read operation - const statPromise = this.validateReadFile(resource, options).then(stat => stat, error => { + const statPromise = this.validateReadFile(resource, readFileOptions).then(stat => stat, error => { cancellableSource.dispose(true); throw error; @@ -549,27 +566,27 @@ export class FileService extends Disposable implements IFileService { // due to the likelihood of hitting a NOT_MODIFIED_SINCE result. // otherwise, we let it run in parallel to the file reading for // optimal startup performance. - if (typeof options?.etag === 'string' && options.etag !== ETAG_DISABLED) { + if (typeof readFileOptions?.etag === 'string' && readFileOptions.etag !== ETAG_DISABLED) { await statPromise; } // read unbuffered if ( - (options?.atomic && hasFileAtomicReadCapability(provider)) || // atomic reads are always unbuffered + (readFileOptions?.atomic && hasFileAtomicReadCapability(provider)) || // atomic reads are always unbuffered !(hasOpenReadWriteCloseCapability(provider) || hasFileReadStreamCapability(provider)) || // provider has no buffered capability - (hasReadWriteCapability(provider) && options?.preferUnbuffered) // unbuffered read is preferred + (hasReadWriteCapability(provider) && readFileOptions?.preferUnbuffered) // unbuffered read is preferred ) { - fileStream = this.readFileUnbuffered(provider, resource, options); + fileStream = this.readFileUnbuffered(provider, resource, readFileOptions); } // read streamed (always prefer over primitive buffered read) else if (hasFileReadStreamCapability(provider)) { - fileStream = this.readFileStreamed(provider, resource, cancellableSource.token, options); + fileStream = this.readFileStreamed(provider, resource, cancellableSource.token, readFileOptions); } // read buffered else { - fileStream = this.readFileBuffered(provider, resource, cancellableSource.token, options); + fileStream = this.readFileBuffered(provider, resource, cancellableSource.token, readFileOptions); } fileStream.on('end', () => cancellableSource.dispose()); @@ -592,7 +609,7 @@ export class FileService extends Disposable implements IFileService { // Re-throw errors as file operation errors but preserve // specific errors (such as not modified since) - throw this.restoreReadError(error, resource, options); + throw this.restoreReadError(error, resource, readFileOptions); } } @@ -1023,9 +1040,17 @@ export class FileService extends Disposable implements IFileService { async del(resource: URI, options?: Partial): Promise { const provider = await this.doValidateDelete(resource, options); - const useTrash = !!options?.useTrash; - const recursive = !!options?.recursive; - const atomic = options?.atomic ?? false; + let deleteFileOptions = options; + if (hasFileAtomicDeleteCapability(provider) && !deleteFileOptions?.atomic) { + const enforcedAtomicDelete = provider.enforceAtomicDelete?.(resource); + if (enforcedAtomicDelete) { + deleteFileOptions = { ...options, atomic: enforcedAtomicDelete }; + } + } + + const useTrash = !!deleteFileOptions?.useTrash; + const recursive = !!deleteFileOptions?.recursive; + const atomic = deleteFileOptions?.atomic ?? false; // Delete through provider await provider.delete(resource, { recursive, useTrash, atomic }); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 4f7c149b6d6..0913cb6eaa8 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -657,6 +657,7 @@ export function hasFileReadStreamCapability(provider: IFileSystemProvider): prov export interface IFileSystemProviderWithFileAtomicReadCapability extends IFileSystemProvider { readFile(resource: URI, opts?: IFileAtomicReadOptions): Promise; + enforceAtomicReadFile?(resource: URI): boolean; } export function hasFileAtomicReadCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAtomicReadCapability { @@ -669,6 +670,7 @@ export function hasFileAtomicReadCapability(provider: IFileSystemProvider): prov export interface IFileSystemProviderWithFileAtomicWriteCapability extends IFileSystemProvider { writeFile(resource: URI, contents: Uint8Array, opts?: IFileAtomicWriteOptions): Promise; + enforceAtomicWriteFile?(resource: URI): IFileAtomicOptions | false; } export function hasFileAtomicWriteCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAtomicWriteCapability { @@ -681,6 +683,7 @@ export function hasFileAtomicWriteCapability(provider: IFileSystemProvider): pro export interface IFileSystemProviderWithFileAtomicDeleteCapability extends IFileSystemProvider { delete(resource: URI, opts: IFileAtomicDeleteOptions): Promise; + enforceAtomicDelete?(resource: URI): IFileAtomicOptions | false; } export function hasFileAtomicDeleteCapability(provider: IFileSystemProvider): provider is IFileSystemProviderWithFileAtomicDeleteCapability { diff --git a/src/vs/platform/files/common/inMemoryFilesystemProvider.ts b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts index 9402533647e..f875c3be628 100644 --- a/src/vs/platform/files/common/inMemoryFilesystemProvider.ts +++ b/src/vs/platform/files/common/inMemoryFilesystemProvider.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; +import { ReadableStreamEvents, newWriteableStream } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; -import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError } from 'vs/platform/files/common/files'; +import { FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions, createFileSystemProviderError, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileReadStreamCapability } from 'vs/platform/files/common/files'; class File implements IStat { - type: FileType.File; - ctime: number; + readonly type: FileType.File; + readonly ctime: number; mtime: number; size: number; @@ -30,13 +32,13 @@ class File implements IStat { class Directory implements IStat { - type: FileType.Directory; - ctime: number; + readonly type: FileType.Directory; + readonly ctime: number; mtime: number; size: number; name: string; - entries: Map; + readonly entries: Map; constructor(name: string) { this.type = FileType.Directory; @@ -50,8 +52,16 @@ class Directory implements IStat { type Entry = File | Directory; -export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability { +export class InMemoryFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileAtomicReadCapability, + IFileSystemProviderWithFileAtomicWriteCapability, + IFileSystemProviderWithFileAtomicDeleteCapability { + private memoryFdCounter = 0; + private readonly fdMemory = new Map(); private _onDidChangeCapabilities = this._register(new Emitter()); readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; @@ -92,6 +102,15 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound); } + readFileStream(resource: URI): ReadableStreamEvents { + const data = this._lookupAsFile(resource, false).data; + + const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + stream.end(data); + + return stream; + } + async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { const basename = resources.basename(resource); const parent = this._lookupParentDirectory(resource); @@ -117,6 +136,44 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste this._fireSoon({ type: FileChangeType.UPDATED, resource }); } + // file open/read/write/close + open(resource: URI, opts: IFileOpenOptions): Promise { + const data = this._lookupAsFile(resource, false).data; + if (data) { + const fd = this.memoryFdCounter++; + this.fdMemory.set(fd, data); + return Promise.resolve(fd); + } + throw createFileSystemProviderError('file not found', FileSystemProviderErrorCode.FileNotFound); + } + + close(fd: number): Promise { + this.fdMemory.delete(fd); + return Promise.resolve(); + } + + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const memory = this.fdMemory.get(fd); + if (!memory) { + throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable); + } + + const toWrite = VSBuffer.wrap(memory).slice(pos, pos + length); + data.set(toWrite.buffer, offset); + return Promise.resolve(toWrite.byteLength); + } + + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const memory = this.fdMemory.get(fd); + if (!memory) { + throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable); + } + + const toWrite = VSBuffer.wrap(data).slice(offset, offset + length); + memory.set(toWrite.buffer, pos); + return Promise.resolve(toWrite.byteLength); + } + // --- manage files/folders async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { @@ -241,4 +298,10 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste this._bufferedChanges.length = 0; }, 5); } + + override dispose(): void { + super.dispose(); + + this.fdMemory.clear(); + } } diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index 5b32db14d64..5d0b16250ed 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -5,12 +5,14 @@ import * as assert from 'assert'; import { DeferredPromise, timeout } from 'vs/base/common/async'; +import { bufferToReadable, bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; import { consumeStream, newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IFileOpenOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileType, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IStat } from 'vs/platform/files/common/files'; +import { IFileOpenOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileType, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IStat, IFileAtomicReadOptions, IFileAtomicWriteOptions, IFileAtomicDeleteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileAtomicOptions } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -177,7 +179,7 @@ suite('File Service', () => { throw new Error('failed'); } - readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { + override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { if (async) { const stream = newWriteableStream(chunk => chunk[0]); timeout(5, CancellationToken.None).then(() => stream.error(new Error('failed'))); @@ -232,7 +234,7 @@ suite('File Service', () => { }; } - readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { + override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { const stream = newWriteableStream(chunk => chunk[0]); disposables.add(token.onCancellationRequested(() => { stream.error(new Error('Expected cancellation')); @@ -245,7 +247,7 @@ suite('File Service', () => { } }; - const disposable = service.registerProvider('test', provider); + disposables.add(service.registerProvider('test', provider)); provider.setCapabilities(FileSystemProviderCapabilities.FileReadStream); @@ -272,8 +274,96 @@ suite('File Service', () => { } assert.ok(e2); + }); - disposable.dispose(); + test('enforced atomic read/write/delete', async () => { + const service = disposables.add(new FileService(new NullLogService())); + + const atomicResource = URI.parse('test://foo/bar/atomic'); + const nonAtomicResource = URI.parse('test://foo/nonatomic'); + + let atomicReadCounter = 0; + let atomicWriteCounter = 0; + let atomicDeleteCounter = 0; + + const provider = new class extends NullFileSystemProvider implements IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability { + + override async stat(resource: URI): Promise { + return { + type: FileType.File, + ctime: Date.now(), + mtime: Date.now(), + size: 0 + }; + } + + override async readFile(resource: URI, opts?: IFileAtomicReadOptions): Promise { + if (opts?.atomic) { + atomicReadCounter++; + } + return new Uint8Array(); + } + + override readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { + return newWriteableStream(chunk => chunk[0]); + } + + enforceAtomicReadFile(resource: URI): boolean { + return isEqual(resource, atomicResource); + } + + override async writeFile(resource: URI, content: Uint8Array, opts: IFileAtomicWriteOptions): Promise { + if (opts.atomic) { + atomicWriteCounter++; + } + } + + enforceAtomicWriteFile(resource: URI): IFileAtomicOptions | false { + return isEqual(resource, atomicResource) ? { postfix: '.tmp' } : false; + } + + override async delete(resource: URI, opts: IFileAtomicDeleteOptions): Promise { + if (opts.atomic) { + atomicDeleteCounter++; + } + } + + enforceAtomicDelete(resource: URI): IFileAtomicOptions | false { + return isEqual(resource, atomicResource) ? { postfix: '.tmp' } : false; + } + }; + + provider.setCapabilities( + FileSystemProviderCapabilities.FileReadWrite | + FileSystemProviderCapabilities.FileOpenReadWriteClose | + FileSystemProviderCapabilities.FileReadStream | + FileSystemProviderCapabilities.FileAtomicRead | + FileSystemProviderCapabilities.FileAtomicWrite | + FileSystemProviderCapabilities.FileAtomicDelete + ); + + disposables.add(service.registerProvider('test', provider)); + + await service.readFile(atomicResource); + await service.readFile(nonAtomicResource); + await service.readFileStream(atomicResource); + await service.readFileStream(nonAtomicResource); + + await service.writeFile(atomicResource, VSBuffer.fromString('')); + await service.writeFile(nonAtomicResource, VSBuffer.fromString('')); + + await service.writeFile(atomicResource, bufferToStream(VSBuffer.fromString(''))); + await service.writeFile(nonAtomicResource, bufferToStream(VSBuffer.fromString(''))); + + await service.writeFile(atomicResource, bufferToReadable(VSBuffer.fromString(''))); + await service.writeFile(nonAtomicResource, bufferToReadable(VSBuffer.fromString(''))); + + await service.del(atomicResource); + await service.del(nonAtomicResource); + + assert.strictEqual(atomicReadCounter, 2); + assert.strictEqual(atomicWriteCounter, 3); + assert.strictEqual(atomicDeleteCounter, 1); }); ensureNoDisposablesAreLeakedInTestSuite(); diff --git a/src/vs/platform/files/test/common/nullFileSystemProvider.ts b/src/vs/platform/files/test/common/nullFileSystemProvider.ts index 291682d58c1..59862eaf63f 100644 --- a/src/vs/platform/files/test/common/nullFileSystemProvider.ts +++ b/src/vs/platform/files/test/common/nullFileSystemProvider.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; -import { IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileWriteOptions, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; +import { IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileWriteOptions, IFileChange, IFileSystemProvider, IStat, IWatchOptions, IFileReadStreamOptions } from 'vs/platform/files/common/files'; export class NullFileSystemProvider implements IFileSystemProvider { @@ -36,11 +38,12 @@ export class NullFileSystemProvider implements IFileSystemProvider { async readdir(resource: URI): Promise<[string, FileType][]> { return undefined!; } async delete(resource: URI, opts: IFileDeleteOptions): Promise { return undefined; } async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { return undefined; } - async copy?(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { return undefined; } - async readFile?(resource: URI): Promise { return undefined!; } - async writeFile?(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { return undefined; } - async open?(resource: URI, opts: IFileOpenOptions): Promise { return undefined!; } - async close?(fd: number): Promise { return undefined; } - async read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } - async write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } + async copy(from: URI, to: URI, opts: IFileOverwriteOptions): Promise { return undefined; } + async readFile(resource: URI): Promise { return undefined!; } + readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { return undefined!; } + async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { return undefined; } + async open(resource: URI, opts: IFileOpenOptions): Promise { return undefined!; } + async close(fd: number): Promise { return undefined; } + async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } + async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } } diff --git a/src/vs/platform/userData/common/fileUserDataProvider.ts b/src/vs/platform/userData/common/fileUserDataProvider.ts index 47239b78561..3cddb5514a3 100644 --- a/src/vs/platform/userData/common/fileUserDataProvider.ts +++ b/src/vs/platform/userData/common/fileUserDataProvider.ts @@ -2,16 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, IFileOverwriteOptions, FileType, IFileWriteOptions, IFileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileFolderCopyCapability, hasFileFolderCopyCapability, hasFileAtomicWriteCapability } from 'vs/platform/files/common/files'; +import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, IFileOverwriteOptions, FileType, IFileWriteOptions, IFileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions, IFileSystemProviderWithFileAtomicReadCapability, hasFileFolderCopyCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileCloneCapability, hasFileCloneCapability, IFileAtomicReadOptions, IFileAtomicOptions } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; import { ILogService } from 'vs/platform/log/common/log'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { isObject } from 'vs/base/common/types'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ResourceSet } from 'vs/base/common/map'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -23,44 +21,63 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' */ export class FileUserDataProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileAtomicReadCapability, - IFileSystemProviderWithFileFolderCopyCapability { + IFileSystemProviderWithFileAtomicWriteCapability, + IFileSystemProviderWithFileAtomicDeleteCapability, + IFileSystemProviderWithFileCloneCapability { - get capabilities() { return this.fileSystemProvider.capabilities & ~FileSystemProviderCapabilities.FileOpenReadWriteClose; } - readonly onDidChangeCapabilities: Event = this.fileSystemProvider.onDidChangeCapabilities; + readonly capabilities = this.fileSystemProvider.capabilities; + readonly onDidChangeCapabilities = this.fileSystemProvider.onDidChangeCapabilities; private readonly _onDidChangeFile = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + readonly onDidChangeFile = this._onDidChangeFile.event; private readonly watchResources = TernarySearchTree.forUris(() => !(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); - private readonly atomicWritesResources: ResourceSet; + private readonly atomicReadWriteResources = new ResourceSet((uri) => this.uriIdentityService.extUri.getComparisonKey(this.toFileSystemResource(uri))); constructor( private readonly fileSystemScheme: string, - private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability & (IFileSystemProviderWithFileReadStreamCapability | IFileSystemProviderWithFileAtomicReadCapability | IFileSystemProviderWithFileFolderCopyCapability), + private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability & IFileSystemProviderWithOpenReadWriteCloseCapability & IFileSystemProviderWithFileReadStreamCapability & IFileSystemProviderWithFileAtomicReadCapability & IFileSystemProviderWithFileAtomicWriteCapability & IFileSystemProviderWithFileAtomicDeleteCapability, private readonly userDataScheme: string, private readonly userDataProfilesService: IUserDataProfilesService, - uriIdentityService: IUriIdentityService, + private readonly uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); - this.atomicWritesResources = new ResourceSet((uri) => uriIdentityService.extUri.getComparisonKey(this.toFileSystemResource(uri))); - this.updateAtomicWritesResources(); - this._register(userDataProfilesService.onDidChangeProfiles(() => this.updateAtomicWritesResources())); + this.updateAtomicReadWritesResources(); + this._register(userDataProfilesService.onDidChangeProfiles(() => this.updateAtomicReadWritesResources())); this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e))); } - private updateAtomicWritesResources(): void { - this.atomicWritesResources.clear(); + private updateAtomicReadWritesResources(): void { + this.atomicReadWriteResources.clear(); for (const profile of this.userDataProfilesService.profiles) { - this.atomicWritesResources.add(profile.settingsResource); - this.atomicWritesResources.add(profile.keybindingsResource); - this.atomicWritesResources.add(profile.tasksResource); - this.atomicWritesResources.add(profile.extensionsResource); + this.atomicReadWriteResources.add(profile.settingsResource); + this.atomicReadWriteResources.add(profile.keybindingsResource); + this.atomicReadWriteResources.add(profile.tasksResource); + this.atomicReadWriteResources.add(profile.extensionsResource); } } + open(resource: URI, opts: IFileOpenOptions): Promise { + return this.fileSystemProvider.open(this.toFileSystemResource(resource), opts); + } + + close(fd: number): Promise { + return this.fileSystemProvider.close(fd); + } + + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + return this.fileSystemProvider.read(fd, pos, data, offset, length); + } + + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + return this.fileSystemProvider.write(fd, pos, data, offset, length); + } + watch(resource: URI, opts: IWatchOptions): IDisposable { this.watchResources.set(resource, resource); const disposable = this.fileSystemProvider.watch(this.toFileSystemResource(resource), opts); @@ -82,35 +99,34 @@ export class FileUserDataProvider extends Disposable implements return this.fileSystemProvider.rename(this.toFileSystemResource(from), this.toFileSystemResource(to), opts); } - readFile(resource: URI): Promise { - return this.fileSystemProvider.readFile(this.toFileSystemResource(resource), { atomic: true }); + readFile(resource: URI, opts?: IFileAtomicReadOptions): Promise { + return this.fileSystemProvider.readFile(this.toFileSystemResource(resource), opts); } readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { - const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); - (async () => { - try { - const contents = await this.readFile(resource); - stream.end(contents); - } catch (error) { - stream.error(error); - stream.end(); - } - })(); - return stream; + return this.fileSystemProvider.readFileStream(this.toFileSystemResource(resource), opts, token); } readdir(resource: URI): Promise<[string, FileType][]> { return this.fileSystemProvider.readdir(this.toFileSystemResource(resource)); } + enforceAtomicReadFile(resource: URI): boolean { + return this.atomicReadWriteResources.has(resource); + } + writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { - if (this.atomicWritesResources.has(resource) && !isObject(opts.atomic) && hasFileAtomicWriteCapability(this.fileSystemProvider)) { - opts = { ...opts, atomic: { postfix: '.vsctmp' } }; - } return this.fileSystemProvider.writeFile(this.toFileSystemResource(resource), content, opts); } + enforceAtomicWriteFile(resource: URI): IFileAtomicOptions | false { + if (this.atomicReadWriteResources.has(resource)) { + return { postfix: '.vsctmp' }; + } + + return false; + } + delete(resource: URI, opts: IFileDeleteOptions): Promise { return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts); } @@ -122,6 +138,13 @@ export class FileUserDataProvider extends Disposable implements throw new Error('copy not supported'); } + cloneFile(from: URI, to: URI): Promise { + if (hasFileCloneCapability(this.fileSystemProvider)) { + return this.fileSystemProvider.cloneFile(this.toFileSystemResource(from), this.toFileSystemResource(to)); + } + throw new Error('clone not supported'); + } + private handleFileChanges(changes: readonly IFileChange[]): void { const userDataChanges: IFileChange[] = []; for (const change of changes) { diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index 2a88a1d2c5f..ef65f7c57e5 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileOpenOptions, IFileSystemProviderWithFileReadStreamCapability, IFileReadStreamOptions } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; @@ -21,6 +21,8 @@ import product from 'vs/platform/product/common/product'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { ReadableStreamEvents } from 'vs/base/common/stream'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -278,10 +280,11 @@ suite('FileUserDataProvider', () => { }); }); -class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { +class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability { constructor(readonly onDidChangeFile: Event) { } + readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities: Event = Event.None; @@ -301,7 +304,12 @@ class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapa writeFile(): Promise { throw new Error('Not Supported'); } delete(): Promise { throw new Error('Not Supported'); } + open(resource: URI, opts: IFileOpenOptions): Promise { throw new Error('Not Supported'); } + close(fd: number): Promise { throw new Error('Not Supported'); } + read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { throw new Error('Not Supported'); } + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { throw new Error('Not Supported'); } + readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { throw new Error('Method not implemented.'); } } suite('FileUserDataProvider - Watching', () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index ab2bc3c34ea..1a62f5f9cd6 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1396,7 +1396,7 @@ export class TestInMemoryFileSystemProvider extends InMemoryFileSystemProvider i | FileSystemProviderCapabilities.FileReadStream; } - readFileStream(resource: URI): ReadableStreamEvents { + override readFileStream(resource: URI): ReadableStreamEvents { const BUFFER_SIZE = 64 * 1024; const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); From 30605c2548ec72c3b769c70e29e9dacefb49c9dc Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 20 Sep 2023 10:28:09 +0200 Subject: [PATCH 124/133] Update to @vscode/proxy-agent 0.17.4 --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 4d055ed5e9c..1940684eb1b 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.17.3", + "@vscode/proxy-agent": "^0.17.4", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.11", "@vscode/sqlite3": "5.1.6-vscode", diff --git a/remote/package.json b/remote/package.json index fa7bd64e3d6..5da071b0724 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.17.3", + "@vscode/proxy-agent": "^0.17.4", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.11", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/yarn.lock b/remote/yarn.lock index 040443b2631..21b77b54043 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -58,10 +58,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.17.3": - version "0.17.3" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.3.tgz#ae46cb64d58a1a2ba063321eb4e3339eaba703b8" - integrity sha512-l+00ALcWQyDsW3kd22uOeAn84KHFkixnk/STiBFRxltbTiaiJsHCCpjlmX+x/RK+B97FcRLlg+MRRb7jtWAnng== +"@vscode/proxy-agent@^0.17.4": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.4.tgz#e3ffb63357353a428436f15a69de3453a5061f0c" + integrity sha512-tX8eidofoJlZFRWzdiiW3wyu26hgIRk8HvM/RoP1wVSu3U/As36EgGIZYG6pPnqiythRqTcsddniVNA5M39g4w== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" diff --git a/yarn.lock b/yarn.lock index 1cbe44fc1c4..924eaed8282 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1304,10 +1304,10 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.17.3": - version "0.17.3" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.3.tgz#ae46cb64d58a1a2ba063321eb4e3339eaba703b8" - integrity sha512-l+00ALcWQyDsW3kd22uOeAn84KHFkixnk/STiBFRxltbTiaiJsHCCpjlmX+x/RK+B97FcRLlg+MRRb7jtWAnng== +"@vscode/proxy-agent@^0.17.4": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.4.tgz#e3ffb63357353a428436f15a69de3453a5061f0c" + integrity sha512-tX8eidofoJlZFRWzdiiW3wyu26hgIRk8HvM/RoP1wVSu3U/As36EgGIZYG6pPnqiythRqTcsddniVNA5M39g4w== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" From bc8147bc866c02a5da50423125af2584ce53dfd4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Sep 2023 10:55:26 +0200 Subject: [PATCH 125/133] simplify (#193554) simplufy - suggestison from @rzhao271 --- src/vs/base/browser/ui/icons/iconSelectBox.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index 421893899b1..afc47f018d0 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -223,8 +223,7 @@ export class IconSelectBox extends Disposable { focusNextRow(): void { let nextRowIndex = this.focusedItemIndex + this.numberOfElementsPerRow; if (nextRowIndex >= this.renderedIcons.length) { - nextRowIndex = (nextRowIndex % this.numberOfElementsPerRow) + 1; - nextRowIndex = nextRowIndex === this.numberOfElementsPerRow ? 0 : nextRowIndex; + nextRowIndex = (nextRowIndex + 1) % this.numberOfElementsPerRow; } this.focusIcon(nextRowIndex); } From 333c4e687d97007e07dc8ec4e5532cb615496163 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 20 Sep 2023 10:56:37 +0200 Subject: [PATCH 126/133] Only keep one pending comment per location for continue on (#193555) --- src/vs/workbench/contrib/comments/browser/commentService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 303f24a6a84..8e5f5e4de7d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -430,7 +430,7 @@ export class CommentService extends Disposable implements ICommentService { changedOwners.add(pendingComment.owner); } else { const commentsForOwner = this._continueOnComments.get(pendingComment.owner)!; - if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range) || (comment.body !== pendingComment.body))) { + if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range))) { commentsForOwner.push(pendingComment); changedOwners.add(pendingComment.owner); } From 1da15ea905612d27e8b64633248857cdd09e2134 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 20 Sep 2023 12:00:29 +0200 Subject: [PATCH 127/133] Indicates end of line positions --- .../editor/test/node/diffing/fixtures.test.ts | 11 ++++++-- .../legacy.expected.diff.json | 8 +++--- .../deletion/advanced.expected.diff.json | 2 +- .../advanced.expected.diff.json | 4 +-- .../difficult-move/legacy.expected.diff.json | 28 +++++++++---------- .../advanced.expected.diff.json | 28 +++++++++---------- .../advanced.expected.diff.json | 12 ++++---- .../legacy.expected.diff.json | 12 ++++---- .../advanced.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 4 +-- .../legacy.expected.diff.json | 8 +++--- .../advanced.expected.diff.json | 4 +-- .../just-whitespace/legacy.expected.diff.json | 4 +-- .../legacy.expected.diff.json | 4 +-- .../move-1/advanced.expected.diff.json | 8 +++--- .../noise-1/advanced.expected.diff.json | 4 +-- .../random-match-2/legacy.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 4 +-- .../random-match-3/legacy.expected.diff.json | 4 +-- .../subword/advanced.expected.diff.json | 4 +-- .../trivial/advanced.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 12 ++++---- .../ts-advanced-bug/legacy.expected.diff.json | 12 ++++---- .../ts-class/advanced.expected.diff.json | 8 +++--- .../ts-class/legacy.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 4 +-- .../ts-confusing-2/legacy.expected.diff.json | 12 ++++---- .../advanced.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 4 +-- .../legacy.expected.diff.json | 4 +-- .../ts-methods/legacy.expected.diff.json | 4 +-- .../legacy.expected.diff.json | 4 +-- .../advanced.expected.diff.json | 4 +-- .../legacy.expected.diff.json | 4 +-- .../ws-alignment/legacy.expected.diff.json | 16 +++++------ 35 files changed, 134 insertions(+), 127 deletions(-) diff --git a/src/vs/editor/test/node/diffing/fixtures.test.ts b/src/vs/editor/test/node/diffing/fixtures.test.ts index a7ff5dbe5a1..18a949c6538 100644 --- a/src/vs/editor/test/node/diffing/fixtures.test.ts +++ b/src/vs/editor/test/node/diffing/fixtures.test.ts @@ -11,6 +11,7 @@ import { FileAccess } from 'vs/base/common/network'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { LegacyLinesDiffComputer } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer'; +import { Range } from 'vs/editor/common/core/range'; suite('diffing fixtures', () => { setup(() => { @@ -48,12 +49,18 @@ suite('diffing fixtures', () => { originalRange: c.original.toString(), modifiedRange: c.modified.toString(), innerChanges: c.innerChanges?.map(c => ({ - originalRange: c.originalRange.toString(), - modifiedRange: c.modifiedRange.toString(), + originalRange: formatRange(c.originalRange, firstContentLines), + modifiedRange: formatRange(c.modifiedRange, secondContentLines), })) || null })); } + function formatRange(range: Range, lines: string[]): string { + const toLastChar = range.endColumn === lines[range.endLineNumber - 1].length + 1; + + return '[' + range.startLineNumber + ',' + range.startColumn + ' -> ' + range.endLineNumber + ',' + range.endColumn + (toLastChar ? ' EOL' : '') + ']'; + } + const actualDiffingResult: DiffingResult = { original: { content: firstContent, fileName: `./${firstFileName}` }, modified: { content: secondContent, fileName: `./${secondFileName}` }, diff --git a/src/vs/editor/test/node/diffing/fixtures/class-replacement/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/class-replacement/legacy.expected.diff.json index ec12e5d7a02..8f07edb769f 100644 --- a/src/vs/editor/test/node/diffing/fixtures/class-replacement/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/class-replacement/legacy.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[29,31)", "innerChanges": [ { - "originalRange": "[29,1 -> 33,41]", - "modifiedRange": "[29,1 -> 30,54]" + "originalRange": "[29,1 -> 33,41 EOL]", + "modifiedRange": "[29,1 -> 30,54 EOL]" } ] }, @@ -59,8 +59,8 @@ "modifiedRange": "[36,42 -> 36,43]" }, { - "originalRange": "[41,29 -> 45,24]", - "modifiedRange": "[36,46 -> 36,66]" + "originalRange": "[41,29 -> 45,24 EOL]", + "modifiedRange": "[36,46 -> 36,66 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/deletion/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/deletion/advanced.expected.diff.json index 77b3553a99c..c1c5787e10f 100644 --- a/src/vs/editor/test/node/diffing/fixtures/deletion/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/deletion/advanced.expected.diff.json @@ -14,7 +14,7 @@ "innerChanges": [ { "originalRange": "[1,1 -> 29,64]", - "modifiedRange": "[1,1 -> 1,1]" + "modifiedRange": "[1,1 -> 1,1 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json index b2155f7f625..7686d146b16 100644 --- a/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json @@ -33,8 +33,8 @@ "modifiedRange": "[156,163)", "innerChanges": [ { - "originalRange": "[159,1 -> 159,1]", - "modifiedRange": "[156,1 -> 163,1]" + "originalRange": "[159,1 -> 159,1 EOL]", + "modifiedRange": "[156,1 -> 163,1 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/difficult-move/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/difficult-move/legacy.expected.diff.json index 54e5d6610b0..6b0e1568dc0 100644 --- a/src/vs/editor/test/node/diffing/fixtures/difficult-move/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/difficult-move/legacy.expected.diff.json @@ -28,8 +28,8 @@ "modifiedRange": "[226,234)", "innerChanges": [ { - "originalRange": "[222,17 -> 222,19]", - "modifiedRange": "[226,17 -> 226,17]" + "originalRange": "[222,17 -> 222,19 EOL]", + "modifiedRange": "[226,17 -> 226,17 EOL]" }, { "originalRange": "[223,4 -> 223,28]", @@ -40,8 +40,8 @@ "modifiedRange": "[227,41 -> 227,48]" }, { - "originalRange": "[223,54 -> 223,65]", - "modifiedRange": "[227,53 -> 227,62]" + "originalRange": "[223,54 -> 223,65 EOL]", + "modifiedRange": "[227,53 -> 227,62 EOL]" }, { "originalRange": "[224,4 -> 224,21]", @@ -60,8 +60,8 @@ "modifiedRange": "[228,123 -> 228,152]" }, { - "originalRange": "[226,22 -> 226,25]", - "modifiedRange": "[228,166 -> 228,169]" + "originalRange": "[226,22 -> 226,25 EOL]", + "modifiedRange": "[228,166 -> 228,169 EOL]" }, { "originalRange": "[227,5 -> 227,30]", @@ -72,16 +72,16 @@ "modifiedRange": "[229,28 -> 229,32]" }, { - "originalRange": "[227,45 -> 227,93]", - "modifiedRange": "[229,35 -> 229,67]" + "originalRange": "[227,45 -> 227,93 EOL]", + "modifiedRange": "[229,35 -> 229,67 EOL]" }, { - "originalRange": "[228,5 -> 228,51]", - "modifiedRange": "[230,5 -> 230,30]" + "originalRange": "[228,5 -> 228,51 EOL]", + "modifiedRange": "[230,5 -> 230,30 EOL]" }, { - "originalRange": "[229,6 -> 230,6]", - "modifiedRange": "[231,6 -> 231,40]" + "originalRange": "[229,6 -> 230,6 EOL]", + "modifiedRange": "[231,6 -> 231,40 EOL]" }, { "originalRange": "[231,4 -> 232,42]", @@ -92,8 +92,8 @@ "modifiedRange": "[232,25 -> 233,32]" }, { - "originalRange": "[232,72 -> 233,4]", - "modifiedRange": "[233,35 -> 233,60]" + "originalRange": "[232,72 -> 233,4 EOL]", + "modifiedRange": "[233,35 -> 233,60 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/advanced.expected.diff.json index 4059da96da8..30bf62d99e9 100644 --- a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/advanced.expected.diff.json @@ -17,32 +17,32 @@ "modifiedRange": "[1,1 -> 1,1]" }, { - "originalRange": "[2,15 -> 2,15]", - "modifiedRange": "[1,15 -> 1,16]" + "originalRange": "[2,15 -> 2,15 EOL]", + "modifiedRange": "[1,15 -> 1,16 EOL]" }, { - "originalRange": "[3,15 -> 3,15]", - "modifiedRange": "[2,15 -> 2,16]" + "originalRange": "[3,15 -> 3,15 EOL]", + "modifiedRange": "[2,15 -> 2,16 EOL]" }, { - "originalRange": "[4,15 -> 4,15]", - "modifiedRange": "[3,15 -> 3,16]" + "originalRange": "[4,15 -> 4,15 EOL]", + "modifiedRange": "[3,15 -> 3,16 EOL]" }, { - "originalRange": "[5,15 -> 5,15]", - "modifiedRange": "[4,15 -> 5,1]" + "originalRange": "[5,15 -> 5,15 EOL]", + "modifiedRange": "[4,15 -> 5,1 EOL]" }, { - "originalRange": "[6,15 -> 6,15]", - "modifiedRange": "[6,15 -> 6,16]" + "originalRange": "[6,15 -> 6,15 EOL]", + "modifiedRange": "[6,15 -> 6,16 EOL]" }, { - "originalRange": "[7,15 -> 7,15]", - "modifiedRange": "[7,15 -> 7,16]" + "originalRange": "[7,15 -> 7,15 EOL]", + "modifiedRange": "[7,15 -> 7,16 EOL]" }, { - "originalRange": "[8,15 -> 8,15]", - "modifiedRange": "[8,15 -> 9,1]" + "originalRange": "[8,15 -> 8,15 EOL]", + "modifiedRange": "[8,15 -> 9,1 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/advanced.expected.diff.json index 84c7fde1881..25b767b28eb 100644 --- a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/advanced.expected.diff.json @@ -13,16 +13,16 @@ "modifiedRange": "[1,4)", "innerChanges": [ { - "originalRange": "[1,15 -> 1,16]", - "modifiedRange": "[1,15 -> 1,15]" + "originalRange": "[1,15 -> 1,16 EOL]", + "modifiedRange": "[1,15 -> 1,15 EOL]" }, { - "originalRange": "[2,15 -> 3,16]", - "modifiedRange": "[2,15 -> 2,15]" + "originalRange": "[2,15 -> 3,16 EOL]", + "modifiedRange": "[2,15 -> 2,15 EOL]" }, { - "originalRange": "[4,15 -> 4,16]", - "modifiedRange": "[3,15 -> 3,15]" + "originalRange": "[4,15 -> 4,16 EOL]", + "modifiedRange": "[3,15 -> 3,15 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/legacy.expected.diff.json index 84c7fde1881..25b767b28eb 100644 --- a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/legacy.expected.diff.json @@ -13,16 +13,16 @@ "modifiedRange": "[1,4)", "innerChanges": [ { - "originalRange": "[1,15 -> 1,16]", - "modifiedRange": "[1,15 -> 1,15]" + "originalRange": "[1,15 -> 1,16 EOL]", + "modifiedRange": "[1,15 -> 1,15 EOL]" }, { - "originalRange": "[2,15 -> 3,16]", - "modifiedRange": "[2,15 -> 2,15]" + "originalRange": "[2,15 -> 3,16 EOL]", + "modifiedRange": "[2,15 -> 2,15 EOL]" }, { - "originalRange": "[4,15 -> 4,16]", - "modifiedRange": "[3,15 -> 3,15]" + "originalRange": "[4,15 -> 4,16 EOL]", + "modifiedRange": "[3,15 -> 3,15 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json index 8a805f72ae1..131a397b9a5 100644 --- a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-bug/advanced.expected.diff.json @@ -39,8 +39,8 @@ "modifiedRange": "[22,42 -> 22,47]" }, { - "originalRange": "[27,9 -> 33,1]", - "modifiedRange": "[22,47 -> 23,1]" + "originalRange": "[27,9 -> 33,1 EOL]", + "modifiedRange": "[22,47 -> 23,1 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json index 6bfa1f36e92..bdaa293acd4 100644 --- a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/advanced.expected.diff.json @@ -33,8 +33,8 @@ "modifiedRange": "[745,4 -> 745,8]" }, { - "originalRange": "[744,59 -> 745,6]", - "modifiedRange": "[745,56 -> 745,59]" + "originalRange": "[744,59 -> 745,6 EOL]", + "modifiedRange": "[745,56 -> 745,59 EOL]" }, { "originalRange": "[746,24 -> 746,25]", diff --git a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json index e48b9d56df1..924e83b900e 100644 --- a/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/invalid-diff-trimws/legacy.expected.diff.json @@ -33,16 +33,16 @@ "modifiedRange": "[745,4 -> 745,8]" }, { - "originalRange": "[744,59 -> 745,6]", - "modifiedRange": "[745,56 -> 745,59]" + "originalRange": "[744,59 -> 745,6 EOL]", + "modifiedRange": "[745,56 -> 745,59 EOL]" }, { "originalRange": "[746,24 -> 746,25]", "modifiedRange": "[746,26 -> 746,26]" }, { - "originalRange": "[746,37 -> 746,37]", - "modifiedRange": "[747,4 -> 750,20]" + "originalRange": "[746,37 -> 746,37 EOL]", + "modifiedRange": "[747,4 -> 750,20 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/advanced.expected.diff.json index a24c28bdcfa..1bff77e1739 100644 --- a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/advanced.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[1,2)", "innerChanges": [ { - "originalRange": "[1,20 -> 1,21]", - "modifiedRange": "[1,20 -> 1,20]" + "originalRange": "[1,20 -> 1,21 EOL]", + "modifiedRange": "[1,20 -> 1,20 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/legacy.expected.diff.json index a24c28bdcfa..1bff77e1739 100644 --- a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/legacy.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[1,2)", "innerChanges": [ { - "originalRange": "[1,20 -> 1,21]", - "modifiedRange": "[1,20 -> 1,20]" + "originalRange": "[1,20 -> 1,21 EOL]", + "modifiedRange": "[1,20 -> 1,20 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/method-splitting/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/method-splitting/legacy.expected.diff.json index e1f4edb8a10..a5e2847e3b9 100644 --- a/src/vs/editor/test/node/diffing/fixtures/method-splitting/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/method-splitting/legacy.expected.diff.json @@ -57,8 +57,8 @@ "modifiedRange": "[7,148 -> 7,170]" }, { - "originalRange": "[8,62 -> 9,3]", - "modifiedRange": "[7,181 -> 7,183]" + "originalRange": "[8,62 -> 9,3 EOL]", + "modifiedRange": "[7,181 -> 7,183 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json index 73f6bfc728f..ad809ac7fe0 100644 --- a/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[24,24)", "innerChanges": [ { - "originalRange": "[24,1 -> 28,1]", - "modifiedRange": "[24,1 -> 24,1]" + "originalRange": "[24,1 -> 28,1 EOL]", + "modifiedRange": "[24,1 -> 24,1 EOL]" } ] }, @@ -23,8 +23,8 @@ "modifiedRange": "[70,74)", "innerChanges": [ { - "originalRange": "[74,1 -> 74,1]", - "modifiedRange": "[70,1 -> 74,1]" + "originalRange": "[74,1 -> 74,1 EOL]", + "modifiedRange": "[70,1 -> 74,1 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json index 20190a83798..36bbcbebac9 100644 --- a/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json @@ -21,8 +21,8 @@ "modifiedRange": "[53,6 -> 53,45]" }, { - "originalRange": "[52,77 -> 56,1]", - "modifiedRange": "[53,98 -> 66,1]" + "originalRange": "[52,77 -> 56,1 EOL]", + "modifiedRange": "[53,98 -> 66,1 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/random-match-2/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/random-match-2/legacy.expected.diff.json index 7d0d8ed1629..b9a24abd3bd 100644 --- a/src/vs/editor/test/node/diffing/fixtures/random-match-2/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/random-match-2/legacy.expected.diff.json @@ -37,8 +37,8 @@ "modifiedRange": "[3,78 -> 3,82]" }, { - "originalRange": "[5,123 -> 6,3]", - "modifiedRange": "[3,120 -> 3,120]" + "originalRange": "[5,123 -> 6,3 EOL]", + "modifiedRange": "[3,120 -> 3,120 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/random-match-3/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/random-match-3/advanced.expected.diff.json index 869c81350b7..f690b15dd1d 100644 --- a/src/vs/editor/test/node/diffing/fixtures/random-match-3/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/random-match-3/advanced.expected.diff.json @@ -17,8 +17,8 @@ "modifiedRange": "[1,6 -> 2,18]" }, { - "originalRange": "[2,1 -> 2,1]", - "modifiedRange": "[3,1 -> 4,1]" + "originalRange": "[2,1 -> 2,1 EOL]", + "modifiedRange": "[3,1 -> 4,1 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/random-match-3/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/random-match-3/legacy.expected.diff.json index a1f2e8db4a5..328fc5656a4 100644 --- a/src/vs/editor/test/node/diffing/fixtures/random-match-3/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/random-match-3/legacy.expected.diff.json @@ -21,8 +21,8 @@ "modifiedRange": "[2,5 -> 2,18]" }, { - "originalRange": "[1,98 -> 1,98]", - "modifiedRange": "[2,71 -> 3,39]" + "originalRange": "[1,98 -> 1,98 EOL]", + "modifiedRange": "[2,71 -> 3,39 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/subword/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/subword/advanced.expected.diff.json index c8a5000a54c..d22795d014f 100644 --- a/src/vs/editor/test/node/diffing/fixtures/subword/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/subword/advanced.expected.diff.json @@ -23,8 +23,8 @@ "modifiedRange": "[6,7)", "innerChanges": [ { - "originalRange": "[5,92 -> 5,92]", - "modifiedRange": "[5,92 -> 6,31]" + "originalRange": "[5,92 -> 5,92 EOL]", + "modifiedRange": "[5,92 -> 6,31 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/trivial/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/trivial/advanced.expected.diff.json index b88bd08c021..a92226e91aa 100644 --- a/src/vs/editor/test/node/diffing/fixtures/trivial/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/trivial/advanced.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[1,2)", "innerChanges": [ { - "originalRange": "[1,1 -> 1,1]", - "modifiedRange": "[1,1 -> 1,2]" + "originalRange": "[1,1 -> 1,1 EOL]", + "modifiedRange": "[1,1 -> 1,2 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/advanced.expected.diff.json index a1f9f55cf07..59ce8640941 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/advanced.expected.diff.json @@ -21,16 +21,16 @@ "modifiedRange": "[4,17 -> 4,28]" }, { - "originalRange": "[2,39 -> 2,40]", - "modifiedRange": "[4,50 -> 4,50]" + "originalRange": "[2,39 -> 2,40 EOL]", + "modifiedRange": "[4,50 -> 4,50 EOL]" }, { "originalRange": "[3,5 -> 3,9]", "modifiedRange": "[5,5 -> 5,5]" }, { - "originalRange": "[4,1 -> 5,1]", - "modifiedRange": "[6,1 -> 6,1]" + "originalRange": "[4,1 -> 5,1 EOL]", + "modifiedRange": "[6,1 -> 6,1 EOL]" } ] }, @@ -39,8 +39,8 @@ "modifiedRange": "[9,10)", "innerChanges": [ { - "originalRange": "[8,35 -> 8,35]", - "modifiedRange": "[9,35 -> 9,36]" + "originalRange": "[8,35 -> 8,35 EOL]", + "modifiedRange": "[9,35 -> 9,36 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/legacy.expected.diff.json index 3411d7fbc1a..ee4907cc31f 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-advanced-bug/legacy.expected.diff.json @@ -21,16 +21,16 @@ "modifiedRange": "[4,20 -> 4,31]" }, { - "originalRange": "[2,39 -> 2,40]", - "modifiedRange": "[4,50 -> 4,50]" + "originalRange": "[2,39 -> 2,40 EOL]", + "modifiedRange": "[4,50 -> 4,50 EOL]" }, { "originalRange": "[3,5 -> 3,9]", "modifiedRange": "[5,5 -> 5,5]" }, { - "originalRange": "[3,57 -> 4,36]", - "modifiedRange": "[5,53 -> 5,53]" + "originalRange": "[3,57 -> 4,36 EOL]", + "modifiedRange": "[5,53 -> 5,53 EOL]" } ] }, @@ -39,8 +39,8 @@ "modifiedRange": "[9,10)", "innerChanges": [ { - "originalRange": "[8,35 -> 8,35]", - "modifiedRange": "[9,35 -> 9,36]" + "originalRange": "[8,35 -> 8,35 EOL]", + "modifiedRange": "[9,35 -> 9,36 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json index 0b00ea98beb..7e9d27ae2e0 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json @@ -37,8 +37,8 @@ "modifiedRange": "[6,7)", "innerChanges": [ { - "originalRange": "[6,85 -> 9,1]", - "modifiedRange": "[6,85 -> 6,140]" + "originalRange": "[6,85 -> 9,1 EOL]", + "modifiedRange": "[6,85 -> 6,140 EOL]" } ] }, @@ -47,8 +47,8 @@ "modifiedRange": "[8,12)", "innerChanges": [ { - "originalRange": "[11,10 -> 21,1]", - "modifiedRange": "[8,10 -> 12,1]" + "originalRange": "[11,10 -> 21,1 EOL]", + "modifiedRange": "[8,10 -> 12,1 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-class/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-class/legacy.expected.diff.json index bfb1d8aa5dc..ea38da72c7a 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-class/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-class/legacy.expected.diff.json @@ -36,8 +36,8 @@ "modifiedRange": "[6,85 -> 6,123]" }, { - "originalRange": "[8,57 -> 9,1]", - "modifiedRange": "[6,127 -> 6,140]" + "originalRange": "[8,57 -> 9,1 EOL]", + "modifiedRange": "[6,127 -> 6,140 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json index 3835a5ceb3c..dea51256ca6 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/advanced.expected.diff.json @@ -65,8 +65,8 @@ "modifiedRange": "[21,18 -> 21,43]" }, { - "originalRange": "[17,71 -> 24,1]", - "modifiedRange": "[21,74 -> 22,1]" + "originalRange": "[17,71 -> 24,1 EOL]", + "modifiedRange": "[21,74 -> 22,1 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/legacy.expected.diff.json index f011be41a70..be3f0413bf3 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/legacy.expected.diff.json @@ -71,8 +71,8 @@ "modifiedRange": "[16,40 -> 16,47]" }, { - "originalRange": "[14,196 -> 14,211]", - "modifiedRange": "[16,51 -> 17,1]" + "originalRange": "[14,196 -> 14,211 EOL]", + "modifiedRange": "[16,51 -> 17,1 EOL]" } ] }, @@ -101,8 +101,8 @@ "modifiedRange": "[21,39 -> 21,43]" }, { - "originalRange": "[17,71 -> 17,72]", - "modifiedRange": "[21,74 -> 22,1]" + "originalRange": "[17,71 -> 17,72 EOL]", + "modifiedRange": "[21,74 -> 22,1 EOL]" }, { "originalRange": "[18,3 -> 19,26]", @@ -152,8 +152,8 @@ "modifiedRange": "[34,12 -> 36,16]" }, { - "originalRange": "[30,30 -> 30,31]", - "modifiedRange": "[36,20 -> 37,9]" + "originalRange": "[30,30 -> 30,31 EOL]", + "modifiedRange": "[36,20 -> 37,9 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/advanced.expected.diff.json index 647fee613e5..e4262043f06 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/advanced.expected.diff.json @@ -31,8 +31,8 @@ "modifiedRange": "[19,26)", "innerChanges": [ { - "originalRange": "[8,2 -> 8,2]", - "modifiedRange": "[18,2 -> 25,2]" + "originalRange": "[8,2 -> 8,2 EOL]", + "modifiedRange": "[18,2 -> 25,2 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/advanced.expected.diff.json index 93657f71909..5000ede1044 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/advanced.expected.diff.json @@ -27,8 +27,8 @@ "modifiedRange": "[7,1 -> 7,1]" }, { - "originalRange": "[7,9 -> 7,15]", - "modifiedRange": "[7,4 -> 7,4]" + "originalRange": "[7,9 -> 7,15 EOL]", + "modifiedRange": "[7,4 -> 7,4 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/legacy.expected.diff.json index 2c2c64d2b9c..1e160d871d4 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/legacy.expected.diff.json @@ -27,8 +27,8 @@ "modifiedRange": "[7,1 -> 7,1]" }, { - "originalRange": "[7,9 -> 7,15]", - "modifiedRange": "[7,4 -> 7,4]" + "originalRange": "[7,9 -> 7,15 EOL]", + "modifiedRange": "[7,4 -> 7,4 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-methods/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-methods/legacy.expected.diff.json index 5034a4b336a..4f1a7a5647d 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-methods/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-methods/legacy.expected.diff.json @@ -21,8 +21,8 @@ "modifiedRange": "[3,12 -> 3,20]" }, { - "originalRange": "[3,91 -> 3,91]", - "modifiedRange": "[3,102 -> 4,73]" + "originalRange": "[3,91 -> 3,91 EOL]", + "modifiedRange": "[3,102 -> 4,73 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/legacy.expected.diff.json index aa65f823a85..3f20554ee6e 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/legacy.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[9,15)", "innerChanges": [ { - "originalRange": "[9,124 -> 9,124]", - "modifiedRange": "[9,124 -> 14,143]" + "originalRange": "[9,124 -> 9,124 EOL]", + "modifiedRange": "[9,124 -> 14,143 EOL]" } ] } diff --git a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json index 5742291665d..23feeaa2119 100644 --- a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/advanced.expected.diff.json @@ -83,8 +83,8 @@ "modifiedRange": "[43,44)", "innerChanges": [ { - "originalRange": "[43,5 -> 43,16]", - "modifiedRange": "[43,5 -> 43,11]" + "originalRange": "[43,5 -> 43,16 EOL]", + "modifiedRange": "[43,5 -> 43,11 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/legacy.expected.diff.json index 8f9a20db404..5cf3d49bc8f 100644 --- a/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/word-shared-letters/legacy.expected.diff.json @@ -95,8 +95,8 @@ "modifiedRange": "[43,8 -> 43,8]" }, { - "originalRange": "[43,14 -> 43,16]", - "modifiedRange": "[43,11 -> 43,11]" + "originalRange": "[43,14 -> 43,16 EOL]", + "modifiedRange": "[43,11 -> 43,11 EOL]" } ] }, diff --git a/src/vs/editor/test/node/diffing/fixtures/ws-alignment/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ws-alignment/legacy.expected.diff.json index c248859e098..0fff59a0214 100644 --- a/src/vs/editor/test/node/diffing/fixtures/ws-alignment/legacy.expected.diff.json +++ b/src/vs/editor/test/node/diffing/fixtures/ws-alignment/legacy.expected.diff.json @@ -27,28 +27,28 @@ "modifiedRange": "[7,5 -> 8,5]" }, { - "originalRange": "[7,14 -> 7,43]", - "modifiedRange": "[8,8 -> 11,140]" + "originalRange": "[7,14 -> 7,43 EOL]", + "modifiedRange": "[8,8 -> 11,140 EOL]" }, { "originalRange": "[8,5 -> 8,6]", "modifiedRange": "[12,5 -> 12,25]" }, { - "originalRange": "[8,9 -> 9,12]", - "modifiedRange": "[12,28 -> 12,131]" + "originalRange": "[8,9 -> 9,12 EOL]", + "modifiedRange": "[12,28 -> 12,131 EOL]" }, { "originalRange": "[10,7 -> 10,22]", "modifiedRange": "[13,7 -> 13,17]" }, { - "originalRange": "[10,30 -> 10,48]", - "modifiedRange": "[13,25 -> 14,8]" + "originalRange": "[10,30 -> 10,48 EOL]", + "modifiedRange": "[13,25 -> 14,8 EOL]" }, { - "originalRange": "[11,6 -> 11,13]", - "modifiedRange": "[15,6 -> 15,7]" + "originalRange": "[11,6 -> 11,13 EOL]", + "modifiedRange": "[15,6 -> 15,7 EOL]" }, { "originalRange": "[12,5 -> 12,17]", From 3503252879e022b518b8ac45cd636cd336d30e4b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Sep 2023 14:40:51 +0200 Subject: [PATCH 128/133] accessibility improvements (#193571) --- .../base/browser/ui/icons/iconSelectBox.css | 5 + src/vs/base/browser/ui/icons/iconSelectBox.ts | 108 ++++++++++++------ src/vs/workbench/browser/iconSelectBox.ts | 34 ++++-- 3 files changed, 105 insertions(+), 42 deletions(-) diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.css b/src/vs/base/browser/ui/icons/iconSelectBox.css index a5a9b767478..4e2278d04bc 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.css +++ b/src/vs/base/browser/ui/icons/iconSelectBox.css @@ -9,6 +9,7 @@ .icon-select-box .icon-select-icons-container { height: 100%; + outline: 0 !important; } .icon-select-box .icon-select-icons-container > .icon-container { @@ -31,3 +32,7 @@ padding: 10px; opacity: .8; } + +.icon-select-box .icon-select-id-container .icon-select-id-label .highlight { + color: var(--vscode-list-highlightForeground); +} diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index afc47f018d0..c592c37d3f7 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -14,27 +14,37 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; import { IMatch } from 'vs/base/common/filters'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; export interface IIconSelectBoxOptions { readonly icons: ThemeIcon[]; readonly inputBoxStyles: IInputBoxStyles; } +interface IRenderedIconItem { + readonly icon: ThemeIcon; + readonly element: HTMLElement; + readonly highlightMatches?: IMatch[]; +} + export class IconSelectBox extends Disposable { + private static InstanceCount = 0; + readonly domId = `icon_select_box_id_${++IconSelectBox.InstanceCount}`; + readonly domNode: HTMLElement; private _onDidSelect = this._register(new Emitter()); readonly onDidSelect = this._onDidSelect.event; - private renderedIcons: [ThemeIcon, HTMLElement][] = []; + private renderedIcons: IRenderedIconItem[] = []; private focusedItemIndex: number = 0; private numberOfElementsPerRow: number = 1; - private inputBox: InputBox | undefined; + protected inputBox: InputBox | undefined; private scrollableElement: DomScrollableElement | undefined; - private iconIdElement: HTMLElement | undefined; + private iconIdElement: HighlightedLabel | undefined; private readonly iconContainerWidth = 36; private readonly iconContainerHeight = 32; @@ -59,47 +69,61 @@ export class IconSelectBox extends Disposable { inputBoxStyles: this.options.inputBoxStyles, })); - const iconsContainer = dom.$('.icon-select-icons-container'); + const iconsContainer = dom.$('.icon-select-icons-container', { id: `${this.domId}_icons` }); iconsContainer.style.paddingRight = '10px'; + iconsContainer.role = 'listbox'; + iconsContainer.tabIndex = 0; this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, { useShadows: false, horizontal: ScrollbarVisibility.Hidden, })); dom.append(iconSelectBoxContainer, this.scrollableElement.getDomNode()); - this.iconIdElement = dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label')); + this.iconIdElement = new HighlightedLabel(dom.append(dom.append(iconSelectBoxContainer, dom.$('.icon-select-id-container')), dom.$('.icon-select-id-label'))); const iconsDisposables = disposables.add(new MutableDisposable()); - iconsDisposables.value = this.renderIcons(this.options.icons, iconsContainer); + iconsDisposables.value = this.renderIcons(this.options.icons, [], iconsContainer); this.scrollableElement.scanDomNode(); disposables.add(this.inputBox.onDidChange(value => { - const icons = this.options.icons.filter(icon => { - return this.matchesContiguous(value, icon.id); - }); - iconsDisposables.value = this.renderIcons(icons, iconsContainer); + const icons = [], matches = []; + for (const icon of this.options.icons) { + const match = this.matchesContiguous(value, icon.id); + if (match) { + icons.push(icon); + matches.push(match); + } + } + iconsDisposables.value = this.renderIcons(icons, matches, iconsContainer); this.scrollableElement?.scanDomNode(); })); + this.inputBox.inputElement.role = 'combobox'; + this.inputBox.inputElement.ariaHasPopup = 'menu'; + this.inputBox.inputElement.ariaAutoComplete = 'list'; + this.inputBox.inputElement.ariaExpanded = 'true'; + this.inputBox.inputElement.setAttribute('aria-controls', iconsContainer.id); + return disposables; } - private renderIcons(icons: ThemeIcon[], container: HTMLElement): IDisposable { + private renderIcons(icons: ThemeIcon[], matches: IMatch[][], container: HTMLElement): IDisposable { const disposables = new DisposableStore(); dom.clearNode(container); - const focusedIcon = this.renderedIcons[this.focusedItemIndex]?.[0]; + const focusedIcon = this.renderedIcons[this.focusedItemIndex]?.icon; let focusedIconIndex = 0; - const renderedIcons: [ThemeIcon, HTMLElement][] = []; + const renderedIcons: IRenderedIconItem[] = []; if (icons.length) { for (let index = 0; index < icons.length; index++) { const icon = icons[index]; - const iconContainer = dom.append(container, dom.$('.icon-container')); + const iconContainer = dom.append(container, dom.$('.icon-container', { id: `${this.domId}_icons_${index}` })); iconContainer.style.width = `${this.iconContainerWidth}px`; iconContainer.style.height = `${this.iconContainerHeight}px`; - iconContainer.tabIndex = -1; - iconContainer.role = 'button'; iconContainer.title = icon.id; + iconContainer.role = 'button'; + iconContainer.setAttribute('aria-setsize', `${icons.length}`); + iconContainer.setAttribute('aria-posinset', `${index + 1}`); dom.append(iconContainer, dom.$(ThemeIcon.asCSSSelector(icon))); - renderedIcons.push([icon, iconContainer]); + renderedIcons.push({ icon, element: iconContainer, highlightMatches: matches[index] }); disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.CLICK, (e: MouseEvent) => { e.stopPropagation(); @@ -129,17 +153,30 @@ export class IconSelectBox extends Disposable { private focusIcon(index: number): void { const existing = this.renderedIcons[this.focusedItemIndex]; if (existing) { - existing[1].classList.remove('focused'); + existing.element.classList.remove('focused'); } this.focusedItemIndex = index; - const icon = this.renderedIcons[index]?.[1]; - if (icon) { - icon.classList.add('focused'); + const renderedItem = this.renderedIcons[index]; + + if (renderedItem) { + renderedItem.element.classList.add('focused'); + } + + if (this.inputBox) { + if (renderedItem) { + this.inputBox.inputElement.setAttribute('aria-activedescendant', renderedItem.element.id); + } else { + this.inputBox.inputElement.removeAttribute('aria-activedescendant'); + } } if (this.iconIdElement) { - this.iconIdElement.textContent = this.renderedIcons[index]?.[0].id; + if (renderedItem) { + this.iconIdElement.set(renderedItem.icon.id, renderedItem.highlightMatches); + } else { + this.iconIdElement.set(''); + } } this.reveal(index); @@ -152,16 +189,16 @@ export class IconSelectBox extends Disposable { if (index < 0 || index >= this.renderedIcons.length) { return; } - const icon = this.renderedIcons[index][1]; - if (!icon) { + const element = this.renderedIcons[index].element; + if (!element) { return; } const { height } = this.scrollableElement.getScrollDimensions(); const { scrollTop } = this.scrollableElement.getScrollPosition(); - if (icon.offsetTop + this.iconContainerHeight > scrollTop + height) { - this.scrollableElement.setScrollPosition({ scrollTop: icon.offsetTop + this.iconContainerHeight - height }); - } else if (icon.offsetTop < scrollTop) { - this.scrollableElement.setScrollPosition({ scrollTop: icon.offsetTop }); + if (element.offsetTop + this.iconContainerHeight > scrollTop + height) { + this.scrollableElement.setScrollPosition({ scrollTop: element.offsetTop + this.iconContainerHeight - height }); + } else if (element.offsetTop < scrollTop) { + this.scrollableElement.setScrollPosition({ scrollTop: element.offsetTop }); } } @@ -185,8 +222,8 @@ export class IconSelectBox extends Disposable { const extraSpace = iconsContainerWidth % this.iconContainerWidth; const margin = Math.floor(extraSpace / this.numberOfElementsPerRow); - for (const [, icon] of this.renderedIcons) { - icon.style.marginRight = `${margin}px`; + for (const { element } of this.renderedIcons) { + element.style.marginRight = `${margin}px`; } if (this.scrollableElement) { @@ -204,7 +241,7 @@ export class IconSelectBox extends Disposable { throw new Error(`Invalid index ${index}`); } this.focusIcon(index); - this._onDidSelect.fire(this.renderedIcons[index][0]); + this._onDidSelect.fire(this.renderedIcons[index].icon); } focus(): void { @@ -224,6 +261,7 @@ export class IconSelectBox extends Disposable { let nextRowIndex = this.focusedItemIndex + this.numberOfElementsPerRow; if (nextRowIndex >= this.renderedIcons.length) { nextRowIndex = (nextRowIndex + 1) % this.numberOfElementsPerRow; + nextRowIndex = nextRowIndex >= this.renderedIcons.length ? 0 : nextRowIndex; } this.focusIcon(nextRowIndex); } @@ -233,13 +271,17 @@ export class IconSelectBox extends Disposable { if (previousRowIndex < 0) { const numberOfRows = Math.floor(this.renderedIcons.length / this.numberOfElementsPerRow); previousRowIndex = this.focusedItemIndex + (this.numberOfElementsPerRow * numberOfRows) - 1; - previousRowIndex = previousRowIndex >= this.renderedIcons.length ? previousRowIndex - this.numberOfElementsPerRow : previousRowIndex; + previousRowIndex = previousRowIndex < 0 + ? this.renderedIcons.length - 1 + : previousRowIndex >= this.renderedIcons.length + ? previousRowIndex - this.numberOfElementsPerRow + : previousRowIndex; } this.focusIcon(previousRowIndex); } getFocusedIcon(): ThemeIcon { - return this.renderedIcons[this.focusedItemIndex][0]; + return this.renderedIcons[this.focusedItemIndex].icon; } } diff --git a/src/vs/workbench/browser/iconSelectBox.ts b/src/vs/workbench/browser/iconSelectBox.ts index 0e38a01d30f..e74f52687cd 100644 --- a/src/vs/workbench/browser/iconSelectBox.ts +++ b/src/vs/workbench/browser/iconSelectBox.ts @@ -5,10 +5,13 @@ import { IIconSelectBoxOptions, IconSelectBox } from 'vs/base/browser/ui/icons/iconSelectBox'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import * as dom from 'vs/base/browser/dom'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export const WorkbenchIconSelectBoxFocusContextKey = new RawContextKey('iconSelectBoxFocus', true); +export const WorkbenchIconSelectBoxInputFocusContextKey = new RawContextKey('iconSelectBoxInputFocus', true); +export const WorkbenchIconSelectBoxInputEmptyContextKey = new RawContextKey('iconSelectBoxInputEmpty', true); export class WorkbenchIconSelectBox extends IconSelectBox { @@ -17,12 +20,25 @@ export class WorkbenchIconSelectBox extends IconSelectBox { return WorkbenchIconSelectBox.focusedWidget; } + private readonly contextKeyService: IContextKeyService; + private readonly inputFocusContextKey: IContextKey; + private readonly inputEmptyContextKey: IContextKey; + constructor( options: IIconSelectBoxOptions, @IContextKeyService contextKeyService: IContextKeyService ) { super(options); - WorkbenchIconSelectBoxFocusContextKey.bindTo(this._register(contextKeyService.createScoped(this.domNode))); + this.contextKeyService = this._register(contextKeyService.createScoped(this.domNode)); + WorkbenchIconSelectBoxFocusContextKey.bindTo(this.contextKeyService); + this.inputFocusContextKey = WorkbenchIconSelectBoxInputFocusContextKey.bindTo(this.contextKeyService); + this.inputEmptyContextKey = WorkbenchIconSelectBoxInputEmptyContextKey.bindTo(this.contextKeyService); + if (this.inputBox) { + const focusTracker = this._register(dom.trackFocus(this.inputBox.inputElement)); + this._register(focusTracker.onDidFocus(() => this.inputFocusContextKey.set(true))); + this._register(focusTracker.onDidBlur(() => this.inputFocusContextKey.set(false))); + this._register(this.inputBox.onDidChange(() => this.inputEmptyContextKey.set(this.inputBox?.value.length === 0))); + } } override focus(): void { @@ -37,7 +53,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchIconSelectBoxFocusContextKey, primary: KeyCode.UpArrow, - handler: (accessor, arg2) => { + handler: () => { const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); if (selectBox) { selectBox.focusPreviousRow(); @@ -50,7 +66,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchIconSelectBoxFocusContextKey, primary: KeyCode.DownArrow, - handler: (accessor, arg2) => { + handler: () => { const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); if (selectBox) { selectBox.focusNextRow(); @@ -61,9 +77,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'iconSelectBox.focusNext', weight: KeybindingWeight.WorkbenchContrib, - when: WorkbenchIconSelectBoxFocusContextKey, + when: ContextKeyExpr.and(WorkbenchIconSelectBoxFocusContextKey, ContextKeyExpr.or(WorkbenchIconSelectBoxInputEmptyContextKey, WorkbenchIconSelectBoxInputFocusContextKey.toNegated())), primary: KeyCode.RightArrow, - handler: (accessor, arg2) => { + handler: () => { const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); if (selectBox) { selectBox.focusNext(); @@ -74,9 +90,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'iconSelectBox.focusPrevious', weight: KeybindingWeight.WorkbenchContrib, - when: WorkbenchIconSelectBoxFocusContextKey, + when: ContextKeyExpr.and(WorkbenchIconSelectBoxFocusContextKey, ContextKeyExpr.or(WorkbenchIconSelectBoxInputEmptyContextKey, WorkbenchIconSelectBoxInputFocusContextKey.toNegated())), primary: KeyCode.LeftArrow, - handler: (accessor, arg2) => { + handler: () => { const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); if (selectBox) { selectBox.focusPrevious(); @@ -89,7 +105,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchIconSelectBoxFocusContextKey, primary: KeyCode.Enter, - handler: (accessor, arg2) => { + handler: () => { const selectBox = WorkbenchIconSelectBox.getFocusedWidget(); if (selectBox) { selectBox.setSelection(selectBox.getFocus()[0]); From 5528f93f38672ab788c5ce78251926dce3a59a94 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 20 Sep 2023 14:47:40 +0200 Subject: [PATCH 129/133] Top padding is wrong on comment view zone (#193569) Fixes #193140 Co-authored-by: Martin Aeschlimann --- .../workbench/contrib/comments/browser/commentThreadWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index aff152a1a9c..0b4723b0c50 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -200,7 +200,7 @@ export class CommentThreadWidget extends } display(lineHeight: number) { - const headHeight = Math.ceil(lineHeight * 1.2); + const headHeight = Math.max(23, Math.ceil(lineHeight * 1.2)); // 23 is the value of `Math.ceil(lineHeight * 1.2)` with the default editor font size this._header.updateHeight(headHeight); this._body.display(); From ceda6cc4856841f1550a60327a3eaf3a1d0c306a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:57:14 +0200 Subject: [PATCH 130/133] SCM - set of fixes for the SCM Sync View (#193567) --- extensions/git/src/historyProvider.ts | 4 +- .../contrib/scm/browser/media/scm.css | 4 ++ .../contrib/scm/browser/scmSyncViewPane.ts | 39 ++++++++++++------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 3a5541152ae..0b3f6e1075e 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon } from 'vscode'; +import { Disposable, Event, EventEmitter, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, l10n } from 'vscode'; import { Repository } from './repository'; import { IDisposable } from './util'; import { toGitUri } from './uri'; @@ -133,7 +133,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos private async getSummaryHistoryItem(ref1: string, ref2: string): Promise { const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2); - return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: 'Changes', description: diffShortStat }; + return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), description: diffShortStat }; } dispose(): void { diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index f937d75dd31..f92c54db113 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -132,6 +132,10 @@ .scm-view .monaco-list-row .history-item-group .monaco-icon-label > .monaco-icon-label-container { display: flex; } +.scm-view .monaco-list-row .history-item-group .monaco-icon-label > .monaco-icon-label-container .monaco-icon-description-container { + overflow: hidden; + text-overflow: ellipsis; +} .scm-sync-view .monaco-list-row .monaco-icon-label .icon-container .scm-sync-view .monaco-list-row .monaco-icon-label .icon-container { diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index 26df810e221..5e0db441a80 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -40,6 +40,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { basename, dirname } from 'vs/base/common/resources'; import { ILabelService } from 'vs/platform/label/common/label'; import { stripIcons } from 'vs/base/common/iconLabels'; +import { FileKind } from 'vs/platform/files/common/files'; type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement; @@ -63,7 +64,7 @@ function toDiffEditorArguments(uri: URI, originalUri: URI, modifiedUri: URI): un const originalShortRef = originalQuery.ref.substring(0, 8).concat(originalQuery.ref.endsWith('^') ? '^' : ''); const modifiedShortRef = modifiedQuery.ref.substring(0, 8).concat(modifiedQuery.ref.endsWith('^') ? '^' : ''); - return [originalUri, modifiedUri, `${basename} (${originalShortRef}) ↔ ${basename} (${modifiedShortRef})`]; + return [originalUri, modifiedUri, `${basename} (${originalShortRef}) ↔ ${basename} (${modifiedShortRef})`, null]; } function getSCMResourceId(element: TreeElement): string { @@ -173,8 +174,8 @@ interface HistoryItemTemplate { readonly iconContainer: HTMLElement; // readonly avatarImg: HTMLImageElement; readonly iconLabel: IconLabel; - readonly timestampContainer: HTMLElement; - readonly timestamp: HTMLSpanElement; + // readonly timestampContainer: HTMLElement; + // readonly timestamp: HTMLSpanElement; readonly disposables: IDisposable; } @@ -193,10 +194,10 @@ class HistoryItemRenderer implements ITreeRenderer, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { @@ -254,6 +255,7 @@ class HistoryItemChangeRenderer implements ITreeRenderer, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void { templateData.fileLabel.setFile(node.element.uri, { fileDecorations: { colors: false, badges: true }, + fileKind: FileKind.FILE, hidePath: false, }); } @@ -372,6 +374,7 @@ class SCMSyncViewPaneTreeSorter implements ITreeSorter { export class SCMSyncViewPane extends ViewPane { private listLabels!: ResourceLabels; + private treeContainer!: HTMLElement; private _tree!: WorkbenchAsyncDataTree; private _viewModel!: SCMSyncPaneViewModel; @@ -398,7 +401,7 @@ export class SCMSyncViewPane extends ViewPane { protected override renderBody(container: HTMLElement): void { super.renderBody(container); - const treeContainer = append(container, $('.scm-view.scm-sync-view.file-icon-themable-tree')); + this.treeContainer = append(container, $('.scm-view.scm-sync-view')); this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.listLabels); @@ -406,7 +409,7 @@ export class SCMSyncViewPane extends ViewPane { this._tree = this.instantiationService.createInstance( WorkbenchAsyncDataTree, 'SCM Sync View', - treeContainer, + this.treeContainer, new ListDelegate(), [ this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), @@ -427,6 +430,12 @@ export class SCMSyncViewPane extends ViewPane { this._register(this._tree.onDidOpen(this.onDidOpen, this)); this._viewModel = this.instantiationService.createInstance(SCMSyncPaneViewModel, this._tree); + + this.treeContainer.classList.add('file-icon-themable-tree'); + this.treeContainer.classList.add('show-file-icons'); + + this.updateIndentStyles(this.themeService.getFileIconTheme()); + this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this)); } protected override layoutBody(height: number, width: number): void { @@ -439,11 +448,15 @@ export class SCMSyncViewPane extends ViewPane { return; } else if (isSCMHistoryItemChangeTreeElement(e.element)) { if (e.element.originalUri && e.element.modifiedUri) { - await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri)); + await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri), e); } } } + private updateIndentStyles(theme: any): void { + this.treeContainer.classList.toggle('align-icons-and-twisties', theme.hasFileIcons || (theme.hasFileIcons && !theme.hasFolderIcons)); + } + override dispose(): void { this.disposables.dispose(); super.dispose(); @@ -590,8 +603,8 @@ class SCMSyncDataSource implements IAsyncDataSource { if (historyItemGroupBase) { children.push({ id: historyItemGroupBase.id, - label: localize('incoming', "$(cloud-download) Incoming Changes"), - description: historyItemGroupBase.label, + label: `$(cloud-download) ${historyItemGroupBase.label}`, + description: localize('incoming', "Incoming Changes"), ancestor: ancestor?.id, count: ancestor?.behind ?? 0, repository: element, @@ -603,8 +616,8 @@ class SCMSyncDataSource implements IAsyncDataSource { if (historyItemGroup) { children.push({ id: historyItemGroup.id, - label: localize('outgoing', "$(cloud-upload) Outgoing Changes"), - description: historyItemGroup.label, + label: `$(cloud-upload) ${historyItemGroup.label}`, + description: localize('outgoing', "Outgoing Changes"), ancestor: ancestor?.id, count: ancestor?.ahead ?? 0, repository: element, From 2b7ccea60a98ae4fc7c26f01110675b6dc89780b Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 20 Sep 2023 16:15:44 +0200 Subject: [PATCH 131/133] Make command center not configurable, avoid confusion about double command center entry in context menu --- src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 2dbe0416b4e..30a4e589a06 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -40,7 +40,7 @@ export class CommandCenterControl { const titleToolbar = instantiationService.createInstance(MenuWorkbenchToolBar, this.element, MenuId.CommandCenter, { contextMenu: MenuId.TitleBarContext, - hiddenItemStrategy: HiddenItemStrategy.Ignore, + hiddenItemStrategy: HiddenItemStrategy.NoHide, toolbarOptions: { primaryGroup: () => true, }, From b0d1559698b621f2c1331f13a8fe5edcc75aa41d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 20 Sep 2023 17:02:36 +0200 Subject: [PATCH 132/133] show notification after downloading successfully (#193582) --- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 186b50b58cd..215e53b2f9b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -1150,6 +1150,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const uriIdentityService = accessor.get(IUriIdentityService); const fileService = accessor.get(IFileService); const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); + const notificationService = accessor.get(INotificationService); const result = await fileDialogService.showOpenDialog({ title: localize('download sync activity dialog title', "Select folder to download Settings Sync activity"), @@ -1185,6 +1186,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return userDataSyncWorkbenchService.downloadSyncActivity(uriIdentityService.extUri.joinPath(result[0], indexes[0] !== 0 ? name : `${name} ${indexes[indexes.length - 1] + 1}`)); }); + + notificationService.info(localize('download sync activity complete', "Successfully downloaded Settings Sync activity.")); } })); From 943d5b2c5c8b967e80b9e16db6a8faea55d68ccf Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:11:15 +0200 Subject: [PATCH 133/133] SCM - Add file decorations to history item changes (#193576) SCM - Add file decorations to history items --- extensions/git/src/historyProvider.ts | 59 +++++++++-- extensions/git/src/repository.ts | 140 ++++++++++++++------------ 2 files changed, 124 insertions(+), 75 deletions(-) diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 0b3f6e1075e..f5a5b27b93c 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, l10n } from 'vscode'; -import { Repository } from './repository'; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, l10n } from 'vscode'; +import { Repository, Resource } from './repository'; import { IDisposable } from './util'; import { toGitUri } from './uri'; import { SyncActionButton } from './actionButton'; +import { Status } from './api/git'; -export class GitHistoryProvider implements SourceControlHistoryProvider, IDisposable { +export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { private readonly _onDidChangeActionButton = new EventEmitter(); readonly onDidChangeActionButton: Event = this._onDidChangeActionButton.event; @@ -18,6 +19,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + private _actionButton: SourceControlActionButton | undefined; get actionButton(): SourceControlActionButton | undefined { return this._actionButton; } set actionButton(button: SourceControlActionButton | undefined) { @@ -33,6 +37,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos this._onDidChangeCurrentHistoryItemGroup.fire(); } + private historyItemDecorations = new Map(); + private disposables: Disposable[] = []; constructor(protected readonly repository: Repository) { @@ -42,6 +48,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this)); this.disposables.push(actionButton.onDidChange(() => this.actionButton = actionButton.button)); + + this.disposables.push(window.registerFileDecorationProvider(this)); } private async onDidRunGitStatus(): Promise { @@ -96,14 +104,32 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos ? historyItemId.split('..') : [`${historyItemId}^`, historyItemId]; + const historyItemChangesUri: Uri[] = []; + const historyItemChanges: SourceControlHistoryItemChange[] = []; const changes = await this.repository.diffBetween(ref1, ref2); - return changes.map(change => ({ - uri: change.uri.with({ query: `ref=${historyItemId}` }), - originalUri: toGitUri(change.originalUri, ref1), - modifiedUri: toGitUri(change.originalUri, ref2), - renameUri: change.renameUri, - })); + for (const change of changes) { + const historyItemUri = change.uri.with({ + query: `ref=${historyItemId}` + }); + + // History item change + historyItemChanges.push({ + uri: historyItemUri, + originalUri: toGitUri(change.originalUri, ref1), + modifiedUri: toGitUri(change.originalUri, ref2), + renameUri: change.renameUri, + }); + + // History item change decoration + const fileDecoration = this.historyItemChangeFileDecoration(change.status); + this.historyItemDecorations.set(historyItemUri.toString(), fileDecoration); + + historyItemChangesUri.push(historyItemUri); + } + + this._onDidChangeDecorations.fire(historyItemChangesUri); + return historyItemChanges; } async resolveHistoryItemGroupBase(historyItemGroupId: string): Promise { @@ -131,6 +157,21 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, IDispos return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind }; } + provideFileDecoration(uri: Uri): FileDecoration | undefined { + return this.historyItemDecorations.get(uri.toString()); + } + + private historyItemChangeFileDecoration(status: Status): FileDecoration { + const letter = Resource.getStatusLetter(status); + const tooltip = Resource.getStatusText(status); + const color = Resource.getStatusColor(status); + + const fileDecoration = new FileDecoration(letter, tooltip, color); + fileDecoration.propagate = status !== Status.DELETED && status !== Status.INDEX_DELETED; + + return fileDecoration; + } + private async getSummaryHistoryItem(ref1: string, ref2: string): Promise { const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2); return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), description: diffShortStat }; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 16b1e84be0c..1065762a3fb 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -47,6 +47,43 @@ export const enum ResourceGroupType { export class Resource implements SourceControlResourceState { + static getStatusLetter(type: Status): string { + switch (type) { + case Status.INDEX_MODIFIED: + case Status.MODIFIED: + return 'M'; + case Status.INDEX_ADDED: + case Status.INTENT_TO_ADD: + return 'A'; + case Status.INDEX_DELETED: + case Status.DELETED: + return 'D'; + case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: + return 'R'; + case Status.TYPE_CHANGED: + return 'T'; + case Status.UNTRACKED: + return 'U'; + case Status.IGNORED: + return 'I'; + case Status.DELETED_BY_THEM: + return 'D'; + case Status.DELETED_BY_US: + return 'D'; + case Status.INDEX_COPIED: + return 'C'; + case Status.BOTH_DELETED: + case Status.ADDED_BY_US: + case Status.ADDED_BY_THEM: + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return '!'; // Using ! instead of ⚠, because the latter looks really bad on windows + default: + throw new Error('Unknown git status: ' + type); + } + } + static getStatusText(type: Status) { switch (type) { case Status.INDEX_MODIFIED: return l10n.t('Index Modified'); @@ -72,6 +109,41 @@ export class Resource implements SourceControlResourceState { } } + static getStatusColor(type: Status): ThemeColor { + switch (type) { + case Status.INDEX_MODIFIED: + return new ThemeColor('gitDecoration.stageModifiedResourceForeground'); + case Status.MODIFIED: + case Status.TYPE_CHANGED: + return new ThemeColor('gitDecoration.modifiedResourceForeground'); + case Status.INDEX_DELETED: + return new ThemeColor('gitDecoration.stageDeletedResourceForeground'); + case Status.DELETED: + return new ThemeColor('gitDecoration.deletedResourceForeground'); + case Status.INDEX_ADDED: + case Status.INTENT_TO_ADD: + return new ThemeColor('gitDecoration.addedResourceForeground'); + case Status.INDEX_COPIED: + case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: + return new ThemeColor('gitDecoration.renamedResourceForeground'); + case Status.UNTRACKED: + return new ThemeColor('gitDecoration.untrackedResourceForeground'); + case Status.IGNORED: + return new ThemeColor('gitDecoration.ignoredResourceForeground'); + case Status.BOTH_DELETED: + case Status.ADDED_BY_US: + case Status.DELETED_BY_THEM: + case Status.ADDED_BY_THEM: + case Status.DELETED_BY_US: + case Status.BOTH_ADDED: + case Status.BOTH_MODIFIED: + return new ThemeColor('gitDecoration.conflictingResourceForeground'); + default: + throw new Error('Unknown git status: ' + type); + } + } + @memoize get resourceUri(): Uri { if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED || this._type === Status.INTENT_TO_RENAME)) { @@ -189,75 +261,11 @@ export class Resource implements SourceControlResourceState { } get letter(): string { - switch (this.type) { - case Status.INDEX_MODIFIED: - case Status.MODIFIED: - return 'M'; - case Status.INDEX_ADDED: - case Status.INTENT_TO_ADD: - return 'A'; - case Status.INDEX_DELETED: - case Status.DELETED: - return 'D'; - case Status.INDEX_RENAMED: - case Status.INTENT_TO_RENAME: - return 'R'; - case Status.TYPE_CHANGED: - return 'T'; - case Status.UNTRACKED: - return 'U'; - case Status.IGNORED: - return 'I'; - case Status.DELETED_BY_THEM: - return 'D'; - case Status.DELETED_BY_US: - return 'D'; - case Status.INDEX_COPIED: - return 'C'; - case Status.BOTH_DELETED: - case Status.ADDED_BY_US: - case Status.ADDED_BY_THEM: - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return '!'; // Using ! instead of ⚠, because the latter looks really bad on windows - default: - throw new Error('Unknown git status: ' + this.type); - } + return Resource.getStatusLetter(this.type); } get color(): ThemeColor { - switch (this.type) { - case Status.INDEX_MODIFIED: - return new ThemeColor('gitDecoration.stageModifiedResourceForeground'); - case Status.MODIFIED: - case Status.TYPE_CHANGED: - return new ThemeColor('gitDecoration.modifiedResourceForeground'); - case Status.INDEX_DELETED: - return new ThemeColor('gitDecoration.stageDeletedResourceForeground'); - case Status.DELETED: - return new ThemeColor('gitDecoration.deletedResourceForeground'); - case Status.INDEX_ADDED: - case Status.INTENT_TO_ADD: - return new ThemeColor('gitDecoration.addedResourceForeground'); - case Status.INDEX_COPIED: - case Status.INDEX_RENAMED: - case Status.INTENT_TO_RENAME: - return new ThemeColor('gitDecoration.renamedResourceForeground'); - case Status.UNTRACKED: - return new ThemeColor('gitDecoration.untrackedResourceForeground'); - case Status.IGNORED: - return new ThemeColor('gitDecoration.ignoredResourceForeground'); - case Status.BOTH_DELETED: - case Status.ADDED_BY_US: - case Status.DELETED_BY_THEM: - case Status.ADDED_BY_THEM: - case Status.DELETED_BY_US: - case Status.BOTH_ADDED: - case Status.BOTH_MODIFIED: - return new ThemeColor('gitDecoration.conflictingResourceForeground'); - default: - throw new Error('Unknown git status: ' + this.type); - } + return Resource.getStatusColor(this.type); } get priority(): number {