From 03e53fd77b0742af1ead4fa104df9542ab23d9e9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 6 May 2022 15:55:35 -0700 Subject: [PATCH 001/350] add telemetry for shell integration --- .../common/xterm/shellIntegrationAddon.ts | 9 +++- .../terminal/browser/terminalInstance.ts | 11 +++- .../terminal/browser/xterm/xtermTerminal.ts | 54 +++++++++++++------ 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index ebe5ab92d14..b89e8338411 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -15,6 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; // eslint-disable-next-line code-import-patterns import type { ITerminalAddon, Terminal } from 'xterm-headless'; import { ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; /** * Shell integration is a feature that enhances the terminal's understanding of what's happening @@ -123,9 +124,11 @@ const enum VSCodeOscPt { export class ShellIntegrationAddon extends Disposable implements IShellIntegration, ITerminalAddon { private _terminal?: Terminal; readonly capabilities = new TerminalCapabilityStore(); + private _hasUpdatedTelemetry: boolean = false; constructor( - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @ITelemetryService private readonly _telemetryService?: ITelemetryService ) { super(); } @@ -244,6 +247,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati commandDetection = new CommandDetectionCapability(terminal, this._logService); this.capabilities.add(TerminalCapability.CommandDetection, commandDetection); } + if (!this._hasUpdatedTelemetry) { + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivated'); + this._hasUpdatedTelemetry = true; + } return commandDetection; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 88b3e2d041d..390435476e9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -80,6 +80,7 @@ import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import type { ITerminalAddon, Terminal as XTermTerminal } from 'xterm'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const enum Constants { /** @@ -361,7 +362,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IEditorService private readonly _editorService: IEditorService, @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, - @IHistoryService private readonly _historyService: IHistoryService + @IHistoryService private readonly _historyService: IHistoryService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(); @@ -1631,6 +1633,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + if (shellIntegrationAttempted) { + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationProcessExit'); + } + // First onExit to consumers, this can happen after the terminal has already been disposed. this._onExit.fire(exitCodeOrError); @@ -2572,7 +2578,7 @@ export class TerminalLabelComputer extends Disposable { } } -export function parseExitResult( +export function parseExitResult(this: any, exitCodeOrError: ITerminalLaunchError | number | undefined, shellLaunchConfig: IShellLaunchConfig, processState: ProcessState, @@ -2602,6 +2608,7 @@ export function parseExitResult( if (shellIntegrationAttempted) { if (commandLine) { message = nls.localize('launchFailed.exitCodeAndCommandLineShellIntegration', "The terminal process \"{0}\" failed to launch (exit code: {1}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", commandLine, code); + this._logProcessExitShellIntegrationTelemetry(); } else { message = nls.localize('launchFailed.exitCodeOnlyShellIntegration', "The terminal process failed to launch (exit code: {0}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", code); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index a1efe91c333..91236a0d747 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -32,8 +32,9 @@ import { Color } from 'vs/base/common/color'; import { ShellIntegrationAddon } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DecorationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/decorationAddon'; -import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { Emitter } from 'vs/base/common/event'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -59,7 +60,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Always on addons private _commandNavigationAddon: CommandNavigationAddon; - private _shellIntegrationAddon: ShellIntegrationAddon; + private _shellIntegrationAddon!: ShellIntegrationAddon; private _decorationAddon: DecorationAddon | undefined; // Optional addons @@ -103,7 +104,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { @INotificationService private readonly _notificationService: INotificationService, @IStorageService private readonly _storageService: IStorageService, @IThemeService private readonly _themeService: IThemeService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(); this.target = location; @@ -153,7 +155,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { } if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) { - this._updateDecorationAddon(); + this._updateShellIntegrationAddons(); } })); @@ -169,9 +171,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); - this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon); - this.raw.loadAddon(this._shellIntegrationAddon); - this._updateDecorationAddon(); + this._updateShellIntegrationAddons(); } private _createDecorationAddon(): void { this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, this._capabilities); @@ -595,16 +595,40 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { } } - private _updateDecorationAddon(): void { - if (this._configHelper.config.shellIntegration?.enabled && this._configHelper.config.shellIntegration.decorationsEnabled) { - if (!this._decorationAddon) { + private _updateShellIntegrationAddons(): void { + const shellIntegrationEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); + const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); + if (shellIntegrationEnabled) { + if (!this._shellIntegrationAddon) { + this._createShellIntegrationAddon(); + } + if (decorationsEnabled && !this._decorationAddon) { this._createDecorationAddon(); + } else if (this._decorationAddon && !decorationsEnabled) { + this._decorationAddon.dispose(); + this._decorationAddon = undefined; + } + } else { + this._shellIntegrationAddon?.dispose(); + if (this._decorationAddon) { + this._decorationAddon.dispose(); + this._decorationAddon = undefined; + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationDisabledByUser'); } - return; - } - if (this._decorationAddon) { - this._decorationAddon.dispose(); - this._decorationAddon = undefined; } } + + private _createShellIntegrationAddon(): void { + this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon); + this.raw.loadAddon(this._shellIntegrationAddon); + this._addTelemetryIfShellIntegrationFailed(); + } + + private async _addTelemetryIfShellIntegrationFailed(): Promise { + setTimeout(() => { + if (!this._capabilities.get(TerminalCapability.CommandDetection) && !this._capabilities.get(TerminalCapability.CwdDetection)) { + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivate'); + } + }, 10000); + } } From 549c4077ecb6bd89299d8547441fd67a8e9390ff Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 6 May 2022 16:02:00 -0700 Subject: [PATCH 002/350] clean up --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 4 ++-- .../workbench/contrib/terminal/browser/terminalInstance.ts | 5 ++--- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index c48979fa4d6..371b0f7e8f7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -858,9 +858,9 @@ export interface IXtermTerminal { readonly commandTracker: ICommandTracker; /** - * Reports the status of shell integration and fires events relating to it. + * Reports the status of shell integration and fires events relating to it when enabled. */ - readonly shellIntegration: IShellIntegration; + readonly shellIntegration: IShellIntegration | undefined; /** * The position of the terminal. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 390435476e9..c04dcc4d37c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -725,7 +725,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._areLinksReady = true; this._onLinksReady.fire(this); }); - this._processManager.onRestoreCommands(e => this.xterm?.shellIntegration.deserialize(e)); + this._processManager.onRestoreCommands(e => this.xterm?.shellIntegration?.deserialize(e)); this._loadTypeAheadAddon(xterm); @@ -2578,7 +2578,7 @@ export class TerminalLabelComputer extends Disposable { } } -export function parseExitResult(this: any, +export function parseExitResult( exitCodeOrError: ITerminalLaunchError | number | undefined, shellLaunchConfig: IShellLaunchConfig, processState: ProcessState, @@ -2608,7 +2608,6 @@ export function parseExitResult(this: any, if (shellIntegrationAttempted) { if (commandLine) { message = nls.localize('launchFailed.exitCodeAndCommandLineShellIntegration', "The terminal process \"{0}\" failed to launch (exit code: {1}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", commandLine, code); - this._logProcessExitShellIntegrationTelemetry(); } else { message = nls.localize('launchFailed.exitCodeOnlyShellIntegration', "The terminal process failed to launch (exit code: {0}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", code); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 91236a0d747..81f47c7bba9 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -60,7 +60,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Always on addons private _commandNavigationAddon: CommandNavigationAddon; - private _shellIntegrationAddon!: ShellIntegrationAddon; + private _shellIntegrationAddon: ShellIntegrationAddon | undefined; private _decorationAddon: DecorationAddon | undefined; // Optional addons @@ -79,7 +79,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { readonly onDidChangeFindResults = this._onDidChangeFindResults.event; get commandTracker(): ICommandTracker { return this._commandNavigationAddon; } - get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; } + get shellIntegration(): IShellIntegration | undefined { return this._shellIntegrationAddon; } private _target: TerminalLocation | undefined; set target(location: TerminalLocation | undefined) { From 855c0f86571af731858774499bcff2ac69282b13 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 6 May 2022 16:12:29 -0700 Subject: [PATCH 003/350] =?UTF-8?q?=F0=9F=92=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 81f47c7bba9..14c09ba3477 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -621,13 +621,14 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { private _createShellIntegrationAddon(): void { this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon); this.raw.loadAddon(this._shellIntegrationAddon); - this._addTelemetryIfShellIntegrationFailed(); + this._ensureCapabilitiesOrAddFailureTelemetry(); } - private async _addTelemetryIfShellIntegrationFailed(): Promise { + private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { setTimeout(() => { if (!this._capabilities.get(TerminalCapability.CommandDetection) && !this._capabilities.get(TerminalCapability.CwdDetection)) { this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivate'); + this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } }, 10000); } From 34d31f4b9e7be13cddbe9a3c085b81595d91fcb9 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 9 May 2022 14:09:00 +0200 Subject: [PATCH 004/350] merge editor kick off --- build/lib/i18n.resources.json | 4 + .../mergeEditor/browser/media/mergeEditor.css | 4 + .../browser/mergeEditor.contribution.ts | 61 ++++++++++ .../mergeEditor/browser/mergeEditor.ts | 110 ++++++++++++++++++ .../mergeEditor/browser/mergeEditorInput.ts | 93 +++++++++++++++ .../mergeEditor/browser/mergeEditorModel.ts | 20 ++++ .../browser/mergeEditorSerializer.ts | 31 +++++ src/vs/workbench/workbench.common.main.ts | 3 + 8 files changed, 326 insertions(+) create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 06bfbb2b506..246384ae15a 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -118,6 +118,10 @@ "name": "vs/workbench/contrib/markers", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/mergeEditor", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/localizations", "project": "vscode-workbench" diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css new file mode 100644 index 00000000000..a4a092d8349 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts new file mode 100644 index 00000000000..27d3d7c8c9b --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import { localize } from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; +import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; +import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditor'; +import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { MergeEditorSerializer } from './mergeEditorSerializer'; + + +//#region Editor Descriptior + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + MergeEditor, + MergeEditor.ID, + localize('name', "Merge Editor") + ), + [ + new SyncDescriptor(MergeEditorInput) + ] +); + +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( + MergeEditorInput.ID, + MergeEditorSerializer +); + +registerAction2(class Foo extends Action2 { + + constructor() { + super({ + id: 'testMergeEditor', + title: '3wm', + f1: true + }); + } + + run(accessor: ServicesAccessor, ...args: any[]): void { + const instaService = accessor.get(IInstantiationService); + const input = instaService.createInstance( + MergeEditorInput, + URI.file('/Users/jrieken/Code/_samples/abc/test.md'), + URI.file('/Users/jrieken/Code/_samples/abc/test.md'), + URI.file('/Users/jrieken/Code/_samples/abc/test.md'), + URI.file('/Users/jrieken/Code/_samples/abc/test.md'), + ); + accessor.get(IEditorService).openEditor(input); + } + +}); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts new file mode 100644 index 00000000000..54f3b6dadb0 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./media/mergeEditor'; +import { Dimension, reset } from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Direction, Grid, IView, IViewSize, LayoutPriority } from 'vs/base/browser/ui/grid/grid'; +import { Sizing } from 'vs/base/browser/ui/splitview/splitview'; + + +class CodeEditorView implements IView { + + preferredWidth?: number | undefined; + preferredHeight?: number | undefined; + + element: HTMLElement = document.createElement('div'); + + minimumWidth: number = 10; + maximumWidth: number = Number.MAX_SAFE_INTEGER; + minimumHeight: number = 10; + maximumHeight: number = Number.MAX_SAFE_INTEGER; + priority?: LayoutPriority | undefined; + snap?: boolean | undefined; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor(text: string) { + this.element.innerText = text; + } + + layout(width: number, height: number, top: number, left: number): void { + this.element.style.width = `${width}px`; + this.element.style.height = `${height}px`; + this.element.style.top = `${top}px`; + this.element.style.left = `${left}px`; + } + +} + +export class MergeEditor extends EditorPane { + + static readonly ID = 'mergeEditor'; + + private readonly _sessionDisposables = new DisposableStore(); + + private _grid!: Grid; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IThemeService themeService: IThemeService, + ) { + super(MergeEditor.ID, telemetryService, themeService, storageService); + } + + override dispose(): void { + this._sessionDisposables.dispose(); + super.dispose(); + } + + protected createEditor(parent: HTMLElement): void { + + + const inputOneView = new CodeEditorView('one'); + const inputTwoView = new CodeEditorView('two'); + const inputResultView = new CodeEditorView('result'); + + this._grid = new Grid(inputResultView); + + this._grid.addView(inputOneView, Sizing.Distribute, inputResultView, Direction.Up); + this._grid.addView(inputTwoView, Sizing.Distribute, inputOneView, Direction.Right); + reset(parent, this._grid.element); + } + + layout(dimension: Dimension): void { + this._grid.layout(dimension.width, dimension.height); + } + + override async setInput(input: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + if (!(input instanceof MergeEditorInput)) { + throw new BugIndicatingError('ONLY MergeEditorInput is supported'); + } + await super.setInput(input, options, context, token); + console.trace('mergeEditor@53'); + this._sessionDisposables.clear(); + // const model = await input.resolve(); + // if (token.isCancellationRequested) { + // return; + // } + } + + override clearInput(): void { + console.trace('mergeEditor@66'); + } + +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts new file mode 100644 index 00000000000..9a3c69d7cc2 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; + +export interface MergeEditorInputJSON { + anchestor: URI; + inputOne: URI; + inputTwo: URI; + result: URI; +} + +export class MergeEditorInput extends EditorInput { + + static readonly ID = 'mergeEditor.Input'; + + private _model?: MergeEditorModel; + + constructor( + private readonly _anchestor: URI, + private readonly _inputOne: URI, + private readonly _inputTwo: URI, + private readonly _result: URI, + @IInstantiationService private readonly _instaService: IInstantiationService, + @ITextModelService private readonly _textModelService: ITextModelService, + ) { + super(); + } + + override dispose(): void { + super.dispose(); + } + + get typeId(): string { + return MergeEditorInput.ID; + } + + get resource(): URI | undefined { + return this._result; + } + + override async resolve(): Promise { + if (!this._model) { + + const anchestor = await this._textModelService.createModelReference(this._anchestor); + const inputOne = await this._textModelService.createModelReference(this._inputOne); + const inputTwo = await this._textModelService.createModelReference(this._inputTwo); + const result = await this._textModelService.createModelReference(this._result); + + this._model = this._instaService.createInstance( + MergeEditorModel, + anchestor.object.textEditorModel, + inputOne.object.textEditorModel, + inputTwo.object.textEditorModel, + result.object.textEditorModel + ); + + this._store.add(this._model); + this._store.add(anchestor); + this._store.add(inputOne); + this._store.add(inputTwo); + this._store.add(result); + } + return this._model; + } + + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { + if (!(otherInput instanceof MergeEditorInput)) { + return false; + } + return isEqual(this._anchestor, otherInput._anchestor) + && isEqual(this._inputOne, otherInput._inputOne) + && isEqual(this._inputTwo, otherInput._inputTwo) + && isEqual(this._result, otherInput._result); + } + + toJSON(): MergeEditorInputJSON { + return { + anchestor: this._anchestor, + inputOne: this._inputOne, + inputTwo: this._inputTwo, + result: this._result, + }; + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts new file mode 100644 index 00000000000..172a5c8e1a0 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel } from 'vs/editor/common/model'; +import { EditorModel } from 'vs/workbench/common/editor/editorModel'; + +export class MergeEditorModel extends EditorModel { + + constructor( + readonly anchestor: ITextModel, + readonly inputOne: ITextModel, + readonly inputTwo: ITextModel, + readonly result: ITextModel, + ) { + super(); + } + +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts new file mode 100644 index 00000000000..2480f0950ba --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; +import { parse } from 'vs/base/common/marshalling'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorSerializer } from 'vs/workbench/common/editor'; +import { MergeEditorInput, MergeEditorInputJSON } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; + +export class MergeEditorSerializer implements IEditorSerializer { + + canSerialize(): boolean { + return true; + } + + serialize(editor: MergeEditorInput): string { + return JSON.stringify(editor.toJSON()); + } + + deserialize(instantiationService: IInstantiationService, raw: string): MergeEditorInput | undefined { + try { + const data = parse(raw); + return instantiationService.createInstance(MergeEditorInput, data.anchestor, data.inputOne, data.inputTwo, data.result); + } catch (err) { + onUnexpectedError(err); + return undefined; + } + } +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index bf87db19d53..74ebaf956ec 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -210,6 +210,9 @@ import 'vs/workbench/contrib/debug/browser/debugViewlet'; // Markers import 'vs/workbench/contrib/markers/browser/markers.contribution'; +// Merge Editor +import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution'; + // Comments import 'vs/workbench/contrib/comments/browser/comments.contribution'; From 6435afb931f5f1ac496e115c91fb0a1ed6ead82a Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 9 May 2022 15:26:23 +0200 Subject: [PATCH 005/350] remove trace, resolve input to model, log model --- .../workbench/contrib/mergeEditor/browser/mergeEditor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 54f3b6dadb0..4ce0cc0eb12 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -74,7 +74,6 @@ export class MergeEditor extends EditorPane { protected createEditor(parent: HTMLElement): void { - const inputOneView = new CodeEditorView('one'); const inputTwoView = new CodeEditorView('two'); const inputResultView = new CodeEditorView('result'); @@ -95,16 +94,17 @@ export class MergeEditor extends EditorPane { throw new BugIndicatingError('ONLY MergeEditorInput is supported'); } await super.setInput(input, options, context, token); - console.trace('mergeEditor@53'); + this._sessionDisposables.clear(); - // const model = await input.resolve(); + const model = await input.resolve(); + console.log(model); // if (token.isCancellationRequested) { // return; // } } override clearInput(): void { - console.trace('mergeEditor@66'); + } } From 26a86d4a42da140bb268ee16f1ae1f04b1827b4b Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 9 May 2022 15:53:22 +0200 Subject: [PATCH 006/350] Loads paths from command argument. --- .../browser/mergeEditor.contribution.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 27d3d7c8c9b..c1fc3b0fbf2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -45,17 +45,39 @@ registerAction2(class Foo extends Action2 { f1: true }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + const validatedArgs = ITestMergeEditorArgs.validate(args[0]); + + function normalize(uri: URI | string): URI { + if (typeof uri === 'string') { + return URI.parse(uri); + } else { + return uri; + } + } + const instaService = accessor.get(IInstantiationService); const input = instaService.createInstance( MergeEditorInput, - URI.file('/Users/jrieken/Code/_samples/abc/test.md'), - URI.file('/Users/jrieken/Code/_samples/abc/test.md'), - URI.file('/Users/jrieken/Code/_samples/abc/test.md'), - URI.file('/Users/jrieken/Code/_samples/abc/test.md'), + normalize(validatedArgs.ancestor), + normalize(validatedArgs.input1), + normalize(validatedArgs.input2), + normalize(validatedArgs.output), ); accessor.get(IEditorService).openEditor(input); } }); + +namespace ITestMergeEditorArgs { + export function validate(args: any): ITestMergeEditorArgs { + return args as ITestMergeEditorArgs; + } +} + +interface ITestMergeEditorArgs { + ancestor: URI | string; + input1: URI | string; + input2: URI | string; + output: URI | string; +} From 6a051dc03a7763ed75fbc746b7bb16ed84aef9eb Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 9 May 2022 16:17:29 +0200 Subject: [PATCH 007/350] Creates editors & MVP of scroll synchronization. --- .../mergeEditor/browser/mergeEditor.ts | 109 +++++++++++------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 4ce0cc0eb12..14950aef60d 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -19,37 +19,10 @@ import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/merge import { DisposableStore } from 'vs/base/common/lifecycle'; import { Direction, Grid, IView, IViewSize, LayoutPriority } from 'vs/base/browser/ui/grid/grid'; import { Sizing } from 'vs/base/browser/ui/splitview/splitview'; - - -class CodeEditorView implements IView { - - preferredWidth?: number | undefined; - preferredHeight?: number | undefined; - - element: HTMLElement = document.createElement('div'); - - minimumWidth: number = 10; - maximumWidth: number = Number.MAX_SAFE_INTEGER; - minimumHeight: number = 10; - maximumHeight: number = Number.MAX_SAFE_INTEGER; - priority?: LayoutPriority | undefined; - snap?: boolean | undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - constructor(text: string) { - this.element.innerText = text; - } - - layout(width: number, height: number, top: number, left: number): void { - this.element.style.width = `${width}px`; - this.element.style.height = `${height}px`; - this.element.style.top = `${top}px`; - this.element.style.left = `${left}px`; - } - -} +import { ITextModel } from 'vs/editor/common/model'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ScrollType } from 'vs/editor/common/editorCommon'; export class MergeEditor extends EditorPane { @@ -59,12 +32,28 @@ export class MergeEditor extends EditorPane { private _grid!: Grid; + private readonly inputOneView = this.instantiation.createInstance(CodeEditorView); + private readonly inputTwoView = this.instantiation.createInstance(CodeEditorView); + private readonly inputResultView = this.instantiation.createInstance(CodeEditorView); + constructor( + @IInstantiationService private readonly instantiation: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IThemeService themeService: IThemeService, ) { super(MergeEditor.ID, telemetryService, themeService, storageService); + + this._store.add(this.inputOneView.editor.onDidScrollChange(c => { + if (c.scrollTopChanged) { + this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + } + })); + this._store.add(this.inputTwoView.editor.onDidScrollChange(c => { + if (c.scrollTopChanged) { + this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + } + })); } override dispose(): void { @@ -73,15 +62,10 @@ export class MergeEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { + this._grid = new Grid(this.inputResultView); - const inputOneView = new CodeEditorView('one'); - const inputTwoView = new CodeEditorView('two'); - const inputResultView = new CodeEditorView('result'); - - this._grid = new Grid(inputResultView); - - this._grid.addView(inputOneView, Sizing.Distribute, inputResultView, Direction.Up); - this._grid.addView(inputTwoView, Sizing.Distribute, inputOneView, Direction.Right); + this._grid.addView(this.inputOneView, Sizing.Distribute, this.inputResultView, Direction.Up); + this._grid.addView(this.inputTwoView, Sizing.Distribute, this.inputOneView, Direction.Right); reset(parent, this._grid.element); } @@ -97,6 +81,11 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); + + this.inputOneView.setModel(model.inputOne); + this.inputTwoView.setModel(model.inputTwo); + this.inputResultView.setModel(model.result); + console.log(model); // if (token.isCancellationRequested) { // return; @@ -108,3 +97,45 @@ export class MergeEditor extends EditorPane { } } + +class CodeEditorView implements IView { + preferredWidth?: number | undefined; + preferredHeight?: number | undefined; + + element: HTMLElement = document.createElement('div'); + + minimumWidth: number = 10; + maximumWidth: number = Number.MAX_SAFE_INTEGER; + minimumHeight: number = 10; + maximumHeight: number = Number.MAX_SAFE_INTEGER; + priority?: LayoutPriority | undefined; + snap?: boolean | undefined; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + public readonly editor = this.instantiationService.createInstance( + CodeEditorWidget, + this.element, + { minimap: { enabled: false } }, + {} + ); + + constructor( + @IInstantiationService + private readonly instantiationService: IInstantiationService + ) { + } + + public setModel(model: ITextModel | undefined): void { + this.editor.setModel(model); + } + + layout(width: number, height: number, top: number, left: number): void { + this.element.style.width = `${width}px`; + this.element.style.height = `${height}px`; + this.element.style.top = `${top}px`; + this.element.style.left = `${left}px`; + this.editor.layout({ width, height }); + } +} From 52a634dd87122c021f2ae6645d27fc043463442e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 9 May 2022 16:25:11 +0200 Subject: [PATCH 008/350] Synchronize scrolling of all three editors. --- .../mergeEditor/browser/mergeEditor.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 14950aef60d..c7c4d8e9358 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -44,14 +44,29 @@ export class MergeEditor extends EditorPane { ) { super(MergeEditor.ID, telemetryService, themeService, storageService); + const reentrancyBarrier = new ReentrancyBarrier(); this._store.add(this.inputOneView.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { - this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + reentrancyBarrier.runExclusively(() => { + this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + }); } })); this._store.add(this.inputTwoView.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { - this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + reentrancyBarrier.runExclusively(() => { + this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + }); + } + })); + this._store.add(this.inputResultView.editor.onDidScrollChange(c => { + if (c.scrollTopChanged) { + reentrancyBarrier.runExclusively(() => { + this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + }); } })); } @@ -139,3 +154,19 @@ class CodeEditorView implements IView { this.editor.layout({ width, height }); } } + +class ReentrancyBarrier { + private isActive = false; + + public runExclusively(fn: () => void): void { + if (this.isActive) { + return; + } + this.isActive = true; + try { + fn(); + } finally { + this.isActive = false; + } + } +} From c54e9d45e170d871be0bb485a103d80f9c06f5e7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 09:06:48 -0700 Subject: [PATCH 009/350] add doHandleVSCodeSequence and update telemetry based upon that --- .../common/xterm/shellIntegrationAddon.ts | 23 +++++++++++++++---- .../terminal/browser/xterm/xtermTerminal.ts | 10 -------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index b89e8338411..648285d4a45 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -137,9 +137,19 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati this._terminal = xterm; this.capabilities.add(TerminalCapability.PartialCommandDetection, new PartialCommandDetectionCapability(this._terminal)); this._register(xterm.parser.registerOscHandler(ShellIntegrationOscPs.VSCode, data => this._handleVSCodeSequence(data))); + this._ensureCapabilitiesOrAddFailureTelemetry(); } private _handleVSCodeSequence(data: string): boolean { + const didHandle = this._doHandleVSCodeSequence(data); + if (!this._hasUpdatedTelemetry && didHandle) { + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivated'); + this._hasUpdatedTelemetry = true; + } + return didHandle; + } + + private _doHandleVSCodeSequence(data: string): boolean { if (!this._terminal) { return false; } @@ -214,6 +224,15 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return false; } + private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { + setTimeout(() => { + if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivate'); + this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); + } + }, 10000); + } + serialize(): ISerializedCommandDetectionCapability { if (!this._terminal || !this.capabilities.has(TerminalCapability.CommandDetection)) { return { @@ -247,10 +266,6 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati commandDetection = new CommandDetectionCapability(terminal, this._logService); this.capabilities.add(TerminalCapability.CommandDetection, commandDetection); } - if (!this._hasUpdatedTelemetry) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivated'); - this._hasUpdatedTelemetry = true; - } return commandDetection; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 14c09ba3477..fbb92329c8f 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -621,15 +621,5 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { private _createShellIntegrationAddon(): void { this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon); this.raw.loadAddon(this._shellIntegrationAddon); - this._ensureCapabilitiesOrAddFailureTelemetry(); - } - - private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { - setTimeout(() => { - if (!this._capabilities.get(TerminalCapability.CommandDetection) && !this._capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivate'); - this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); - } - }, 10000); } } From 740b2798ddc1954db5b69ad97939774cab31c317 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 09:08:04 -0700 Subject: [PATCH 010/350] shellIntegrationAttempted -> failedShellIntegrationInjection --- .../contrib/terminal/browser/terminalInstance.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c04dcc4d37c..9af95baf333 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1581,7 +1581,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { * @param exitCode The exit code of the process, this is undefined when the terminal was exited * through user action. */ - private async _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError, shellIntegrationAttempted?: boolean): Promise { + private async _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError, failedShellIntegrationInjection?: boolean): Promise { // Prevent dispose functions being triggered multiple times if (this._isExiting) { return; @@ -1592,7 +1592,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { await this._flushXtermData(); this._logService.debug(`Terminal process exit (instanceId: ${this.instanceId}) with code ${this._exitCode}`); - const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd, shellIntegrationAttempted); + const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd, failedShellIntegrationInjection); this._exitCode = parsedExitResult?.code; const exitMessage = parsedExitResult?.message; @@ -1633,7 +1633,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - if (shellIntegrationAttempted) { + if (failedShellIntegrationInjection) { this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationProcessExit'); } @@ -2583,7 +2583,7 @@ export function parseExitResult( shellLaunchConfig: IShellLaunchConfig, processState: ProcessState, initialCwd: string | undefined, - shellIntegrationAttempted?: boolean + failedShellIntegrationInjection?: boolean ): { code: number | undefined; message: string | undefined } | undefined { // Only return a message if the exit code is non-zero if (exitCodeOrError === undefined || exitCodeOrError === 0) { @@ -2605,7 +2605,7 @@ export function parseExitResult( commandLine += shellLaunchConfig.args.map(a => ` '${a}'`).join(); } } - if (shellIntegrationAttempted) { + if (failedShellIntegrationInjection) { if (commandLine) { message = nls.localize('launchFailed.exitCodeAndCommandLineShellIntegration', "The terminal process \"{0}\" failed to launch (exit code: {1}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", commandLine, code); } else { From fef40536407c170d50743d7e6387b2322cbb4c7e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 09:58:23 -0700 Subject: [PATCH 011/350] remove unused --- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index fbb92329c8f..b1c05b75118 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -32,7 +32,7 @@ import { Color } from 'vs/base/common/color'; import { ShellIntegrationAddon } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DecorationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/decorationAddon'; -import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; From 6c796dde398f7925c485c8f1dea306c6d0c48a10 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 11:40:42 -0700 Subject: [PATCH 012/350] rearrange properties --- .../platform/terminal/common/xterm/shellIntegrationAddon.ts | 4 ++-- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 648285d4a45..8994ae11b87 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -127,8 +127,8 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _hasUpdatedTelemetry: boolean = false; constructor( - @ILogService private readonly _logService: ILogService, - @ITelemetryService private readonly _telemetryService?: ITelemetryService + private readonly _logService: ILogService, + private readonly _telemetryService?: ITelemetryService ) { super(); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index b1c05b75118..485936bf0cb 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -619,7 +619,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { } private _createShellIntegrationAddon(): void { - this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon); + this._shellIntegrationAddon = new ShellIntegrationAddon(this._logService, this._telemetryService); this.raw.loadAddon(this._shellIntegrationAddon); } } From 0f51c46f30f8be6a4851b8c856eb1e29ac8a3275 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 11:43:18 -0700 Subject: [PATCH 013/350] keep dependency injection --- src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 8994ae11b87..cfafc890767 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -127,7 +127,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _hasUpdatedTelemetry: boolean = false; constructor( - private readonly _logService: ILogService, + @ILogService private readonly _logService: ILogService, private readonly _telemetryService?: ITelemetryService ) { super(); From 331fa9017a7d7d2370d7fc628f66bdfb61363da8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 16:11:07 -0700 Subject: [PATCH 014/350] add process property type --- src/vs/platform/terminal/common/terminal.ts | 4 +++- src/vs/platform/terminal/node/terminalProcess.ts | 5 ++++- .../contrib/terminal/browser/terminalProcessManager.ts | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index c3d3657b6a9..91b3244bd3f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -209,7 +209,8 @@ export const enum ProcessPropertyType { ShellType = 'shellType', HasChildProcesses = 'hasChildProcesses', ResolvedShellLaunchConfig = 'resolvedShellLaunchConfig', - OverrideDimensions = 'overrideDimensions' + OverrideDimensions = 'overrideDimensions', + FailedShellIntegrationActivation = 'failedShellIntegrationActivation' } export interface IProcessProperty { @@ -226,6 +227,7 @@ export interface IProcessPropertyMap { [ProcessPropertyType.HasChildProcesses]: boolean; [ProcessPropertyType.ResolvedShellLaunchConfig]: IShellLaunchConfig; [ProcessPropertyType.OverrideDimensions]: ITerminalDimensionsOverride | undefined; + [ProcessPropertyType.FailedShellIntegrationActivation]: boolean | undefined; } export interface IFixedTerminalDimensions { diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index a12bb5879dd..bc809abf0cd 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -97,7 +97,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + failedShellIntegrationActivation: false }; private static _lastKillOrStart = 0; private _exitCode: number | undefined; @@ -213,6 +214,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess await fs.copyFile(f.source, f.dest); } } + } else { + this._onDidChangeProperty.fire({ type: ProcessPropertyType.FailedShellIntegrationActivation, value: true }); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 2a5080e8115..9c4280b3856 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -33,6 +33,7 @@ import { NaiveCwdDetectionCapability } from 'vs/platform/terminal/common/capabil import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { URI } from 'vs/base/common/uri'; import { ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -130,7 +131,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService + @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, + @ITelemetryService private readonly _telemetryService: ITelemetryService ) { super(); @@ -332,6 +334,9 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce case ProcessPropertyType.HasChildProcesses: this._hasChildProcesses = value; break; + case ProcessPropertyType.FailedShellIntegrationActivation: + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivateArgs'); + break; } this._onDidChangeProperty.fire({ type, value }); }) From 11ad58bfda475fe95767894aea65d3b97b735885 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 16:31:41 -0700 Subject: [PATCH 015/350] add ShellIntegrationTelemetry --- src/vs/platform/terminal/common/terminal.ts | 9 +++++++++ .../terminal/common/xterm/shellIntegrationAddon.ts | 6 +++--- .../contrib/terminal/browser/terminalInstance.ts | 4 ++-- .../contrib/terminal/browser/terminalProcessManager.ts | 4 ++-- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 3 ++- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 91b3244bd3f..95527630c52 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -242,6 +242,15 @@ export interface IFixedTerminalDimensions { rows?: number; } + +export const enum ShellIntegrationTelemetry { + ActivationTimeout = 'terminal/shellIntegrationActivationTimeout', + FailureProcessExit = 'terminal/shellIntegrationFailureProcessExit', + FailureCustomArgs = 'terminal/shellIntegrationActivationFailureCustomArgs', + Success = 'terminal/shellIntegrationActivationSucceeded', + DisabledByUser = 'terminal/shellIntegrationDisabledByUser' +} + export interface IPtyHostController { readonly onPtyHostExit?: Event; readonly onPtyHostStart?: Event; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index cfafc890767..aa9aca4d9d7 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IShellIntegration } from 'vs/platform/terminal/common/terminal'; +import { IShellIntegration, ShellIntegrationTelemetry } from 'vs/platform/terminal/common/terminal'; import { Disposable } from 'vs/base/common/lifecycle'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -143,7 +143,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _handleVSCodeSequence(data: string): boolean { const didHandle = this._doHandleVSCodeSequence(data); if (!this._hasUpdatedTelemetry && didHandle) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivated'); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.Success); this._hasUpdatedTelemetry = true; } return didHandle; @@ -227,7 +227,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivate'); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.ActivationTimeout); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } }, 10000); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 9af95baf333..ce50ff14aad 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -42,7 +42,7 @@ import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notif import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ProcessPropertyType, ShellIntegrationTelemetry, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -1634,7 +1634,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } if (failedShellIntegrationInjection) { - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationProcessExit'); + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.FailureProcessExit); } // First onExit to consumers, this can happen after the terminal has already been disposed. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 9c4280b3856..8ad3c42cadd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -21,7 +21,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { EnvironmentVariableInfoChangesActive, EnvironmentVariableInfoStale } from 'vs/workbench/contrib/terminal/browser/environmentVariableInfo'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IEnvironmentVariableInfo, IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, ITerminalDimensions, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, ITerminalProcessOptions, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, ITerminalDimensions, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, ITerminalProcessOptions, TerminalSettingId, ShellIntegrationTelemetry } from 'vs/platform/terminal/common/terminal'; import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; import { localize } from 'vs/nls'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; @@ -335,7 +335,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._hasChildProcesses = value; break; case ProcessPropertyType.FailedShellIntegrationActivation: - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailedToActivateArgs'); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.FailureCustomArgs); break; } this._onDidChangeProperty.fire({ type, value }); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 485936bf0cb..5d9bed5f4f7 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -35,6 +35,7 @@ import { DecorationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/dec import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ShellIntegrationTelemetry } from 'vs/workbench/contrib/terminal/common/terminalStrings'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -613,7 +614,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { if (this._decorationAddon) { this._decorationAddon.dispose(); this._decorationAddon = undefined; - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationDisabledByUser'); + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.DisabledByUser); } } } From c252f1158d19a52e344e2262c7dc84ff158c094b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 9 May 2022 17:07:01 -0700 Subject: [PATCH 016/350] fix errors --- src/vs/workbench/contrib/terminal/browser/remotePty.ts | 3 ++- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 6 +++--- .../workbench/contrib/terminal/electron-sandbox/localPty.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index cc7b5d1b6d5..ab808bab234 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -37,7 +37,8 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + failedShellIntegrationActivation: false }; get id(): number { return this._id; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 5d9bed5f4f7..3e73fa309f0 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -13,7 +13,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IShellIntegration, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IShellIntegration, ShellIntegrationTelemetry, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { ICommandTracker, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -35,7 +35,6 @@ import { DecorationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/dec import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ShellIntegrationTelemetry } from 'vs/workbench/contrib/terminal/common/terminalStrings'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -614,7 +613,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { if (this._decorationAddon) { this._decorationAddon.dispose(); this._decorationAddon = undefined; - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.DisabledByUser); + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.DisabledByUser + ); } } } diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index f840fbf52d1..323dd56a2c1 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -24,7 +24,8 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { shellType: undefined, hasChildProcesses: true, resolvedShellLaunchConfig: {}, - overrideDimensions: undefined + overrideDimensions: undefined, + failedShellIntegrationActivation: false }; private readonly _onProcessData = this._register(new Emitter()); readonly onProcessData = this._onProcessData.event; From beeb2e420210bb4673d239b85c7a59fc7b692dba Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 09:37:43 +0200 Subject: [PATCH 017/350] save and dirty state for 3wm --- .../mergeEditor/browser/mergeEditorInput.ts | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 9a3c69d7cc2..b50f8f92516 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -3,13 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { localize } from 'vs/nls'; +import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IUntypedEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export interface MergeEditorInputJSON { anchestor: URI; @@ -18,11 +25,12 @@ export interface MergeEditorInputJSON { result: URI; } -export class MergeEditorInput extends EditorInput { +export class MergeEditorInput extends AbstractTextResourceEditorInput { static readonly ID = 'mergeEditor.Input'; private _model?: MergeEditorModel; + private _outTextModel?: ITextFileEditorModel; constructor( private readonly _anchestor: URI, @@ -31,8 +39,32 @@ export class MergeEditorInput extends EditorInput { private readonly _result: URI, @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService private readonly _textModelService: ITextModelService, + @IEditorService editorService: IEditorService, + @ITextFileService textFileService: ITextFileService, + @ILabelService labelService: ILabelService, + @IFileService fileService: IFileService ) { - super(); + super(_result, undefined, editorService, textFileService, labelService, fileService); + + const modelListener = new DisposableStore(); + const handleDidCreate = (model: ITextFileEditorModel) => { + // TODO@jrieken copied from fileEditorInput.ts + if (isEqual(_result, model.resource)) { + modelListener.clear(); + this._outTextModel = model; + modelListener.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + modelListener.add(model.onDidSaveError(() => this._onDidChangeDirty.fire())); + + modelListener.add(model.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire())); + + modelListener.add(model.onWillDispose(() => { + this._outTextModel = undefined; + modelListener.clear(); + })); + } + }; + textFileService.files.onDidCreate(handleDidCreate, this, modelListener); + textFileService.files.models.forEach(handleDidCreate); } override dispose(): void { @@ -43,11 +75,20 @@ export class MergeEditorInput extends EditorInput { return MergeEditorInput.ID; } - get resource(): URI | undefined { - return this._result; + override getName(): string { + return localize('name', "Merging: {0}", super.getName()); + } + + override get capabilities(): EditorInputCapabilities { + let result = EditorInputCapabilities.Singleton; + if (!this.fileService.hasProvider(this._result) || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) { + result |= EditorInputCapabilities.Readonly; + } + return result; } override async resolve(): Promise { + if (!this._model) { const anchestor = await this._textModelService.createModelReference(this._anchestor); @@ -68,6 +109,8 @@ export class MergeEditorInput extends EditorInput { this._store.add(inputOne); this._store.add(inputTwo); this._store.add(result); + + // result.object. } return this._model; } @@ -90,4 +133,14 @@ export class MergeEditorInput extends EditorInput { result: this._result, }; } + + // ---- FileEditorInput + + override isDirty(): boolean { + return Boolean(this._outTextModel?.isDirty()); + } + + + // implement get/set languageId + // implement get/set encoding } From 8614c14318abbc33bd759bf979d1f0df954b2c58 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 11:52:32 +0200 Subject: [PATCH 018/350] dispose model listener --- src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index b50f8f92516..cb5773e9f86 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -65,6 +65,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { }; textFileService.files.onDidCreate(handleDidCreate, this, modelListener); textFileService.files.models.forEach(handleDidCreate); + this._store.add(modelListener); } override dispose(): void { From 1cd79ce2d97edbf56c464040f707dcd8b53a25bb Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 11:55:51 +0200 Subject: [PATCH 019/350] add grid border color --- src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index c7c4d8e9358..e8f02b90ea2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -23,6 +23,8 @@ import { ITextModel } from 'vs/editor/common/model'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; +import { Color } from 'vs/base/common/color'; export class MergeEditor extends EditorPane { @@ -77,7 +79,7 @@ export class MergeEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { - this._grid = new Grid(this.inputResultView); + this._grid = new Grid(this.inputResultView, { styles: { separatorBorder: this.theme.getColor(settingsSashBorder) ?? Color.transparent } }); this._grid.addView(this.inputOneView, Sizing.Distribute, this.inputResultView, Direction.Up); this._grid.addView(this.inputTwoView, Sizing.Distribute, this.inputOneView, Direction.Right); From 9bcc9a1d3e5798f4b494ce066cbe3a2e2a90f3a3 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 12:17:54 +0200 Subject: [PATCH 020/350] add title area above code editor, add css styles, little :lipstick: --- .../mergeEditor/browser/media/mergeEditor.css | 11 +++++ .../mergeEditor/browser/mergeEditor.ts | 42 ++++++++++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index a4a092d8349..2a1dd749cbb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -2,3 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .merge-editor .code-view>.title { + padding: 0 0 0 10px; + height: 30px; + display: flex; + align-content: center; +} + +.monaco-workbench .merge-editor .code-view>.title .monaco-icon-label { + margin: auto 0; +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index e8f02b90ea2..7029d266c30 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -17,7 +17,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { BugIndicatingError } from 'vs/base/common/errors'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Direction, Grid, IView, IViewSize, LayoutPriority } from 'vs/base/browser/ui/grid/grid'; +import { Direction, Grid, IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { ITextModel } from 'vs/editor/common/model'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -25,6 +25,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ScrollType } from 'vs/editor/common/editorCommon'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { Color } from 'vs/base/common/color'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { localize } from 'vs/nls'; +import { ILabelService } from 'vs/platform/label/common/label'; export class MergeEditor extends EditorPane { @@ -40,6 +43,7 @@ export class MergeEditor extends EditorPane { constructor( @IInstantiationService private readonly instantiation: IInstantiationService, + @ILabelService private readonly _labelService: ILabelService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IThemeService themeService: IThemeService, @@ -79,6 +83,7 @@ export class MergeEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { + parent.classList.add('merge-editor'); this._grid = new Grid(this.inputResultView, { styles: { separatorBorder: this.theme.getColor(settingsSashBorder) ?? Color.transparent } }); this._grid.addView(this.inputOneView, Sizing.Distribute, this.inputResultView, Direction.Up); @@ -99,14 +104,10 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.inputOneView.setModel(model.inputOne); - this.inputTwoView.setModel(model.inputTwo); - this.inputResultView.setModel(model.result); + this.inputOneView.setModel(model.inputOne, localize('yours', 'Yours'), undefined); + this.inputTwoView.setModel(model.inputTwo, localize('theirs', 'Theirs',), undefined); + this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); - console.log(model); - // if (token.isCancellationRequested) { - // return; - // } } override clearInput(): void { @@ -116,36 +117,47 @@ export class MergeEditor extends EditorPane { } class CodeEditorView implements IView { - preferredWidth?: number | undefined; - preferredHeight?: number | undefined; + + // preferredWidth?: number | undefined; + // preferredHeight?: number | undefined; element: HTMLElement = document.createElement('div'); + private _titleElement = document.createElement('div'); + private _editorElement = document.createElement('div'); minimumWidth: number = 10; maximumWidth: number = Number.MAX_SAFE_INTEGER; minimumHeight: number = 10; maximumHeight: number = Number.MAX_SAFE_INTEGER; - priority?: LayoutPriority | undefined; - snap?: boolean | undefined; + // priority?: LayoutPriority | undefined; + // snap?: boolean | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; + private _title = new IconLabel(this._titleElement, { supportIcons: true }); + public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, - this.element, + this._editorElement, { minimap: { enabled: false } }, {} ); + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService ) { + this.element.classList.add('code-view'); + this._titleElement.classList.add('title'); + this.element.appendChild(this._titleElement); + this.element.appendChild(this._editorElement); } - public setModel(model: ITextModel | undefined): void { + public setModel(model: ITextModel, title: string, description: string | undefined): void { this.editor.setModel(model); + this._title.setLabel(title, description); } layout(width: number, height: number, top: number, left: number): void { @@ -153,7 +165,7 @@ class CodeEditorView implements IView { this.element.style.height = `${height}px`; this.element.style.top = `${top}px`; this.element.style.left = `${left}px`; - this.editor.layout({ width, height }); + this.editor.layout({ width, height: height - this._titleElement.clientHeight }); } } From 45abbdf08690a5011dae1eb87980eea005080595 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 12:32:54 +0200 Subject: [PATCH 021/350] ensure default implementation of `clearInput` runs, make sure editor can be re-opened... --- .../workbench/contrib/mergeEditor/browser/mergeEditor.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 7029d266c30..8afe3dbd737 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -110,9 +110,13 @@ export class MergeEditor extends EditorPane { } - override clearInput(): void { + // override clearInput(): void { + // super.clearInput(); + // } - } + // protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { + // console.log('VISISBLE', visible); + // } } From 44acc7887bc9869ffacf49d62a797ce3916bb86b Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 12:37:52 +0200 Subject: [PATCH 022/350] add floating btn for merge (all fake tho...) --- .../workbench/contrib/mergeEditor/browser/mergeEditor.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 8afe3dbd737..ac0265355b6 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -28,6 +28,7 @@ import { Color } from 'vs/base/common/color'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; export class MergeEditor extends EditorPane { @@ -75,6 +76,12 @@ export class MergeEditor extends EditorPane { }); } })); + + // TODO@jrieken make this proper: add menu id and allow extensions to contribute + const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, 'Accept Merge', ''); + acceptBtn.render(); + this._store.add(acceptBtn.onClick(() => { console.log('DO IT'); })); + this._store.add(acceptBtn); } override dispose(): void { From c7fa36480e452bc30b11061e56330d0b68e22f1a Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 12:53:19 +0200 Subject: [PATCH 023/350] implement `getControl` and `scopedContextKeyService` for better interaction with "outside", add `ICodeEditorViewOptions` and make yours/theirs readonly --- .../mergeEditor/browser/mergeEditor.ts | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index ac0265355b6..3a1e6b68079 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -12,7 +12,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { BugIndicatingError } from 'vs/base/common/errors'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; @@ -29,6 +29,8 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; export class MergeEditor extends EditorPane { @@ -36,11 +38,11 @@ export class MergeEditor extends EditorPane { private readonly _sessionDisposables = new DisposableStore(); - private _grid!: Grid; + private _grid!: Grid; - private readonly inputOneView = this.instantiation.createInstance(CodeEditorView); - private readonly inputTwoView = this.instantiation.createInstance(CodeEditorView); - private readonly inputResultView = this.instantiation.createInstance(CodeEditorView); + private readonly inputOneView = this.instantiation.createInstance(CodeEditorView, { readonly: true }); + private readonly inputTwoView = this.instantiation.createInstance(CodeEditorView, { readonly: true }); + private readonly inputResultView = this.instantiation.createInstance(CodeEditorView, { readonly: false }); constructor( @IInstantiationService private readonly instantiation: IInstantiationService, @@ -125,6 +127,27 @@ export class MergeEditor extends EditorPane { // console.log('VISISBLE', visible); // } + // ---- interact with "outside world" via `getControl`, `scopedContextKeyService` + + override getControl(): IEditorControl | undefined { + for (const view of [this.inputOneView, this.inputTwoView, this.inputResultView]) { + if (view.editor.hasWidgetFocus()) { + return view.editor; + } + } + return undefined; + } + + override get scopedContextKeyService(): IContextKeyService | undefined { + const control = this.getControl(); + return isCodeEditor(control) + ? control.invokeWithinContext(accessor => accessor.get(IContextKeyService)) + : undefined; + } +} + +interface ICodeEditorViewOptions { + readonly: boolean; } class CodeEditorView implements IView { @@ -151,14 +174,14 @@ class CodeEditorView implements IView { public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, this._editorElement, - { minimap: { enabled: false } }, + { minimap: { enabled: false }, readOnly: this._options.readonly }, {} ); constructor( - @IInstantiationService - private readonly instantiationService: IInstantiationService + private readonly _options: ICodeEditorViewOptions, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { this.element.classList.add('code-view'); this._titleElement.classList.add('title'); From e4c04ea2e99dc6afc6f2c645ec4541ec8df9ed09 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 13:49:31 +0200 Subject: [PATCH 024/350] - Introduce policy model with file policy - Introduce policy configuration using policy model - Use policy configuration while reading, inspecting and writing configuration - Adopt json settings editor --- .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/code/electron-main/main.ts | 2 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../standalone/browser/standaloneServices.ts | 6 +- .../configuration/common/configuration.ts | 3 + .../common/configurationModels.ts | 82 +++++----- .../common/configurationService.ts | 36 +++-- .../configuration/common/configurations.ts | 143 ++++++++++++++++++ .../test/common/configurationModels.test.ts | 33 ++-- .../test/common/configurationService.test.ts | 26 ++-- src/vs/platform/environment/common/argv.ts | 1 + .../environment/common/environment.ts | 3 + .../environment/common/environmentService.ts | 3 + src/vs/platform/environment/node/argv.ts | 1 + src/vs/platform/policy/common/policy.ts | 112 ++++++++++++++ .../test/common/userDataSyncClient.ts | 2 +- .../node/remoteExtensionHostAgentCli.ts | 2 +- src/vs/server/node/serverServices.ts | 2 +- .../api/common/extHostConfiguration.ts | 2 +- .../test/browser/extHostConfiguration.test.ts | 5 + .../browser/preferencesRenderers.ts | 26 ++++ .../configuration/browser/configuration.ts | 32 ++-- .../browser/configurationService.ts | 32 +++- .../common/configurationEditingService.ts | 10 ++ .../common/configurationModels.ts | 3 +- .../test/common/configurationModels.test.ts | 8 +- 26 files changed, 458 insertions(+), 121 deletions(-) create mode 100644 src/vs/platform/configuration/common/configurations.ts create mode 100644 src/vs/platform/policy/common/policy.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 4dd6d42da9b..aa4c7ad67e7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -222,7 +222,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 762f288daba..7b95aaad618 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -167,7 +167,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, environmentMainService, logService); services.set(IConfigurationService, configurationService); // Lifecycle diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 9495713716e..6ae96fc2047 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -129,7 +129,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index ef4d4407aa9..52803c25a6a 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -29,7 +29,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfiguration'; import { CommandsRegistry, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, ConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { createDecorator, IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -88,6 +88,7 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com import { staticObservableValue } from 'vs/base/common/observableValue'; import 'vs/editor/common/services/languageFeaturesService'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; class SimpleModel implements IResolvedTextEditorModel { @@ -521,7 +522,7 @@ export class StandaloneConfigurationService implements IConfigurationService { private readonly _configuration: Configuration; constructor() { - this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); } getValue(): T; @@ -582,6 +583,7 @@ export class StandaloneConfigurationService implements IConfigurationService { }; return { defaults: emptyModel, + policy: emptyModel, user: emptyModel, workspace: emptyModel, folders: [] diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 6ed2b0c3ef8..4012d405858 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -80,6 +80,7 @@ export interface IConfigurationValue { readonly workspaceValue?: T; readonly workspaceFolderValue?: T; readonly memoryValue?: T; + readonly policyValue?: T; readonly value?: T; readonly default?: { value?: T; override?: T }; @@ -89,6 +90,7 @@ export interface IConfigurationValue { readonly workspace?: { value?: T; override?: T }; readonly workspaceFolder?: { value?: T; override?: T }; readonly memory?: { value?: T; override?: T }; + readonly policy?: { value?: T }; readonly overrideIdentifiers?: string[]; } @@ -163,6 +165,7 @@ export interface IOverrides { export interface IConfigurationData { defaults: IConfigurationModel; + policy: IConfigurationModel; user: IConfigurationModel; workspace: IConfigurationModel; folders: [UriComponents, IConfigurationModel][]; diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 8afa3b7f065..03135dbdbb4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -232,33 +232,6 @@ export class ConfigurationModel implements IConfigurationModel { } } -export class DefaultConfigurationModel extends ConfigurationModel { - - constructor(configurationDefaultsOverrides: IStringDictionary = {}) { - const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keys = Object.keys(properties); - const contents: any = Object.create(null); - const overrides: IOverrides[] = []; - - for (const key in properties) { - const defaultOverrideValue = configurationDefaultsOverrides[key]; - const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; - addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); - } - for (const key of Object.keys(contents)) { - if (OVERRIDE_PROPERTY_REGEX.test(key)) { - overrides.push({ - identifiers: overrideIdentifiersFromKey(key), - keys: Object.keys(contents[key]), - contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), - }); - } - } - - super(contents, keys, overrides); - } -} - export interface ConfigurationParseOptions { scopes: ConfigurationScope[] | undefined; skipRestricted?: boolean; @@ -473,6 +446,7 @@ export class Configuration { constructor( private _defaultConfiguration: ConfigurationModel, + private _policyConfiguration: ConfigurationModel, private _localUserConfiguration: ConfigurationModel, private _remoteUserConfiguration: ConfigurationModel = new ConfigurationModel(), private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), @@ -483,7 +457,7 @@ export class Configuration { } getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): any { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace); return consolidateConfigurationModel.getValue(section); } @@ -511,11 +485,12 @@ export class Configuration { } inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue { - const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); + const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); + const policyValue = this._policyConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); @@ -523,19 +498,21 @@ export class Configuration { const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); const value = consolidateConfigurationModel.getValue(key); - const overrideIdentifiers: string[] = arrays.distinct(arrays.flatten(consolidateConfigurationModel.overrides.map(override => override.identifiers))).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); + const overrideIdentifiers: string[] = arrays.distinct(consolidateConfigurationModel.overrides.map(override => override.identifiers).flat()).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); return { - defaultValue: defaultValue, - userValue: userValue, - userLocalValue: userLocalValue, - userRemoteValue: userRemoteValue, - workspaceValue: workspaceValue, - workspaceFolderValue: workspaceFolderValue, - memoryValue: memoryValue, + defaultValue, + policyValue, + userValue, + userLocalValue, + userRemoteValue, + workspaceValue, + workspaceFolderValue, + memoryValue, value, default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + policy: policyValue !== undefined ? { value: policyValue } : undefined, user: userValue !== undefined ? { value: this.userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userLocal: userLocalValue !== undefined ? { value: this.localUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.localUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, userRemote: userRemoteValue !== undefined ? { value: this.remoteUserConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, @@ -568,6 +545,10 @@ export class Configuration { this._foldersConsolidatedConfigurations.clear(); } + updatePolicyConfiguration(policyConfiguration: ConfigurationModel): void { + this._policyConfiguration = policyConfiguration; + } + updateLocalUserConfiguration(localUserConfiguration: ConfigurationModel): void { this._localUserConfiguration = localUserConfiguration; this._userConfiguration = null; @@ -620,6 +601,15 @@ export class Configuration { return { keys, overrides }; } + compareAndUpdatePolicyConfiguration(policyConfiguration: ConfigurationModel): IConfigurationChange { + const { added, updated, removed } = compare(this._policyConfiguration, policyConfiguration); + const keys = [...added, ...updated, ...removed]; + if (keys.length) { + this.updatePolicyConfiguration(policyConfiguration); + } + return { keys, overrides: [] }; + } + compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange { const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); const keys = [...added, ...updated, ...removed]; @@ -698,9 +688,15 @@ export class Configuration { return this._folderConfigurations; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { + private getConsolidatedConfigurationModel(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); - return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; + if (overrides.overrideIdentifier) { + configurationModel = configurationModel.override(overrides.overrideIdentifier); + } + if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) { + configurationModel = configurationModel.merge(this._policyConfiguration); + } + return configurationModel; } private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { @@ -765,6 +761,11 @@ export class Configuration { overrides: this._defaultConfiguration.overrides, keys: this._defaultConfiguration.keys }, + policy: { + contents: this._policyConfiguration.contents, + overrides: this._policyConfiguration.overrides, + keys: this._policyConfiguration.keys + }, user: { contents: this.userConfiguration.contents, overrides: this.userConfiguration.overrides, @@ -812,13 +813,14 @@ export class Configuration { static parse(data: IConfigurationData): Configuration { const defaultConfiguration = this.parseConfigurationModel(data.defaults); + const policyConfiguration = this.parseConfigurationModel(data.policy); const userConfiguration = this.parseConfigurationModel(data.user); const workspaceConfiguration = this.parseConfigurationModel(data.workspace); const folders: ResourceMap = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); - return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); + return new Configuration(defaultConfiguration, policyConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); } private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel { diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 7860c72ea8e..b3ff50a6d0c 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -9,17 +9,20 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { declare readonly _serviceBrand: undefined; private configuration: Configuration; - private userConfiguration: UserSettings; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; + private readonly userConfiguration: UserSettings; private readonly reloadConfigurationScheduler: RunOnceScheduler; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); @@ -27,20 +30,25 @@ export class ConfigurationService extends Disposable implements IConfigurationSe constructor( private readonly settingsResource: URI, - fileService: IFileService + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService, ) { super(); + this.defaultConfiguration = this._register(new DefaultConfiguration()); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); - this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); + this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reloadConfiguration(), 50)); - this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ defaults, properties }) => this.onDidDefaultConfigurationChange(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(model => this.onDidPolicyConfigurationChange(model))); this._register(this.userConfiguration.onDidChange(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - const userConfiguration = await this.userConfiguration.loadConfiguration(); - this.configuration = new Configuration(new DefaultConfigurationModel(), userConfiguration); + const [defaultModel, policyModel, userModel] = await Promise.all([this.defaultConfiguration.initialize(), this.policyConfiguration.initialize(), this.userConfiguration.loadConfiguration()]); + this.configuration = new Configuration(defaultModel, policyModel, userModel); } getConfigurationData(): IConfigurationData { @@ -89,9 +97,15 @@ export class ConfigurationService extends Disposable implements IConfigurationSe this.trigger(change, previous, ConfigurationTarget.USER); } - private onDidDefaultConfigurationChange(properties: string[]): void { + private onDidDefaultConfigurationChange(defaultConfigurationModel: ConfigurationModel, properties: string[]): void { const previous = this.configuration.toData(); - const change = this.configuration.compareAndUpdateDefaultConfiguration(new DefaultConfigurationModel(), properties); + const change = this.configuration.compareAndUpdateDefaultConfiguration(defaultConfigurationModel, properties); + this.trigger(change, previous, ConfigurationTarget.DEFAULT); + } + + private onDidPolicyConfigurationChange(policyConfiguration: ConfigurationModel): void { + const previous = this.configuration.toData(); + const change = this.configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); this.trigger(change, previous, ConfigurationTarget.DEFAULT); } diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts new file mode 100644 index 00000000000..a86a1f50e49 --- /dev/null +++ b/src/vs/platform/configuration/common/configurations.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { PolicyModel } from 'vs/platform/policy/common/policy'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export class DefaultConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + this._configurationModel = new DefaultConfigurationModel(this.getConfigurationDefaultOverrides()); + } + return this._configurationModel; + } + + async initialize(): Promise { + this._configurationModel = undefined; + this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); + return this.configurationModel; + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + protected onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + this._configurationModel = undefined; + this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + } + + protected getConfigurationDefaultOverrides(): IStringDictionary { + return {}; + } + +} + +export class DefaultConfigurationModel extends ConfigurationModel { + + constructor(configurationDefaultsOverrides: IStringDictionary = {}) { + const properties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys = Object.keys(properties); + const contents: any = Object.create(null); + const overrides: IOverrides[] = []; + + for (const key in properties) { + const defaultOverrideValue = configurationDefaultsOverrides[key]; + const value = defaultOverrideValue !== undefined ? defaultOverrideValue : properties[key].default; + addToValueTree(contents, key, value, message => console.error(`Conflict in default settings: ${message}`)); + } + for (const key of Object.keys(contents)) { + if (OVERRIDE_PROPERTY_REGEX.test(key)) { + overrides.push({ + identifiers: overrideIdentifiersFromKey(key), + keys: Object.keys(contents[key]), + contents: toValuesTree(contents[key], message => console.error(`Conflict in default settings file: ${message}`)), + }); + } + } + + super(contents, keys, overrides); + } +} + +export class PolicyConfiguration extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter()); + readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private readonly policies: PolicyModel; + + constructor( + private readonly defaultConfiguration: DefaultConfiguration, + fileService: IFileService, + environmentService: IEnvironmentService, + logService: ILogService + ) { + super(); + this.policies = new PolicyModel(fileService, environmentService, logService); + } + + private _configurationModel: ConfigurationModel | undefined; + get configurationModel(): ConfigurationModel { + if (!this._configurationModel) { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const keys: string[] = []; + const contents: any = Object.create(null); + for (const key of this.defaultConfiguration.configurationModel.keys) { + const policyName = configurationProperties[key].policy?.name; + if (!policyName) { + continue; + } + const value = this.policies.getPolicy(policyName); + if (value === undefined) { + continue; + } + keys.push(key); + addToValueTree(contents, key, value, message => console.error(`Conflict in policy settings: ${message}`)); + } + this._configurationModel = new ConfigurationModel(contents, keys, []); + } + return this._configurationModel; + } + + async initialize(): Promise { + await this.policies.initialize(); + this._register(this.policies.onDidChange(e => this.onDidChange())); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); + return this.reload(); + } + + reload(): ConfigurationModel { + this._configurationModel = undefined; + return this.configurationModel; + } + + private onDidDefaultConfigurationChange(properties: string[]): void { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + if (properties.some(key => configurationProperties[key].policy?.name)) { + this.onDidChange(); + } + } + + private onDidChange(): void { + this._onDidChangeConfiguration.fire(this.reload()); + } + +} diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index ae1add24a78..9887f80e184 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -6,8 +6,9 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, DefaultConfigurationModel, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; +import { AllKeysConfigurationChangeEvent, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { DefaultConfigurationModel } from 'vs/platform/configuration/common/configurations'; import { Registry } from 'vs/platform/registry/common/platform'; import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; @@ -496,7 +497,7 @@ suite('Configuration', () => { test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.updateValue('a', 2); @@ -506,7 +507,7 @@ suite('Configuration', () => { test('Test update value after inspect', () => { const parser = new ConfigurationModelParser('test'); parser.parse(JSON.stringify({ 'a': 1 })); - const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel()); testObject.inspect('a', {}, undefined); testObject.updateValue('a', 2); @@ -515,7 +516,7 @@ suite('Configuration', () => { }); test('Test compare and update default configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on', })); @@ -532,7 +533,7 @@ suite('Configuration', () => { }); test('Test compare and update user configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateLocalUserConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -555,7 +556,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -578,7 +579,7 @@ suite('Configuration', () => { }); test('Test compare and update workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -601,7 +602,7 @@ suite('Configuration', () => { }); test('Test compare and delete workspace folder configuration', () => { - const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', 'editor.fontSize': 12, @@ -627,7 +628,7 @@ suite('Configuration', () => { suite('ConfigurationChangeEvent', () => { test('changeEvent affecting keys with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 1, 'workbench.editor.enablePreview': false, @@ -653,7 +654,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting keys when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'window.zoomLevel': 2, 'workbench.editor.enablePreview': true, @@ -682,7 +683,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides with new configuration', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'files.autoSave': 'off', '[markdown]': { @@ -724,7 +725,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting overrides when configuration changed', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.editor.enablePreview': true, '[markdown]': { @@ -791,7 +792,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting workspace folders', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })); configuration.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); configuration.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })); @@ -881,7 +882,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent - all', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true })); const data = configuration.toData(); const change = mergeChanges( @@ -976,7 +977,7 @@ suite('ConfigurationChangeEvent', () => { }); test('changeEvent affecting tasks and launches', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); const change = configuration.compareAndUpdateLocalUserConfiguration(toConfigurationModel({ 'launch': { 'configuraiton': {} @@ -999,7 +1000,7 @@ suite('ConfigurationChangeEvent', () => { suite('AllKeysConfigurationChangeEvent', () => { test('changeEvent', () => { - const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel()); + const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel()); configuration.updateDefaultConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'off', '[markdown]': { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 559803b0588..04635ed013c 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -12,9 +12,11 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -22,6 +24,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationService', () => { let fileService: IFileService; + let environmentService: IEnvironmentService; let settingsResource: URI; const disposables: DisposableStore = new DisposableStore(); @@ -30,13 +33,14 @@ suite('ConfigurationService', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); settingsResource = URI.file('settings.json'); + environmentService = new TestInstantiationService().mock(IEnvironmentService) as IEnvironmentService; }); teardown(() => disposables.clear()); test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -49,7 +53,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -68,7 +72,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -78,7 +82,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -87,7 +91,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -100,7 +104,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -116,7 +120,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -155,7 +159,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService)); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -163,7 +167,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); setting = testObject.getValue(); @@ -191,7 +195,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -226,7 +230,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index b2b4500a614..3834247b45b 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,6 +88,7 @@ export interface NativeParsedArgs { 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; 'logsPath'?: string; + 'policy-file'?: string; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 56531a3a784..e0feccde448 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -87,6 +87,9 @@ export interface IEnvironmentService { telemetryLogResource: URI; serviceMachineIdResource: URI; + // --- Policy + policyFile?: URI; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index fcea3f58023..d3214f7036f 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -237,6 +237,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } + @memoize + get policyFile(): URI | undefined { return this.args['policy-file'] ? URI.file(this.args['policy-file']) : undefined; } + get args(): NativeParsedArgs { return this._args; } constructor( diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 062ce48a580..0a69e9cd2c4 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -73,6 +73,7 @@ export const OPTIONS: OptionDescriptions> = { 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] }, + 'policy-file': { type: 'string', cat: 't', description: localize('policyFile', "Path of the file defining policies") }, 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts new file mode 100644 index 00000000000..5f9b1cfafe9 --- /dev/null +++ b/src/vs/platform/policy/common/policy.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from 'vs/base/common/async'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; + +export type PolicyValue = string | boolean | number; +export type Policies = IStringDictionary; + +export class PolicyModel extends Disposable { + readonly _serviceBrand: undefined; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private policies: Policies = {}; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILogService private readonly logService: ILogService, + ) { + super(); + } + + async initialize(): Promise { + const policy = this.environmentService.policyFile ? new FilePolicy(this.environmentService.policyFile, this.fileService, this.logService) : new NullPolicy(); + this.policies = await policy.read(); + this._register(policy.onDidChange(({ changed, policies }) => { + this.policies = policies; + this._onDidChange.fire(changed); + })); + } + + getPolicy(name: string): PolicyValue | undefined { + return this.policies[name]; + } +} + +export interface IPolicy { + readonly onDidChange: Event<{ changed: string[]; policies: Policies }>; + read(): Promise; +} + +class FilePolicy extends Disposable implements IPolicy { + + private policies: Policies = {}; + + private readonly _onDidChange = new Emitter<{ changed: string[]; policies: Policies }>(); + readonly onDidChange = this._onDidChange.event; + + private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); + + constructor( + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + this._register(fileService.watch(file)); + this._register(Event.filter(fileService.onDidFilesChange, e => e.affects(file))(e => this.throttledDelayer.trigger(() => this.onDidFileChange()))); + } + + async read(): Promise { + try { + const content = await this.fileService.readFile(this.file); + this.policies = JSON.parse(content.value.toString()); + } catch (error) { + this.logService.error(error); + this.policies = {}; + } + return this.policies; + } + + private async onDidFileChange(): Promise { + const old = this.policies; + await this.read(); + const changed = this.compare(old, this.policies); + if (changed.length > 0) { + this._onDidChange.fire({ changed, policies: this.policies }); + } + } + + private compare(old: Policies, current: Policies): string[] { + const changed: string[] = []; + for (const key of Object.keys(old)) { + if (old[key] !== current[key]) { + changed.push(key); + } + } + for (const key of Object.keys(current)) { + if (old[key] === undefined) { + changed.push(key); + } + } + return changed; + } + +} + +class NullPolicy extends Disposable implements IPolicy { + readonly onDidChange = Event.None; + async read(): Promise { return {}; } +} diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 293324b83c5..5d3ba1f21bd 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -86,7 +86,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index f1e8986f5c5..170fbafa06a 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -91,7 +91,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a072fa8bb0e..a443d3bc84c 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -107,7 +107,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, environmentService, logService); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 3a45d62f28f..0bd5f8e9752 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -256,7 +256,7 @@ export class ExtHostConfigProvider { return { key, - defaultValue: config.default?.value, + defaultValue: config.policy?.value ?? config.default?.value, globalValue: config.user?.value, workspaceValue: config.workspace?.value, workspaceFolderValue: config.workspaceFolder?.value, diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index 764c1c294b1..950c278a1ff 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -43,6 +43,7 @@ suite('ExtHostConfiguration', function () { function createConfigurationData(contents: any): IConfigurationInitData { return { defaults: new ConfigurationModel(contents), + policy: new ConfigurationModel(), user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), folders: [], @@ -279,6 +280,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -328,6 +330,7 @@ suite('ExtHostConfiguration', function () { 'wordWrap': 'off' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -405,6 +408,7 @@ suite('ExtHostConfiguration', function () { 'lineNumbers': 'on' } }, ['editor.wordWrap']), + policy: new ConfigurationModel(), user: new ConfigurationModel({ 'editor': { 'wordWrap': 'on' @@ -508,6 +512,7 @@ suite('ExtHostConfiguration', function () { 'editor.wordWrap': 'bounded', } }), + policy: new ConfigurationModel(), user: toConfigurationModel({ 'editor.wordWrap': 'bounded', '[typescript]': { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 606a9255884..4d2b985ec74 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -175,6 +175,7 @@ class EditSettingRenderer extends Disposable { constructor(private editor: ICodeEditor, private primarySettingsModel: ISettingsEditorModel, private settingHighlighter: SettingHighlighter, + @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -283,6 +284,9 @@ class EditSettingRenderer extends Disposable { return this.getSettingsAtLineNumber(lineNumber).filter(setting => { const configurationNode = configurationMap[setting.key]; if (configurationNode) { + if (configurationNode.policy && this.configurationService.inspect(setting.key).policyValue !== undefined) { + return false; + } if (this.isDefaultSettings()) { if (setting.key === 'launch') { // Do not show because of https://github.com/microsoft/vscode/issues/32593 @@ -522,6 +526,9 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc for (const setting of section.settings) { const configuration = configurationRegistry[setting.key]; if (configuration) { + if (this.handlePolicyConfiguration(setting, configuration, markerData)) { + continue; + } switch (this.settingsEditorModel.configurationTarget) { case ConfigurationTarget.USER_LOCAL: this.handleLocalUserConfiguration(setting, configuration, markerData); @@ -550,6 +557,25 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc return markerData; } + private handlePolicyConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): boolean { + if (!configuration.policy) { + return false; + } + if (this.configurationService.inspect(setting.key).policyValue === undefined) { + return false; + } + if (this.settingsEditorModel.configurationTarget === ConfigurationTarget.DEFAULT) { + return false; + } + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('unsupportedPolicySetting', "This setting cannot be applied because it is configured in the system policy.") + }); + return true; + } + private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { markerData.push({ diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 2bd9b092c9a..39ed9b5e64c 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult, FileOperation, FileOperationEvent } from 'vs/platform/files/common/files'; -import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, DefaultConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, ConfigurationModelParser, ConfigurationParseOptions, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; @@ -26,8 +26,9 @@ import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { isObject } from 'vs/base/common/types'; +import { DefaultConfiguration as BaseDefaultConfiguration } from 'vs/platform/configuration/common/configurations'; -export class DefaultConfiguration extends Disposable { +export class DefaultConfiguration extends BaseDefaultConfiguration { static readonly DEFAULT_OVERRIDES_CACHE_EXISTS_KEY = 'DefaultOverridesCacheExists'; @@ -35,9 +36,6 @@ export class DefaultConfiguration extends Disposable { private cachedConfigurationDefaultsOverrides: IStringDictionary = {}; private readonly cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; - private readonly _onDidChangeConfiguration = this._register(new Emitter<{ defaults: ConfigurationModel; properties: string[] }>()); - readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private updateCache: boolean = false; constructor( @@ -50,27 +48,20 @@ export class DefaultConfiguration extends Disposable { } } - private _configurationModel: ConfigurationModel | undefined; - get configurationModel(): ConfigurationModel { - if (!this._configurationModel) { - this._configurationModel = new DefaultConfigurationModel(this.cachedConfigurationDefaultsOverrides); - } - return this._configurationModel; + protected override getConfigurationDefaultOverrides(): IStringDictionary { + return this.cachedConfigurationDefaultsOverrides; } - async initialize(): Promise { + override async initialize(): Promise { await this.initializeCachedConfigurationDefaultsOverrides(); - this._configurationModel = undefined; - this._register(this.configurationRegistry.onDidUpdateConfiguration(({ properties, defaultsOverrides }) => this.onDidUpdateConfiguration(properties, defaultsOverrides))); - return this.configurationModel; + return super.initialize(); } - reload(): ConfigurationModel { + override reload(): ConfigurationModel { this.updateCache = true; this.cachedConfigurationDefaultsOverrides = {}; - this._configurationModel = undefined; this.updateCachedConfigurationDefaultsOverrides(); - return this.configurationModel; + return super.reload(); } private initiaizeCachedConfigurationDefaultsOverridesPromise: Promise | undefined; @@ -92,9 +83,8 @@ export class DefaultConfiguration extends Disposable { return this.initiaizeCachedConfigurationDefaultsOverridesPromise; } - private onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { - this._configurationModel = undefined; - this._onDidChangeConfiguration.fire({ defaults: this.configurationModel, properties }); + protected override onDidUpdateConfiguration(properties: string[], defaultsOverrides?: boolean): void { + super.onDidUpdateConfiguration(properties, defaultsOverrides); if (defaultsOverrides) { this.updateCachedConfigurationDefaultsOverrides(); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 45af17cc175..a4c11121290 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,6 +13,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -54,7 +55,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private readonly configurationCache: IConfigurationCache; private _configuration: Configuration; private initialized: boolean = false; - private defaultConfiguration: DefaultConfiguration; + private readonly defaultConfiguration: DefaultConfiguration; + private readonly policyConfiguration: PolicyConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -109,12 +111,13 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); - this.defaultConfiguration = new DefaultConfiguration(configurationCache, environmentService); + this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; this.logService = logService; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), this.workspace); this.cachedFolderConfigs = new ResourceMap(); this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService, uriIdentityService, logService)); this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration))); @@ -138,6 +141,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat })); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties, defaults }) => this.onDefaultConfigurationChanged(defaults, properties))); + this._register(this.policyConfiguration.onDidChangeConfiguration(configurationModel => this.onPolicyConfigurationChanged(configurationModel))); this.workspaceEditingQueue = new Queue(); } @@ -572,12 +576,18 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private async initializeConfiguration(): Promise { await this.defaultConfiguration.initialize(); - mark('code/willInitUserConfiguration'); - const { local, remote } = await this.initializeUserConfiguration(); - mark('code/didInitUserConfiguration'); + const [, user] = await Promise.all([ + this.policyConfiguration.initialize(), + (async () => { + mark('code/willInitUserConfiguration'); + const result = await this.initializeUserConfiguration(); + mark('code/didInitUserConfiguration'); + return result; + })() + ]); mark('code/willInitWorkspaceConfiguration'); - await this.loadConfiguration(local, remote); + await this.loadConfiguration(user.local, user.remote); mark('code/didInitWorkspaceConfiguration'); } @@ -640,7 +650,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); + this._configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, userConfigurationModel, remoteUserConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (this.initialized) { const change = this._configuration.compare(currentConfiguration); @@ -692,6 +702,12 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } } + private onPolicyConfigurationChanged(policyConfiguration: ConfigurationModel): void { + const previous = { data: this._configuration.toData(), workspace: this.workspace }; + const change = this._configuration.compareAndUpdatePolicyConfiguration(policyConfiguration); + this.triggerConfigurationChange(change, previous, ConfigurationTarget.DEFAULT); + } + private onLocalUserConfigurationChanged(userConfiguration: ConfigurationModel): void { const previous = { data: this._configuration.toData(), workspace: this.workspace }; const change = this._configuration.compareAndUpdateLocalUserConfiguration(userConfiguration); diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 3e4e805c231..bf0d5f3adde 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -92,6 +92,11 @@ export const enum ConfigurationEditingErrorCode { */ ERROR_INVALID_CONFIGURATION, + /** + * Error when trying to write a policy configuration + */ + ERROR_POLICY_CONFIGURATION, + /** * Internal Error. */ @@ -359,6 +364,7 @@ export class ConfigurationEditingService { switch (error) { // API constraints + case ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION: return nls.localize('errorPolicyConfiguration', "Unable to write {0} because it is configured in system policy.", operation.key); case ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY: return nls.localize('errorUnknownKey', "Unable to write to {0} because {1} is not a registered configuration.", this.stringifyTarget(target), operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION: return nls.localize('errorInvalidWorkspaceConfigurationApplication', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key); case ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_MACHINE: return nls.localize('errorInvalidWorkspaceConfigurationMachine', "Unable to write {0} to Workspace Settings. This setting can be written only into User settings.", operation.key); @@ -492,6 +498,10 @@ export class ConfigurationEditingService { private async validate(target: EditableConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationUpdateOverrides): Promise { + if (this.configurationService.inspect(operation.key).policyValue !== undefined) { + throw this.toConfigurationEditingError(ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION, target, operation); + } + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const configurationScope = configurationProperties[operation.key]?.scope; diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 95a1d40ad77..807e96399df 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -98,6 +98,7 @@ export class Configuration extends BaseConfiguration { constructor( defaults: ConfigurationModel, + policy: ConfigurationModel, localUser: ConfigurationModel, remoteUser: ConfigurationModel, workspaceConfiguration: ConfigurationModel, @@ -105,7 +106,7 @@ export class Configuration extends BaseConfiguration { memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: ResourceMap, private readonly _workspace?: Workspace) { - super(defaults, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); + super(defaults, policy, localUser, remoteUser, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } override getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index 228cc13113a..7f391a11ad3 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -155,14 +155,14 @@ suite('Workspace Configuration', () => { test('Test compare same configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); @@ -176,14 +176,14 @@ suite('Workspace Configuration', () => { test('Test compare different configurations', () => { const workspace = new Workspace('a', [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }), new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }), new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })]); - const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration1 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration1.updateDefaultConfiguration(defaultConfigurationModel); configuration1.updateLocalUserConfiguration(toConfigurationModel({ 'window.title': 'native', '[typescript]': { 'editor.insertSpaces': false } })); configuration1.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.lineNumbers': 'on' })); configuration1.updateFolderConfiguration(URI.file('folder1'), toConfigurationModel({ 'editor.fontSize': 14 })); configuration1.updateFolderConfiguration(URI.file('folder2'), toConfigurationModel({ 'editor.wordWrap': 'on' })); - const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); + const configuration2 = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap(), workspace); configuration2.updateDefaultConfiguration(defaultConfigurationModel); configuration2.updateLocalUserConfiguration(toConfigurationModel({ 'workbench.enableTabs': true, '[typescript]': { 'editor.insertSpaces': true } })); configuration2.updateWorkspaceConfiguration(toConfigurationModel({ 'editor.fontSize': 11 })); From 94da754e273c33fdaa93f84c28240200cd1bc5d0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 14:04:00 +0200 Subject: [PATCH 025/350] make sure policy value is undefined if empty --- src/vs/platform/configuration/common/configurationModels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 03135dbdbb4..de218cadea1 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -490,7 +490,7 @@ export class Configuration { const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._defaultConfiguration.freeze().getValue(key); - const policyValue = this._policyConfiguration.freeze().getValue(key); + const policyValue = this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration.freeze().getValue(key); const userValue = overrides.overrideIdentifier ? this.userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.userConfiguration.freeze().getValue(key); const userLocalValue = overrides.overrideIdentifier ? this.localUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.localUserConfiguration.freeze().getValue(key); const userRemoteValue = overrides.overrideIdentifier ? this.remoteUserConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this.remoteUserConfiguration.freeze().getValue(key); From 6086f78bfdb1a243d290137db5e731429c28dcd5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 14:04:15 +0200 Subject: [PATCH 026/350] fix tests --- .../configuration/test/common/configurationModels.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 9887f80e184..859cd0c28a1 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -487,7 +487,7 @@ suite('Configuration', () => { const defaultConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 1 }, '[l2]': { 'b': 1 } }); const userConfigurationModel = parseConfigurationModel({ '[l3]': { 'a': 2 } }); const workspaceConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 3 }, '[l4]': { 'a': 3 } }); - const testObject: Configuration = new Configuration(defaultConfigurationModel, userConfigurationModel, new ConfigurationModel(), workspaceConfigurationModel); + const testObject: Configuration = new Configuration(defaultConfigurationModel, new ConfigurationModel(), userConfigurationModel, workspaceConfigurationModel); const { overrideIdentifiers } = testObject.inspect('a', {}, undefined); From 364240765b6cc5265c6341b87818a48c62ffa325 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 10 May 2022 14:48:02 +0200 Subject: [PATCH 027/350] refactor policy service --- .../configuration/common/configurations.ts | 13 +- .../policy/common/filePolicyService.ts | 91 ++++++++++++ src/vs/platform/policy/common/policy.ts | 135 +++++------------- 3 files changed, 133 insertions(+), 106 deletions(-) create mode 100644 src/vs/platform/policy/common/filePolicyService.ts diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index a86a1f50e49..ed855abf2ab 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -12,7 +12,8 @@ import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRID import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { PolicyModel } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -82,7 +83,7 @@ export class PolicyConfiguration extends Disposable { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private readonly policies: PolicyModel; + private readonly policyService: IPolicyService; constructor( private readonly defaultConfiguration: DefaultConfiguration, @@ -91,7 +92,7 @@ export class PolicyConfiguration extends Disposable { logService: ILogService ) { super(); - this.policies = new PolicyModel(fileService, environmentService, logService); + this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } private _configurationModel: ConfigurationModel | undefined; @@ -105,7 +106,7 @@ export class PolicyConfiguration extends Disposable { if (!policyName) { continue; } - const value = this.policies.getPolicy(policyName); + const value = this.policyService.getPolicyValue(policyName); if (value === undefined) { continue; } @@ -118,8 +119,8 @@ export class PolicyConfiguration extends Disposable { } async initialize(): Promise { - await this.policies.initialize(); - this._register(this.policies.onDidChange(e => this.onDidChange())); + await this.policyService.initialize(); + this._register(this.policyService.onDidChange(e => this.onDidChange())); this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); return this.reload(); } diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts new file mode 100644 index 00000000000..0cfa9e1588c --- /dev/null +++ b/src/vs/platform/policy/common/filePolicyService.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 { ThrottledDelayer } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isObject } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +function keysDiff(a: Map, b: Map): string[] { + const result: string[] = []; + + for (const key of Iterable.concat(a.keys(), b.keys())) { + if (a.get(key) !== b.get(key)) { + result.push(key); + } + } + + return result; +} + +export class FilePolicyService extends Disposable implements IPolicyService { + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); + + constructor( + private readonly file: URI, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService + ) { + super(); + + const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); + this._register(fileService.watch(file)); + this._register(onDidChangePolicyFile(this.refresh, this)); + } + + async initialize(): Promise { + await this.doRefresh(); + } + + private refresh(): void { + this.throttledDelayer.trigger(() => this.doRefresh()); + } + + private async read(): Promise { + const policies: Policies = new Map(); + + try { + const content = await this.fileService.readFile(this.file); + const raw = JSON.parse(content.value.toString()); + + if (!isObject(raw)) { + throw new Error('Policy file isn\'t a JSON object'); + } + + for (const key of Object.keys(raw)) { + policies.set(key, raw[key]); + } + } catch (error) { + this.logService.error(`[FilePolicyService] Failed to read policies`, error); + } + + return policies; + } + + private async doRefresh(): Promise { + const policies = await this.read(); + const diff = keysDiff(this.policies, policies); + this.policies = policies; + + if (diff.length > 0) { + this._onDidChange.fire(diff); + } + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 5f9b1cfafe9..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -3,110 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ThrottledDelayer } from 'vs/base/common/async'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; +import { Event } from 'vs/base/common/event'; +export type PolicyName = string; export type PolicyValue = string | boolean | number; -export type Policies = IStringDictionary; +export type Policies = Map; -export class PolicyModel extends Disposable { - readonly _serviceBrand: undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - private policies: Policies = {}; - - constructor( - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILogService private readonly logService: ILogService, - ) { - super(); - } - - async initialize(): Promise { - const policy = this.environmentService.policyFile ? new FilePolicy(this.environmentService.policyFile, this.fileService, this.logService) : new NullPolicy(); - this.policies = await policy.read(); - this._register(policy.onDidChange(({ changed, policies }) => { - this.policies = policies; - this._onDidChange.fire(changed); - })); - } - - getPolicy(name: string): PolicyValue | undefined { - return this.policies[name]; - } +export interface IPolicyService { + readonly onDidChange: Event; + initialize(): Promise; + getPolicyValue(name: PolicyName): PolicyValue | undefined; } -export interface IPolicy { - readonly onDidChange: Event<{ changed: string[]; policies: Policies }>; - read(): Promise; -} - -class FilePolicy extends Disposable implements IPolicy { - - private policies: Policies = {}; - - private readonly _onDidChange = new Emitter<{ changed: string[]; policies: Policies }>(); - readonly onDidChange = this._onDidChange.event; - - private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); - - constructor( - private readonly file: URI, - @IFileService private readonly fileService: IFileService, - @ILogService private readonly logService: ILogService - ) { - super(); - this._register(fileService.watch(file)); - this._register(Event.filter(fileService.onDidFilesChange, e => e.affects(file))(e => this.throttledDelayer.trigger(() => this.onDidFileChange()))); - } - - async read(): Promise { - try { - const content = await this.fileService.readFile(this.file); - this.policies = JSON.parse(content.value.toString()); - } catch (error) { - this.logService.error(error); - this.policies = {}; - } - return this.policies; - } - - private async onDidFileChange(): Promise { - const old = this.policies; - await this.read(); - const changed = this.compare(old, this.policies); - if (changed.length > 0) { - this._onDidChange.fire({ changed, policies: this.policies }); - } - } - - private compare(old: Policies, current: Policies): string[] { - const changed: string[] = []; - for (const key of Object.keys(old)) { - if (old[key] !== current[key]) { - changed.push(key); - } - } - for (const key of Object.keys(current)) { - if (old[key] === undefined) { - changed.push(key); - } - } - return changed; - } - -} - -class NullPolicy extends Disposable implements IPolicy { +export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; - async read(): Promise { return {}; } + async initialize() { } + getPolicyValue() { return undefined; } +} + +export class MultiPolicyService implements IPolicyService { + + readonly onDidChange: Event; + + constructor(private policyServices: readonly IPolicyService[]) { + this.onDidChange = Event.any(...policyServices.map(p => p.onDidChange)); + } + + async initialize() { + await Promise.all(this.policyServices.map(p => p.initialize())); + } + + getPolicyValue(name: PolicyName) { + for (const policyService of this.policyServices) { + const result = policyService.getPolicyValue(name); + + if (typeof result !== 'undefined') { + return result; + } + } + + return undefined; + } } From 638ba65eccc030511fab770d731a348cc77360a4 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 15:04:22 +0200 Subject: [PATCH 028/350] rename `testMergeEditor` to `_open.mergeEditor` --- .../mergeEditor/browser/mergeEditor.contribution.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index c1fc3b0fbf2..a9446f8a183 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -40,19 +40,18 @@ registerAction2(class Foo extends Action2 { constructor() { super({ - id: 'testMergeEditor', - title: '3wm', - f1: true + id: '_open.mergeEditor', + title: localize('title', "Open Merge Editor"), }); } run(accessor: ServicesAccessor, ...args: any[]): void { const validatedArgs = ITestMergeEditorArgs.validate(args[0]); - function normalize(uri: URI | string): URI { + function normalize(uri: URI | UriComponents | string): URI { if (typeof uri === 'string') { return URI.parse(uri); } else { - return uri; + return URI.revive(uri); } } From 5971d3188148969dfd101dfccad134b313919368 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 15:09:35 +0200 Subject: [PATCH 029/350] (prototype) use `_open.mergeEditor` from git extension for files under conflict --- extensions/git/package.json | 3 +- extensions/git/src/main.ts | 4 ++- extensions/git/src/mergeInfoProvider.ts | 47 +++++++++++++++++++++++++ extensions/git/src/repository.ts | 15 +++++++- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 extensions/git/src/mergeInfoProvider.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 3517a2803d9..2d3da8735b3 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -22,7 +22,8 @@ ], "activationEvents": [ "*", - "onFileSystem:git" + "onFileSystem:git", + "onFileSystem:git-show" ], "extensionDependencies": [ "vscode.git-base" diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 24768c1dba4..4977cd4058c 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,6 +25,7 @@ import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; import { OutputChannelLogger } from './log'; +import { GitMergeShowContentProvider } from './mergeInfoProvider'; const deactivateTasks: { (): Promise }[] = []; @@ -102,7 +103,8 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu new GitFileSystemProvider(model), new GitDecorations(model), new GitProtocolHandler(), - new GitTimelineProvider(model, cc) + new GitTimelineProvider(model, cc), + new GitMergeShowContentProvider(model) ); checkGitVersion(info); diff --git a/extensions/git/src/mergeInfoProvider.ts b/extensions/git/src/mergeInfoProvider.ts new file mode 100644 index 00000000000..83853a18f47 --- /dev/null +++ b/extensions/git/src/mergeInfoProvider.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocumentContentProvider, Uri, workspace } from 'vscode'; +import { Model } from './model'; + +export class GitMergeShowContentProvider implements TextDocumentContentProvider { + + static readonly Scheme = 'git-show'; + + readonly dispose: () => void; + + constructor(private model: Model) { + const reg = workspace.registerTextDocumentContentProvider(GitMergeShowContentProvider.Scheme, this); + this.dispose = reg.dispose.bind(reg); + } + + async provideTextDocumentContent(uri: Uri): Promise { + await this.model.isInitialized; + + const repository = this.model.getRepository(uri); + if (!repository) { + return undefined; + } + + if (!/^:[123]$/.test(uri.query)) { + return undefined; + } + + try { + return await repository.show(uri.query, uri.fsPath); + } catch (error) { + console.error(error); + return undefined; + } + } +} + +export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } { + return { + base: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':1' }), + ours: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':2' }), + theirs: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':3' }), + }; +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c7962fb1a62..6d603936b80 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -21,6 +21,7 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; +import { toMergeUris } from './mergeInfoProvider'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -602,7 +603,19 @@ class ResourceCommandResolver { resolveChangeCommand(resource: Resource): Command { const title = this.getTitle(resource); - if (!resource.leftUri) { + if (!resource.leftUri && resource.rightUri && resource.type === Status.BOTH_MODIFIED) { + const mergeUris = toMergeUris(resource.rightUri); + return { + command: '_open.mergeEditor', + title: localize('open.merge', "Open Merge"), + arguments: [{ + ancestor: mergeUris.base, + input1: mergeUris.ours, + input2: mergeUris.theirs, + output: resource.rightUri + }] + }; + } else if (!resource.leftUri) { return { command: 'vscode.open', title: localize('open', "Open"), From 983fc7a6b6c9da3272219ab00a59ce5146a8afbf Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 10 May 2022 16:14:28 +0200 Subject: [PATCH 030/350] connect floating button to a menu and contribute to that from git --- extensions/git/package.json | 12 +++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 13 +++++++ src/vs/platform/actions/common/actions.ts | 1 + .../browser/mergeEditor.contribution.ts | 3 -- .../mergeEditor/browser/mergeEditor.ts | 35 +++++++++++++++---- .../actions/common/menusExtensionPoint.ts | 6 ++++ .../common/extensionsApiProposals.ts | 1 + ...de.proposed.contribMergeEditorToolbar.d.ts | 6 ++++ 9 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts diff --git a/extensions/git/package.json b/extensions/git/package.json index 2d3da8735b3..ced39d466d1 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -11,6 +11,7 @@ "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "enabledApiProposals": [ "diffCommand", + "contribMergeEditorToolbar", "contribViewsWelcome", "scmActionButton", "scmSelectedProvider", @@ -550,6 +551,11 @@ "command": "git.api.getRemoteSources", "title": "%command.api.getRemoteSources%", "category": "Git API" + }, + { + "command": "git.acceptMerge", + "title": "%command.git.acceptMerge%", + "category": "Git" } ], "keybindings": [ @@ -1419,6 +1425,12 @@ "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" } ], + "merge/toolbar": [ + { + "command": "git.acceptMerge", + "when": "isMergeEditor" + } + ], "scm/change/title": [ { "command": "git.stageChange", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 523b9945928..2a1ec856252 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -97,6 +97,7 @@ "command.api.getRepositories": "Get Repositories", "command.api.getRepositoryState": "Get Repository State", "command.api.getRemoteSources": "Get Remote Sources", + "command.git.acceptMerge": "Accept Merge", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows). This can also be an array of string values containing multiple paths to look up.", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a3479a0b3b5..93cf4df895a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1033,6 +1033,19 @@ export class CommandCenter { await this._stageChanges(textEditor, selectedChanges); } + @command('git.acceptMerge') + async acceptMerge(uri: Uri | unknown): Promise { + // TODO@jrieken make this proper, needs help from SCM folks + if (!(uri instanceof Uri)) { + return; + } + const repository = this.model.getRepository(uri); + if (!repository) { + return; + } + await repository.add([uri]); + } + private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise { const modifiedDocument = textEditor.document; const modifiedUri = modifiedDocument.uri; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e97c241b95c..aef37651a7d 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -158,6 +158,7 @@ export class MenuId { static readonly WebviewContext = new MenuId('WebviewContext'); static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); static readonly NewFile = new MenuId('NewFile'); + static readonly MergeToolbar = new MenuId('MergeToolbar'); readonly id: number; readonly _debugName: string; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index a9446f8a183..88fc90c9d2a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -17,9 +17,6 @@ import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/merge import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MergeEditorSerializer } from './mergeEditorSerializer'; - -//#region Editor Descriptior - Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( MergeEditor, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 3a1e6b68079..d3a8f704b36 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -29,8 +29,13 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IAction } from 'vs/base/common/actions'; + +export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export class MergeEditor extends EditorPane { @@ -47,12 +52,16 @@ export class MergeEditor extends EditorPane { constructor( @IInstantiationService private readonly instantiation: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, + @IMenuService private readonly _menuService: IMenuService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IThemeService themeService: IThemeService, ) { super(MergeEditor.ID, telemetryService, themeService, storageService); + ctxIsMergeEditor.bindTo(_contextKeyService).set(true); + const reentrancyBarrier = new ReentrancyBarrier(); this._store.add(this.inputOneView.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { @@ -80,10 +89,25 @@ export class MergeEditor extends EditorPane { })); // TODO@jrieken make this proper: add menu id and allow extensions to contribute - const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, 'Accept Merge', ''); - acceptBtn.render(); - this._store.add(acceptBtn.onClick(() => { console.log('DO IT'); })); - this._store.add(acceptBtn); + const toolbarMenu = this._menuService.createMenu(MenuId.MergeToolbar, this._contextKeyService); + const toolbarMenuDisposables = new DisposableStore(); + const toolbarMenuRender = () => { + toolbarMenuDisposables.clear(); + + const actions: IAction[] = []; + createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions); + if (actions.length > 0) { + const [first] = actions; + const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id); + toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri))); + toolbarMenuDisposables.add(acceptBtn); + acceptBtn.render(); + } + }; + this._store.add(toolbarMenu); + this._store.add(toolbarMenuDisposables); + this._store.add(toolbarMenu.onDidChange(toolbarMenuRender)); + toolbarMenuRender(); } override dispose(): void { @@ -116,7 +140,6 @@ export class MergeEditor extends EditorPane { this.inputOneView.setModel(model.inputOne, localize('yours', 'Yours'), undefined); this.inputTwoView.setModel(model.inputTwo, localize('theirs', 'Theirs',), undefined); this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); - } // override clearInput(): void { diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 7e06c120f95..b43e964676d 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -252,6 +252,12 @@ const apiMenus: IAPIMenu[] = [ supportsSubmenus: false, proposed: 'inlineCompletions' }, + { + key: 'merge/toolbar', + id: MenuId.MergeToolbar, + description: localize('merge.toolbar', "The prominent botton in the merge editor"), + proposed: 'contribMergeEditorToolbar' + } ]; namespace schema { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 4b51252484a..bf9cf9d7024 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({ commentsResolvedState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts', contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', + contribMergeEditorToolbar: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts b/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts new file mode 100644 index 00000000000..323ff90cecb --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `mergeEditor/toolbar` menu From d4a3656ab354e17bc047de4590d0b9d58c3c73e5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 10 May 2022 20:07:24 +0200 Subject: [PATCH 031/350] smart/scoped update when policy values change --- .../common/configurationModels.ts | 14 ++- .../configuration/common/configurations.ts | 92 +++++++++++-------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index de218cadea1..ad704762f68 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -21,7 +21,7 @@ import { Workspace } from 'vs/platform/workspace/common/workspace'; export class ConfigurationModel implements IConfigurationModel { - private isFrozen: boolean = false; + private frozen: boolean = false; private readonly overrideConfigurations = new Map(); constructor( @@ -47,6 +47,10 @@ export class ConfigurationModel implements IConfigurationModel { return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0; } + isFrozen(): boolean { + return this.frozen; + } + getValue(section: string | undefined): V { return section ? getConfigurationValue(this.contents, section) : this.contents; } @@ -113,10 +117,14 @@ export class ConfigurationModel implements IConfigurationModel { } freeze(): ConfigurationModel { - this.isFrozen = true; + this.frozen = true; return this; } + clone(): ConfigurationModel { + return new ConfigurationModel(objects.deepClone(this.contents), [...this.keys], objects.deepClone(this.overrides)); + } + private createOverrideConfigurationModel(identifier: string): ConfigurationModel { const overrideContents = this.getContentsForOverrideIdentifer(identifier); @@ -161,7 +169,7 @@ export class ConfigurationModel implements IConfigurationModel { } private checkAndFreeze(data: T): T { - if (this.isFrozen && !Object.isFrozen(data)) { + if (this.frozen && !Object.isFrozen(data)) { return objects.deepFreeze(data); } return data; diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index ed855abf2ab..9e9603e9c61 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { coalesce } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; @@ -13,7 +15,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService, PolicyName } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -84,6 +86,10 @@ export class PolicyConfiguration extends Disposable { readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; private readonly policyService: IPolicyService; + private readonly policyNamesToKeys = new Map(); + + private _configurationModel = new ConfigurationModel(); + get configurationModel() { return this._configurationModel; } constructor( private readonly defaultConfiguration: DefaultConfiguration, @@ -95,50 +101,56 @@ export class PolicyConfiguration extends Disposable { this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } - private _configurationModel: ConfigurationModel | undefined; - get configurationModel(): ConfigurationModel { - if (!this._configurationModel) { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keys: string[] = []; - const contents: any = Object.create(null); - for (const key of this.defaultConfiguration.configurationModel.keys) { - const policyName = configurationProperties[key].policy?.name; - if (!policyName) { - continue; - } - const value = this.policyService.getPolicyValue(policyName); - if (value === undefined) { - continue; - } - keys.push(key); - addToValueTree(contents, key, value, message => console.error(`Conflict in policy settings: ${message}`)); - } - this._configurationModel = new ConfigurationModel(contents, keys, []); - } + async initialize(): Promise { + await this.policyService.initialize(); + this.updateKeys(this.defaultConfiguration.configurationModel.keys, false); + this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.updateKeys(properties, true))); return this._configurationModel; } - async initialize(): Promise { - await this.policyService.initialize(); - this._register(this.policyService.onDidChange(e => this.onDidChange())); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.onDidDefaultConfigurationChange(properties))); - return this.reload(); - } - - reload(): ConfigurationModel { - this._configurationModel = undefined; - return this.configurationModel; - } - - private onDidDefaultConfigurationChange(properties: string[]): void { + private updateKeys(keys: string[], trigger: boolean): void { const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - if (properties.some(key => configurationProperties[key].policy?.name)) { - this.onDidChange(); + const keyPolicyNamePairs: [string, PolicyName][] = coalesce(keys.map(key => { + const policyName = configurationProperties[key].policy?.name; + return policyName ? [key, policyName] : undefined; + })); + this.update(keyPolicyNamePairs, trigger); + } + + private onDidChangePolicies(policyNames: readonly PolicyName[]): void { + const keyPolicyNamePairs: [string, PolicyName][] = coalesce(policyNames.map(policyName => { + const key = this.policyNamesToKeys.get(policyName); + return key ? [key, policyName] : undefined; + })); + this.update(keyPolicyNamePairs, true); + } + + private update(keyPolicyNamePairs: [string, PolicyName][], trigger: boolean): void { + if (!keyPolicyNamePairs.length) { + return; + } + + const updated: string[] = []; + this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + const isEmpty = this._configurationModel.isEmpty(); + for (const [key, policyName] of keyPolicyNamePairs) { + this.policyNamesToKeys.set(policyName, key); + const value = this.policyService.getPolicyValue(policyName); + if (!isEmpty && equals(this._configurationModel.getValue(key), value)) { + continue; + } + if (value === undefined) { + this._configurationModel.removeValue(key); + } else { + this._configurationModel.setValue(key, value); + } + updated.push(key); + } + + if (updated.length && trigger) { + this._onDidChangeConfiguration.fire(this._configurationModel); } } - private onDidChange(): void { - this._onDidChangeConfiguration.fire(this.reload()); - } - } From 02f42326ab9c83b822e1860e528457c08fa4f40a Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 11 May 2022 12:03:41 +0200 Subject: [PATCH 032/350] use descriptive view sizing for grid --- .../mergeEditor/browser/mergeEditor.ts | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index d3a8f704b36..c300d43dd54 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -17,8 +17,8 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { BugIndicatingError } from 'vs/base/common/errors'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Direction, Grid, IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; +import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { ITextModel } from 'vs/editor/common/model'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -117,10 +117,29 @@ export class MergeEditor extends EditorPane { protected createEditor(parent: HTMLElement): void { parent.classList.add('merge-editor'); - this._grid = new Grid(this.inputResultView, { styles: { separatorBorder: this.theme.getColor(settingsSashBorder) ?? Color.transparent } }); - this._grid.addView(this.inputOneView, Sizing.Distribute, this.inputResultView, Direction.Up); - this._grid.addView(this.inputTwoView, Sizing.Distribute, this.inputOneView, Direction.Right); + this._grid = SerializableGrid.from({ + orientation: Orientation.VERTICAL, + size: 100, + groups: [ + { + size: 38, + groups: [{ + data: this.inputOneView + }, { + data: this.inputTwoView + }] + }, + { + size: 62, + data: this.inputResultView + }, + ] + }, { + styles: { separatorBorder: this.theme.getColor(settingsSashBorder) ?? Color.transparent }, + proportionalLayout: true + }); + reset(parent, this._grid.element); } From abd3e66bac0970c2a87c627903053f2f4f28da8d Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 11 May 2022 12:04:04 +0200 Subject: [PATCH 033/350] use git-fs to read base, ours, and theirs --- extensions/git/src/main.ts | 4 +-- extensions/git/src/mergeInfoProvider.ts | 47 ------------------------- extensions/git/src/repository.ts | 3 +- extensions/git/src/uri.ts | 11 ++++++ 4 files changed, 13 insertions(+), 52 deletions(-) delete mode 100644 extensions/git/src/mergeInfoProvider.ts diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 4977cd4058c..24768c1dba4 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,7 +25,6 @@ import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; import { OutputChannelLogger } from './log'; -import { GitMergeShowContentProvider } from './mergeInfoProvider'; const deactivateTasks: { (): Promise }[] = []; @@ -103,8 +102,7 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu new GitFileSystemProvider(model), new GitDecorations(model), new GitProtocolHandler(), - new GitTimelineProvider(model, cc), - new GitMergeShowContentProvider(model) + new GitTimelineProvider(model, cc) ); checkGitVersion(info); diff --git a/extensions/git/src/mergeInfoProvider.ts b/extensions/git/src/mergeInfoProvider.ts deleted file mode 100644 index 83853a18f47..00000000000 --- a/extensions/git/src/mergeInfoProvider.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TextDocumentContentProvider, Uri, workspace } from 'vscode'; -import { Model } from './model'; - -export class GitMergeShowContentProvider implements TextDocumentContentProvider { - - static readonly Scheme = 'git-show'; - - readonly dispose: () => void; - - constructor(private model: Model) { - const reg = workspace.registerTextDocumentContentProvider(GitMergeShowContentProvider.Scheme, this); - this.dispose = reg.dispose.bind(reg); - } - - async provideTextDocumentContent(uri: Uri): Promise { - await this.model.isInitialized; - - const repository = this.model.getRepository(uri); - if (!repository) { - return undefined; - } - - if (!/^:[123]$/.test(uri.query)) { - return undefined; - } - - try { - return await repository.show(uri.query, uri.fsPath); - } catch (error) { - console.error(error); - return undefined; - } - } -} - -export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } { - return { - base: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':1' }), - ours: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':2' }), - theirs: uri.with({ scheme: GitMergeShowContentProvider.Scheme, query: ':3' }), - }; -} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6d603936b80..4a81c8fac64 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -13,7 +13,7 @@ import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git'; import { StatusBarCommands } from './statusbar'; -import { toGitUri } from './uri'; +import { toGitUri, toMergeUris } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { LogLevel, OutputChannelLogger } from './log'; @@ -21,7 +21,6 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; -import { toMergeUris } from './mergeInfoProvider'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 94e6b5e38ae..5694c920d6b 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -51,3 +51,14 @@ export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Ur query: JSON.stringify(params) }); } + +/** + * Assuming `uri` is being merged it creates uris for `base`, `ours`, and `theirs` + */ +export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } { + return { + base: toGitUri(uri, ':1'), + ours: toGitUri(uri, ':2'), + theirs: toGitUri(uri, ':3'), + }; +} From 27fc8f70758943221c8a9c0a248b4a4f7b18b198 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 11 May 2022 14:13:30 +0200 Subject: [PATCH 034/350] First draft of the model implementation. --- src/vs/base/common/arrays.ts | 16 + src/vs/base/common/errors.ts | 4 +- .../mergeEditor/browser/mergeEditorInput.ts | 6 +- .../mergeEditor/browser/mergeEditorModel.ts | 207 ++++++++++- .../contrib/mergeEditor/browser/model.ts | 341 ++++++++++++++++++ 5 files changed, 568 insertions(+), 6 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/model.ts diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 8f6ab7661ae..e876d9d4304 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -748,15 +748,31 @@ export class ArrayQueue { } peek(): T | undefined { + if (this.length === 0) { + return undefined; + } return this.items[this.firstIdx]; } + peekLast(): T | undefined { + if (this.length === 0) { + return undefined; + } + return this.items[this.lastIdx]; + } + dequeue(): T | undefined { const result = this.items[this.firstIdx]; this.firstIdx++; return result; } + removeLast(): T | undefined { + const result = this.items[this.lastIdx]; + this.lastIdx--; + return result; + } + takeCount(count: number): T[] { const result = this.items.slice(this.firstIdx, this.firstIdx + count); this.firstIdx += count; diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 4f397866ae4..38ca3e4e102 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -259,8 +259,8 @@ export class ErrorNoTelemetry extends Error { * Only catch this error to recover gracefully from bugs. */ export class BugIndicatingError extends Error { - constructor(message: string) { - super(message); + constructor(message?: string) { + super(message || 'An unexpected bug occurred.'); Object.setPrototypeOf(this, BugIndicatingError.prototype); // Because we know for sure only buggy code throws this, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index cb5773e9f86..8943def8741 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -14,7 +14,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IUntypedEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; -import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; +import { MergeEditorModel, MergeEditorModelFactory } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -31,6 +31,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { private _model?: MergeEditorModel; private _outTextModel?: ITextFileEditorModel; + private readonly mergeEditorModelFactory = this._instaService.createInstance(MergeEditorModelFactory); constructor( private readonly _anchestor: URI, @@ -97,8 +98,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { const inputTwo = await this._textModelService.createModelReference(this._inputTwo); const result = await this._textModelService.createModelReference(this._result); - this._model = this._instaService.createInstance( - MergeEditorModel, + this._model = await this.mergeEditorModelFactory.create( anchestor.object.textEditorModel, inputOne.object.textEditorModel, inputTwo.object.textEditorModel, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 172a5c8e1a0..ce2eb6b3afb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -3,18 +3,223 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { compareBy, numberComparator } from 'vs/base/common/arrays'; +import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; +import { MergeableDiff, LineEdit, LineEdits, LineDiff, MergeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; + +export class MergeEditorModelFactory { + constructor( + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + //@IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + } + + public async create( + ancestor: ITextModel, + inputOne: ITextModel, + inputTwo: ITextModel, + result: ITextModel, + ): Promise { + + const ancestorToInputOneDiffPromise = this._editorWorkerService.computeDiff( + ancestor.uri, + inputOne.uri, + false, + 1000 + ); + const ancestorToInputTwoDiffPromise = this._editorWorkerService.computeDiff( + ancestor.uri, + inputTwo.uri, + false, + 1000 + ); + + const [ancestorToInputOneDiff, ancestorToInputTwoDiff] = await Promise.all([ + ancestorToInputOneDiffPromise, + ancestorToInputTwoDiffPromise, + ]); + + const changesInput1 = + ancestorToInputOneDiff?.changes.map((c) => + LineDiff.fromLineChange(c, ancestor, inputOne) + ) || []; + const changesInput2 = + ancestorToInputTwoDiff?.changes.map((c) => + LineDiff.fromLineChange(c, ancestor, inputTwo) + ) || []; + + return new MergeEditorModel( + InternalSymbol, + ancestor, + inputOne, + inputTwo, + result, + changesInput1, + changesInput2, + ); + } +} + +const InternalSymbol = Symbol(); export class MergeEditorModel extends EditorModel { + private resultEdits = new ResultEdits([], this.ancestor, this.result); constructor( - readonly anchestor: ITextModel, + symbol: typeof InternalSymbol, + readonly ancestor: ITextModel, readonly inputOne: ITextModel, readonly inputTwo: ITextModel, readonly result: ITextModel, + private readonly inputOneLinesDiffs: readonly LineDiff[], + private readonly inputTwoLinesDiffs: readonly LineDiff[], ) { super(); + + result.setValue(ancestor.getValue()); + + /* + // Apply all non-conflicts diffs + const lineEditsArr: LineEdit[] = []; + for (const diff of this.mergeableDiffs) { + if (!diff.isConflicting) { + for (const d of diff.inputOneDiffs) { + lineEditsArr.push(d.getLineEdit()); + } + for (const d of diff.inputTwoDiffs) { + lineEditsArr.push(d.getLineEdit()); + } + } + } + new LineEdits(lineEditsArr).apply(result); + */ + + console.log(this, 'hello'); + (globalThis as any)['mergeEditorModel'] = this; } + public get resultDiffs(): readonly LineDiff[] { + return this.resultEdits.diffs; + } + + public readonly mergeableDiffs = MergeableDiff.fromDiffs(this.inputOneLinesDiffs, this.inputTwoLinesDiffs); + + public getState(conflict: MergeableDiff): MergeState | undefined { + return undefined; + } + + // Undo all edits of result that conflict with the conflict!! + public setConflictResolutionStatus(conflict: MergeableDiff, status: MergeState): void { + const existingDiff = this.resultEdits.findConflictingDiffs(conflict.originalRange); + if (existingDiff) { + this.resultEdits.removeDiff(existingDiff); + } + + const edit = status.input1 ? conflict.getInput1LineEdit() : conflict.getInput2LineEdit(); + if (edit) { + this.resultEdits.applyEditRelativeToOriginal(edit); + } + } +} + +class ResultEdits { + constructor( + private _diffs: LineDiff[], + private readonly originalTextModel: ITextModel, + private readonly textModel: ITextModel, + ) { + this._diffs.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator)); + } + + public get diffs(): readonly LineDiff[] { + return this._diffs; + } + + public removeDiff(diffToRemove: LineDiff): void { + const len = this._diffs.length; + this._diffs = this._diffs.filter((d) => d !== diffToRemove); + if (len === this._diffs.length) { + throw new BugIndicatingError(); + } + + const edits = new LineEdits([diffToRemove.getReverseLineEdit()]); + edits.apply(this.textModel); + + this._diffs = this._diffs.map((d) => + d.modifiedRange.isAfter(diffToRemove.modifiedRange) + ? new LineDiff( + d.originalTextModel, + d.originalRange, + d.modifiedTextModel, + d.modifiedRange.delta( + diffToRemove.originalRange.lineCount - diffToRemove.modifiedRange.lineCount + ) + ) + : d + ); + } + + /** + * Edit must be conflict free. + */ + public applyEditRelativeToOriginal(edit: LineEdit): void { + let firstAfter = false; + let delta = 0; + const newDiffs = new Array(); + for (let i = 0; i < this._diffs.length; i++) { + const diff = this._diffs[i]; + + if (diff.originalRange.intersects(edit.range)) { + throw new BugIndicatingError(); + } else if (diff.originalRange.isAfter(edit.range)) { + if (!firstAfter) { + firstAfter = true; + + newDiffs.push(new LineDiff( + this.originalTextModel, + edit.range, + this.textModel, + new LineRange(edit.range.startLineNumber + delta, edit.newLines.length) + )); + } + + newDiffs.push(new LineDiff( + diff.originalTextModel, + diff.originalRange, + diff.modifiedTextModel, + diff.modifiedRange.delta(edit.newLines.length - edit.range.lineCount) + )); + } else { + newDiffs.push(diff); + } + + if (!firstAfter) { + delta += diff.modifiedRange.lineCount - diff.originalRange.lineCount; + } + } + + if (!firstAfter) { + firstAfter = true; + + newDiffs.push(new LineDiff( + this.originalTextModel, + edit.range, + this.textModel, + new LineRange(edit.range.startLineNumber + delta, edit.newLines.length) + )); + } + this._diffs = newDiffs; + + const edits = new LineEdits([new LineEdit(edit.range.delta(delta), edit.newLines, edit.data)]); + edits.apply(this.textModel); + } + + // TODO return many! + public findConflictingDiffs(rangeInOriginalTextModel: LineRange): LineDiff | undefined { + // TODO binary search + return this.diffs.find(d => d.originalRange.intersects(rangeInOriginalTextModel)); + } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model.ts b/src/vs/workbench/contrib/mergeEditor/browser/model.ts new file mode 100644 index 00000000000..c26686a0466 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/model.ts @@ -0,0 +1,341 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ArrayQueue, compareBy, numberComparator } from 'vs/base/common/arrays'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { Range } from 'vs/editor/common/core/range'; +import { ILineChange } from 'vs/editor/common/diff/diffComputer'; +import { ITextModel } from 'vs/editor/common/model'; + +export class LineEdits { + constructor(public readonly edits: readonly LineEdit[]) { + + } + + public apply(model: ITextModel): void { + model.pushEditOperations( + null, + this.edits.map((e) => ({ + range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1), + text: e.newLines.map(l => l + '\n').join(), + })), + () => null + ); + } +} + +export class LineEdit { + constructor( + public readonly range: LineRange, + public readonly newLines: string[], + public readonly data: T + ) { } +} + +export class MergeableDiff { + public static fromDiffs( + diffs1: readonly LineDiff[], + diffs2: readonly LineDiff[] + ): MergeableDiff[] { + const compareByStartLineNumber = compareBy( + (d) => d.originalRange.startLineNumber, + numberComparator + ); + + const queueDiffs1 = new ArrayQueue( + diffs1.slice().sort(compareByStartLineNumber) + ); + const queueDiffs2 = new ArrayQueue( + diffs2.slice().sort(compareByStartLineNumber) + ); + + const result = new Array(); + + while (true) { + const lastDiff1 = queueDiffs1.peekLast(); + const lastDiff2 = queueDiffs2.peekLast(); + + if ( + lastDiff1 && + (!lastDiff2 || + lastDiff1.originalRange.startLineNumber >= + lastDiff2.originalRange.startLineNumber) + ) { + queueDiffs1.removeLast(); + + const otherConflictingWith = + queueDiffs2.takeFromEndWhile((d) => d.conflicts(lastDiff1)) || []; + + const singleLinesDiff = LineDiff.hull(otherConflictingWith); + + const moreConflictingWith = + (singleLinesDiff && + queueDiffs1.takeFromEndWhile((d) => + d.conflicts(singleLinesDiff) + )) || + []; + moreConflictingWith.push(lastDiff1); + + result.push( + new MergeableDiff(moreConflictingWith, otherConflictingWith) + ); + } else if (lastDiff2) { + queueDiffs2.removeLast(); + + const otherConflictingWith = + queueDiffs1.takeFromEndWhile((d) => d.conflicts(lastDiff2)) || []; + + const singleLinesDiff = LineDiff.hull(otherConflictingWith); + + const moreConflictingWith = + (singleLinesDiff && + queueDiffs2.takeFromEndWhile((d) => + d.conflicts(singleLinesDiff) + )) || + []; + moreConflictingWith.push(lastDiff2); + + result.push( + new MergeableDiff(otherConflictingWith, moreConflictingWith) + ); + } else { + break; + } + } + + result.reverse(); + + return result; + } + + public readonly originalRange = LineRange.hull( + this.input1Diffs + .map((d) => d.originalRange) + .concat(this.input2Diffs.map((d) => d.originalRange)) + )!; + + constructor( + public readonly input1Diffs: readonly LineDiff[], + public readonly input2Diffs: readonly LineDiff[] + ) { } + + public get isConflicting(): boolean { + return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; + } + + public getInput1LineEdit(): LineEdit | undefined { + if (this.input1Diffs.length === 0) { + return undefined; + } + if (this.input1Diffs.length === 1) { + return this.input1Diffs[0].getLineEdit(); + } else { + throw new Error('Method not implemented.'); + } + } + + public getInput2LineEdit(): LineEdit | undefined { + if (this.input2Diffs.length === 0) { + return undefined; + } + if (this.input2Diffs.length === 1) { + return this.input2Diffs[0].getLineEdit(); + } else { + throw new Error('Method not implemented.'); + } + } +} + +export class LineRange { + public static hull(ranges: LineRange[]): LineRange | undefined { + if (ranges.length === 0) { + return undefined; + } + + let startLineNumber = Number.MAX_SAFE_INTEGER; + let endLineNumber = 0; + for (const range of ranges) { + startLineNumber = Math.min(startLineNumber, range.startLineNumber); + endLineNumber = Math.max(endLineNumber, range.startLineNumber + range.lineCount); + } + return new LineRange(startLineNumber, endLineNumber - startLineNumber); + } + + constructor( + public readonly startLineNumber: number, + public readonly lineCount: number + ) { + if (lineCount < 0) { + throw new BugIndicatingError(); + } + } + + public get endLineNumberExclusive(): number { + return this.startLineNumber + this.lineCount; + } + + public get isEmpty(): boolean { + return this.lineCount === 0; + } + + public intersects(other: LineRange): boolean { + return ( + this.endLineNumberExclusive >= other.startLineNumber && + other.endLineNumberExclusive >= this.startLineNumber + ); + } + + public isAfter(modifiedRange: LineRange): boolean { + return this.startLineNumber >= modifiedRange.endLineNumberExclusive; + } + + public delta(lineDelta: number): LineRange { + return new LineRange(this.startLineNumber + lineDelta, this.lineCount); + } + + public toString() { + return `[${this.startLineNumber},${this.endLineNumberExclusive})`; + } +} + +export class LineDiff { + public static fromLineChange(lineChange: ILineChange, originalTextModel: ITextModel, modifiedTextModel: ITextModel): LineDiff { + let originalRange: LineRange; + if (lineChange.originalEndLineNumber === 0) { + // Insertion + originalRange = new LineRange(lineChange.originalStartLineNumber + 1, 0); + } else { + originalRange = new LineRange(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1); + } + + let modifiedRange: LineRange; + if (lineChange.modifiedEndLineNumber === 0) { + // Insertion + modifiedRange = new LineRange(lineChange.modifiedStartLineNumber + 1, 0); + } else { + modifiedRange = new LineRange(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1); + } + + return new LineDiff( + originalTextModel, + originalRange, + modifiedTextModel, + modifiedRange, + ); + } + + public static hull(lineDiffs: LineDiff[]): LineDiff | undefined { + if (lineDiffs.length === 0) { + return undefined; + } + + return new LineDiff( + lineDiffs[0].originalTextModel, + LineRange.hull(lineDiffs.map((d) => d.originalRange))!, + lineDiffs[0].modifiedTextModel, + LineRange.hull(lineDiffs.map((d) => d.modifiedRange))!, + ); + } + + constructor( + public readonly originalTextModel: ITextModel, + public readonly originalRange: LineRange, + public readonly modifiedTextModel: ITextModel, + public readonly modifiedRange: LineRange, + ) { + } + + private ensureSameOriginalModel(other: LineDiff): void { + if (this.originalTextModel !== other.originalTextModel) { + // Both changes must refer to the same original model + throw new BugIndicatingError(); + } + } + + public conflicts(other: LineDiff): boolean { + this.ensureSameOriginalModel(other); + return this.originalRange.intersects(other.originalRange); + } + + public isStrictBefore(other: LineDiff): boolean { + this.ensureSameOriginalModel(other); + return this.originalRange.endLineNumberExclusive <= other.originalRange.startLineNumber; + } + + public getLineEdit(): LineEdit { + return new LineEdit( + this.originalRange, + this.getModifiedLines(), + undefined + ); + } + + public getReverseLineEdit(): LineEdit { + return new LineEdit( + this.modifiedRange, + this.getOriginalLines(), + undefined + ); + } + + private getModifiedLines(): string[] { + const result = new Array(this.modifiedRange.lineCount); + for (let i = 0; i < this.modifiedRange.lineCount; i++) { + result[i] = this.modifiedTextModel.getLineContent(this.modifiedRange.startLineNumber + i); + } + return result; + } + + private getOriginalLines(): string[] { + const result = new Array(this.originalRange.lineCount); + for (let i = 0; i < this.originalRange.lineCount; i++) { + result[i] = this.originalTextModel.getLineContent(this.originalRange.startLineNumber + i); + } + return result; + } +} + + +export class MergeState { + constructor( + public readonly input1: boolean, + public readonly input2: boolean, + public readonly input2First: boolean + ) { } + + public withInput1(value: boolean): MergeState { + return new MergeState( + value, + this.input2, + value && this.isEmpty ? false : this.input2First + ); + } + + public withInput2(value: boolean): MergeState { + return new MergeState( + this.input1, + value, + value && this.isEmpty ? true : this.input2First + ); + } + + public get isEmpty(): boolean { + return !this.input1 && !this.input2; + } + + public toString(): string { + const arr: ('1' | '2')[] = []; + if (this.input1) { + arr.push('1'); + } + if (this.input2) { + arr.push('2'); + } + if (this.input2First) { + arr.reverse(); + } + return arr.join(','); + } +} From a38e09d1380f81c84ca19abfdd2e8d04e107452c Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 11 May 2022 15:37:12 +0200 Subject: [PATCH 035/350] add toggle layout action - column vs 2 by 1 --- .../browser/mergeEditor.contribution.ts | 35 ++++++++++++++-- .../mergeEditor/browser/mergeEditor.ts | 40 ++++++++++++++----- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 88fc90c9d2a..3f72bfca524 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -6,16 +6,17 @@ import { localize } from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditor'; +import { ctxIsMergeEditor, ctxUsesColumnLayout, MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditor'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MergeEditorSerializer } from './mergeEditorSerializer'; +import { Codicon } from 'vs/base/common/codicons'; Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( @@ -33,7 +34,35 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit MergeEditorSerializer ); -registerAction2(class Foo extends Action2 { +registerAction2(class ToggleLayout extends Action2 { + + constructor() { + super({ + id: 'merge.toggleLayout', + title: localize('toggle.title', "Switch to column view"), + icon: Codicon.layoutCentered, + toggled: { + condition: ctxUsesColumnLayout, + icon: Codicon.layoutPanel, + title: localize('toggle.title2', "Switch to 2 by 1 view"), + }, + menu: [{ + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation' + }] + }); + } + + run(accessor: ServicesAccessor): void { + const { activeEditorPane } = accessor.get(IEditorService); + if (activeEditorPane instanceof MergeEditor) { + activeEditorPane.toggleLayout(); + } + } +}); + +registerAction2(class Open extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index c300d43dd54..93918c9ae43 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -17,8 +17,8 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { BugIndicatingError } from 'vs/base/common/errors'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; -import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; +import { Direction, Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; +import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { ITextModel } from 'vs/editor/common/model'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -29,13 +29,14 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IAction } from 'vs/base/common/actions'; export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); +export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); export class MergeEditor extends EditorPane { @@ -49,6 +50,9 @@ export class MergeEditor extends EditorPane { private readonly inputTwoView = this.instantiation.createInstance(CodeEditorView, { readonly: true }); private readonly inputResultView = this.instantiation.createInstance(CodeEditorView, { readonly: false }); + private readonly _ctxIsMergeEditor: IContextKey; + private readonly _ctxUsesColumnLayout: IContextKey; + constructor( @IInstantiationService private readonly instantiation: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, @@ -60,7 +64,8 @@ export class MergeEditor extends EditorPane { ) { super(MergeEditor.ID, telemetryService, themeService, storageService); - ctxIsMergeEditor.bindTo(_contextKeyService).set(true); + this._ctxIsMergeEditor = ctxIsMergeEditor.bindTo(_contextKeyService); + this._ctxUsesColumnLayout = ctxUsesColumnLayout.bindTo(_contextKeyService); const reentrancyBarrier = new ReentrancyBarrier(); this._store.add(this.inputOneView.editor.onDidScrollChange(c => { @@ -112,6 +117,7 @@ export class MergeEditor extends EditorPane { override dispose(): void { this._sessionDisposables.dispose(); + this._ctxIsMergeEditor.reset(); super.dispose(); } @@ -141,6 +147,7 @@ export class MergeEditor extends EditorPane { }); reset(parent, this._grid.element); + this._ctxUsesColumnLayout.set(false); } layout(dimension: Dimension): void { @@ -161,13 +168,9 @@ export class MergeEditor extends EditorPane { this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); } - // override clearInput(): void { - // super.clearInput(); - // } - - // protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - // console.log('VISISBLE', visible); - // } + protected override setEditorVisible(visible: boolean): void { + this._ctxIsMergeEditor.set(visible); + } // ---- interact with "outside world" via `getControl`, `scopedContextKeyService` @@ -186,6 +189,21 @@ export class MergeEditor extends EditorPane { ? control.invokeWithinContext(accessor => accessor.get(IContextKeyService)) : undefined; } + + // --- layout + + private _usesColumnLayout = false; + + toggleLayout(): void { + if (!this._usesColumnLayout) { + this._grid.moveView(this.inputResultView, Sizing.Distribute, this.inputOneView, Direction.Right); + } else { + this._grid.moveView(this.inputResultView, this._grid.height * .62, this.inputOneView, Direction.Down); + this._grid.moveView(this.inputTwoView, Sizing.Distribute, this.inputOneView, Direction.Right); + } + this._usesColumnLayout = !this._usesColumnLayout; + this._ctxUsesColumnLayout.set(this._usesColumnLayout); + } } interface ICodeEditorViewOptions { From ce60d548e2cced5d3cf868e4a929ed0f8f9d8823 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:16:11 +0200 Subject: [PATCH 036/350] - add tests - fix scenarios causing tests failures - check for duplicate policy names --- .../common/configurationRegistry.ts | 34 +++- .../configuration/common/configurations.ts | 71 +++---- .../test/common/configurationRegistry.test.ts | 26 +++ .../test/common/policyConfiguration.test.ts | 179 ++++++++++++++++++ .../policy/common/filePolicyService.ts | 4 +- src/vs/platform/policy/common/policy.ts | 6 + .../api/common/configurationExtensionPoint.ts | 4 +- .../browser/configurationService.ts | 11 +- .../configurationEditingService.test.ts | 37 +++- .../test/browser/configurationService.test.ts | 30 ++- 10 files changed, 351 insertions(+), 51 deletions(-) create mode 100644 src/vs/platform/configuration/test/common/policyConfiguration.test.ts diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 1f424d52cb7..11331ac829b 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -11,6 +11,7 @@ import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; import { getLanguageTagSettingPlainKey } from 'vs/platform/configuration/common/configuration'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { PolicyName } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export enum EditPresentationTypes { @@ -89,6 +90,11 @@ export interface IConfigurationRegistry { */ getConfigurationProperties(): IStringDictionary; + /** + * Return all configurations by policy name + */ + getPolicyConfigurations(): Map; + /** * Returns all excluded configurations settings of all configuration nodes contributed to this registry. */ @@ -127,12 +133,12 @@ export const enum ConfigurationScope { MACHINE_OVERRIDABLE, } -export interface PolicyConfiguration { +export interface IPolicy { /** * The policy name. */ - readonly name: string; + readonly name: PolicyName; /** * The Code version in which this policy was introduced. @@ -193,7 +199,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema { * When specified, this setting's value can always be overwritten by * a system-wide policy. */ - policy?: PolicyConfiguration; + policy?: IPolicy; } export interface IExtensionInfo { @@ -245,6 +251,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode; private readonly configurationContributors: IConfigurationNode[]; private readonly configurationProperties: IStringDictionary; + private readonly policyConfigurations: Map; private readonly excludedConfigurationProperties: IStringDictionary; private readonly resourceLanguageSettingsSchema: IJSONSchema; private readonly overrideIdentifiers = new Set(); @@ -265,6 +272,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode]; this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; + this.policyConfigurations = new Map(); this.excludedConfigurationProperties = {}; contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); @@ -397,6 +405,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { if (configuration.properties) { for (const key in configuration.properties) { properties.push(key); + const property = this.configurationProperties[key]; + if (property?.policy?.name) { + this.policyConfigurations.delete(property.policy.name); + } delete this.configurationProperties[key]; this.removeFromSchema(key, configuration.properties[key]); } @@ -421,12 +433,12 @@ class ConfigurationRegistry implements IConfigurationRegistry { let properties = configuration.properties; if (properties) { for (let key in properties) { - if (validate && validateProperty(key)) { + const property: IRegisteredConfigurationPropertySchema = properties[key]; + if (validate && validateProperty(key, property)) { delete properties[key]; continue; } - const property: IRegisteredConfigurationPropertySchema = properties[key]; property.source = extensionInfo; // update default value @@ -449,6 +461,9 @@ class ConfigurationRegistry implements IConfigurationRegistry { continue; } else { this.configurationProperties[key] = properties[key]; + if (properties[key].policy?.name) { + this.policyConfigurations.set(properties[key].policy!.name, key); + } } if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) { @@ -477,6 +492,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { return this.configurationProperties; } + getPolicyConfigurations(): Map { + return this.policyConfigurations; + } + getExcludedConfigurationProperties(): IStringDictionary { return this.excludedConfigurationProperties; } @@ -647,7 +666,7 @@ export function getDefaultValue(type: string | string[] | undefined): any { const configurationRegistry = new ConfigurationRegistry(); Registry.add(Extensions.Configuration, configurationRegistry); -export function validateProperty(property: string): string | null { +export function validateProperty(property: string, schema: IRegisteredConfigurationPropertySchema): string | null { if (!property.trim()) { return nls.localize('config.property.empty', "Cannot register an empty property"); } @@ -657,6 +676,9 @@ export function validateProperty(property: string): string | null { if (configurationRegistry.getConfigurationProperties()[property] !== undefined) { return nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", property); } + if (schema.policy?.name && configurationRegistry.getPolicyConfigurations().get(schema.policy?.name) !== undefined) { + return nls.localize('config.policy.duplicate', "Cannot register '{0}'. The associated policy {1} is already registered with {2}.", property, schema.policy?.name, configurationRegistry.getPolicyConfigurations().get(schema.policy?.name)); + } return null; } diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 9e9603e9c61..a9cf1f256f9 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -15,7 +15,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService, PolicyName } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -86,7 +86,6 @@ export class PolicyConfiguration extends Disposable { readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; private readonly policyService: IPolicyService; - private readonly policyNamesToKeys = new Map(); private _configurationModel = new ConfigurationModel(); get configurationModel() { return this._configurationModel; } @@ -103,53 +102,55 @@ export class PolicyConfiguration extends Disposable { async initialize(): Promise { await this.policyService.initialize(); - this.updateKeys(this.defaultConfiguration.configurationModel.keys, false); + this.update(this.defaultConfiguration.configurationModel.keys, false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.updateKeys(properties, true))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); return this._configurationModel; } - private updateKeys(keys: string[], trigger: boolean): void { - const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const keyPolicyNamePairs: [string, PolicyName][] = coalesce(keys.map(key => { - const policyName = configurationProperties[key].policy?.name; - return policyName ? [key, policyName] : undefined; - })); - this.update(keyPolicyNamePairs, trigger); + async reload(): Promise { + await this.policyService.refresh(); + this.update(this.defaultConfiguration.configurationModel.keys, false); + return this._configurationModel; } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { - const keyPolicyNamePairs: [string, PolicyName][] = coalesce(policyNames.map(policyName => { - const key = this.policyNamesToKeys.get(policyName); - return key ? [key, policyName] : undefined; - })); - this.update(keyPolicyNamePairs, true); + const policyConfigurations = Registry.as(Extensions.Configuration).getPolicyConfigurations(); + const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName))); + this.update(keys, true); } - private update(keyPolicyNamePairs: [string, PolicyName][], trigger: boolean): void { - if (!keyPolicyNamePairs.length) { - return; - } + private update(keys: string[], trigger: boolean): void { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const changed: [string, PolicyValue | undefined][] = []; + const wasEmpty = this._configurationModel.isEmpty(); - const updated: string[] = []; - this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; - const isEmpty = this._configurationModel.isEmpty(); - for (const [key, policyName] of keyPolicyNamePairs) { - this.policyNamesToKeys.set(policyName, key); - const value = this.policyService.getPolicyValue(policyName); - if (!isEmpty && equals(this._configurationModel.getValue(key), value)) { - continue; - } - if (value === undefined) { - this._configurationModel.removeValue(key); + for (const key of keys) { + const policyName = configurationProperties[key]?.policy?.name; + if (policyName) { + const policyValue = this.policyService.getPolicyValue(policyName); + if (wasEmpty ? policyValue !== undefined : !equals(this._configurationModel.getValue(key), policyValue)) { + changed.push([key, policyValue]); + } } else { - this._configurationModel.setValue(key, value); + if (this._configurationModel.getValue(key) !== undefined) { + changed.push([key, undefined]); + } } - updated.push(key); } - if (updated.length && trigger) { - this._onDidChangeConfiguration.fire(this._configurationModel); + if (changed.length) { + this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + for (const [key, policyValue] of changed) { + if (policyValue === undefined) { + this._configurationModel.removeValue(key); + } else { + this._configurationModel.setValue(key, policyValue); + } + } + if (trigger) { + this._onDidChangeConfiguration.fire(this._configurationModel); + } } } diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 83314c83ad3..8e836674dae 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -50,4 +50,30 @@ suite('ConfigurationRegistry', () => { assert.deepStrictEqual(configurationRegistry.getConfigurationProperties()['config'].default, { a: 2, c: 3 }); }); + + test('registering multiple settings with same policy', async () => { + configurationRegistry.registerConfiguration({ + 'id': '_test_default', + 'type': 'object', + 'properties': { + 'policy1': { + 'type': 'object', + policy: { + name: 'policy', + minimumVersion: '1.0.0' + } + }, + 'policy2': { + 'type': 'object', + policy: { + name: 'policy', + minimumVersion: '1.0.0' + } + } + } + }); + const actual = configurationRegistry.getConfigurationProperties(); + assert.ok(actual['policy1'] !== undefined); + assert.ok(actual['policy2'] === undefined); + }); }); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts new file mode 100644 index 00000000000..3f6e15bdd0c --- /dev/null +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -0,0 +1,179 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { deepClone } from 'vs/base/common/objects'; + +suite('PolicyConfiguration', () => { + + let testObject: PolicyConfiguration; + let fileService: IFileService; + const policyFile = URI.file('policyFile').with({ scheme: 'vscode-tests' }); + const disposables = new DisposableStore(); + const policyConfigurationNode: IConfigurationNode = { + 'id': 'policyConfiguration', + 'order': 1, + 'title': 'a', + 'type': 'object', + 'properties': { + 'policy.settingA': { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingA', + minimumVersion: '1.0.0', + } + }, + 'nonPolicy.setting': { + 'type': 'boolean', + 'default': true + } + } + }; + + suiteSetup(() => Registry.as(Extensions.Configuration).registerConfiguration(policyConfigurationNode)); + suiteTeardown(() => Registry.as(Extensions.Configuration).deregisterConfigurations([policyConfigurationNode])); + + setup(async () => { + const defaultConfiguration = disposables.add(new DefaultConfiguration()); + await defaultConfiguration.initialize(); + fileService = disposables.add(new FileService(new NullLogService())); + const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, fileService, { policyFile } as IEnvironmentService, new NullLogService())); + }); + + teardown(() => disposables.clear()); + + test('initialize: with policies', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('initialize: no policies', async () => { + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + }); + + test('initialize: with policies but not registered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false }))); + + await testObject.initialize(); + const acutal = testObject.configurationModel; + + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is added', async () => { + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is updated', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': true }))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy is removed', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy setting is registered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingB': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + policyConfigurationNode.properties!['policy.settingB'] = { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingB', + minimumVersion: '1.0.0', + } + }; + Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, ['policy.settingB']); + assert.deepStrictEqual(acutal.overrides, []); + }); + + test('change: when policy setting is deregistered', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await testObject.initialize(); + + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + Registry.as(Extensions.Configuration).deregisterConfigurations([policyConfigurationNode]); + await promise; + + const acutal = testObject.configurationModel; + assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); + assert.deepStrictEqual(acutal.keys, []); + assert.deepStrictEqual(acutal.overrides, []); + }); + +}); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 0cfa9e1588c..4779fe77c0a 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -50,8 +50,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { await this.doRefresh(); } - private refresh(): void { - this.throttledDelayer.trigger(() => this.doRefresh()); + async refresh(): Promise { + await this.throttledDelayer.trigger(() => this.doRefresh()); } private async read(): Promise { diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 4febe66904f..6963bf22081 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,12 +12,14 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; + refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } + async refresh() { } getPolicyValue() { return undefined; } } @@ -33,6 +35,10 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } + async refresh() { + await Promise.all(this.policyServices.map(p => p.refresh())); + } + getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 406661573d2..17d42a088fa 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -204,7 +204,8 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { configuration.properties = {}; } for (let key in properties) { - const message = validateProperty(key); + const propertyConfiguration = properties[key]; + const message = validateProperty(key, propertyConfiguration); if (message) { delete properties[key]; extension.collector.warn(message); @@ -215,7 +216,6 @@ configurationExtPoint.setHandler((extensions, { added, removed }) => { extension.collector.warn(nls.localize('config.property.duplicate', "Cannot register '{0}'. This property is already registered.", key)); continue; } - const propertyConfiguration = properties[key]; if (!isObject(propertyConfiguration)) { delete properties[key]; extension.collector.error(nls.localize('invalid.property', "configuration.properties property '{0}' must be an object", key)); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index a4c11121290..0db722acbc1 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -342,6 +342,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { if (target === undefined) { + this.reloadDefaultConfiguration(); + await this.reloadPolicyConfiguration(); const { local, remote } = await this.reloadUserConfiguration(); await this.reloadWorkspaceConfiguration(); await this.loadConfiguration(local, remote); @@ -355,7 +357,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat switch (target) { case ConfigurationTarget.DEFAULT: - await this.reloadDefaultConfiguration(); + this.reloadDefaultConfiguration(); + await this.reloadPolicyConfiguration(); return; case ConfigurationTarget.USER: { @@ -596,10 +599,14 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return { local, remote }; } - private async reloadDefaultConfiguration(): Promise { + private reloadDefaultConfiguration(): void { this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } + private async reloadPolicyConfiguration(): Promise { + this.onPolicyConfigurationChanged(await this.policyConfiguration.reload()); + } + private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 7d7e3cb1267..2fba810064f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -37,8 +37,9 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService'; -import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { hash } from 'vs/base/common/hash'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -52,7 +53,7 @@ export class ConfigurationCache implements IConfigurationCache { suite('ConfigurationEditingService', () => { let instantiationService: TestInstantiationService; - let environmentService: BrowserWorkbenchEnvironmentService; + let environmentService: IWorkbenchEnvironmentService; let fileService: IFileService; let workspaceService: WorkspaceService; let testObject: ConfigurationEditingService; @@ -76,6 +77,14 @@ suite('ConfigurationEditingService', () => { 'configurationEditing.service.testSettingThree': { 'type': 'string', 'default': 'isSet' + }, + 'configurationEditing.service.policySetting': { + 'type': 'string', + 'default': 'isSet', + policy: { + name: 'configurationEditing.service.policySetting', + minimumVersion: '1.0.0', + } } } }); @@ -92,12 +101,17 @@ suite('ConfigurationEditingService', () => { instantiationService = workbenchInstantiationService(undefined, disposables); environmentService = TestEnvironmentService; + environmentService.policyFile = joinPath(workspaceFolder, 'policies.json'); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + await workspaceService.initialize({ + id: hash(workspaceFolder.toString()).toString(16), + uri: workspaceFolder + }); instantiationService.stub(IWorkspaceContextService, workspaceService); await workspaceService.initialize(getSingleFolderWorkspaceIdentifier(workspaceFolder)); @@ -180,6 +194,25 @@ suite('ConfigurationEditingService', () => { assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'); }); + test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); + await instantiationService.get(IConfigurationService).reloadConfiguration(); + try { + await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); + } catch (error) { + assert.strictEqual(error.code, ConfigurationEditingErrorCode.ERROR_POLICY_CONFIGURATION); + return; + } + assert.fail('Should fail with ERROR_POLICY_CONFIGURATION'); + }); + + test('write policy setting - when not set', async () => { + await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); + const contents = await fileService.readFile(environmentService.settingsResource); + const parsed = json.parse(contents.value.toString()); + assert.strictEqual(parsed['configurationEditing.service.policySetting'], 'value'); + }); + test('write one setting - empty file', async () => { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }); const contents = await fileService.readFile(environmentService.settingsResource); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 352fd8e9d7e..5a207dccd6d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -669,7 +669,7 @@ suite('WorkspaceService - Initialization', () => { suite('WorkspaceConfigurationService - Folder', () => { - let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: BrowserWorkbenchEnvironmentService; + let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); const disposables: DisposableStore = new DisposableStore(); @@ -708,6 +708,14 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', restricted: true }, + 'configurationService.folder.policySetting': { + 'type': 'string', + 'default': 'isSet', + policy: { + name: 'configurationService.folder.policySetting', + minimumVersion: '1.0.0', + } + }, } }); @@ -731,6 +739,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const instantiationService = workbenchInstantiationService(undefined, disposables); environmentService = TestEnvironmentService; + environmentService.policyFile = joinPath(folder, 'policies.json'); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); @@ -750,7 +759,7 @@ suite('WorkspaceConfigurationService - Folder', () => { teardown(() => disposables.clear()); test('defaults', () => { - assert.deepStrictEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet', 'restrictedSetting': 'isSet' } }); + assert.deepStrictEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet', 'restrictedSetting': 'isSet', 'policySetting': 'isSet' } }); }); test('globals override defaults', async () => { @@ -956,6 +965,23 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.getValue('configurationService.folder.machineSetting-3', { resource: workspaceService.getWorkspace().folders[0].uri }), 'userValue'); }); + test('policy value override all', async () => { + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); + }); + + test('policy settings when policy value is not set', async () => { + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); + await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); + await testObject.reloadConfiguration(); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'workspaceValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); + }); + test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 0d291672e501c0f6939910ae8c9e9a118f3fde19 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:30:49 +0200 Subject: [PATCH 037/350] - add test for change event when policy value changes - fix the model. create model always --- src/vs/platform/configuration/common/configurations.ts | 7 ++++++- .../test/browser/configurationService.test.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index a9cf1f256f9..353ad2070d3 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -140,7 +140,11 @@ export class PolicyConfiguration extends Disposable { } if (changed.length) { - this._configurationModel = this._configurationModel.isFrozen() ? this._configurationModel.clone() : this._configurationModel; + const old = this._configurationModel; + this._configurationModel = new ConfigurationModel(); + for (const key of old.keys) { + this._configurationModel.setValue(key, old.getValue(key)); + } for (const [key, policyValue] of changed) { if (policyValue === undefined) { this._configurationModel.removeValue(key); @@ -154,4 +158,5 @@ export class PolicyConfiguration extends Disposable { } } + } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 5a207dccd6d..7f980fd1d1d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -982,6 +982,15 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); }); + test('policy change should trigger change event ', async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + const result = await promise; + assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); + assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); + assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); + }); + test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 2752a8237260b3fcf7e329338fa55c38ef2a951e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:34:59 +0200 Subject: [PATCH 038/350] refresh should not be throttled as it is requested explicitly --- src/vs/platform/policy/common/filePolicyService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 4779fe77c0a..1fbec3f40cd 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -43,7 +43,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); this._register(fileService.watch(file)); - this._register(onDidChangePolicyFile(this.refresh, this)); + this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.doRefresh()))); } async initialize(): Promise { @@ -51,7 +51,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { } async refresh(): Promise { - await this.throttledDelayer.trigger(() => this.doRefresh()); + await this.doRefresh(); } private async read(): Promise { From 78cabb9419909a5cdd2a219b518e1696dc1e0907 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 11 May 2022 16:50:11 +0200 Subject: [PATCH 039/350] make tests roboust --- .../test/common/policyConfiguration.test.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 3f6e15bdd0c..44a8c34894a 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -38,6 +38,14 @@ suite('PolicyConfiguration', () => { minimumVersion: '1.0.0', } }, + 'policy.settingB': { + 'type': 'boolean', + 'default': true, + policy: { + name: 'PolicySettingB', + minimumVersion: '1.0.0', + } + }, 'nonPolicy.setting': { 'type': 'boolean', 'default': true @@ -66,6 +74,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); assert.deepStrictEqual(acutal.overrides, []); @@ -78,32 +87,36 @@ suite('PolicyConfiguration', () => { assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); }); test('initialize: with policies but not registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); await testObject.initialize(); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), false); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is added', async () => { + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); await promise; const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingB'), false); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingA']); + assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); @@ -117,6 +130,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); assert.deepStrictEqual(acutal.overrides, []); @@ -132,21 +146,22 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy setting is registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingB': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': false }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - policyConfigurationNode.properties!['policy.settingB'] = { + policyConfigurationNode.properties!['policy.settingC'] = { 'type': 'boolean', 'default': true, policy: { - name: 'PolicySettingB', + name: 'PolicySettingC', minimumVersion: '1.0.0', } }; @@ -154,10 +169,11 @@ suite('PolicyConfiguration', () => { await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingC'), false); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); - assert.deepStrictEqual(acutal.keys, ['policy.settingB']); + assert.deepStrictEqual(acutal.keys, ['policy.settingC']); assert.deepStrictEqual(acutal.overrides, []); }); @@ -171,6 +187,7 @@ suite('PolicyConfiguration', () => { const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); + assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, []); assert.deepStrictEqual(acutal.overrides, []); From 1c559569eabfe84fa303f795066027bd5dd845f6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 11 May 2022 17:13:47 +0200 Subject: [PATCH 040/350] one -> 1, two -> 2 --- .../mergeEditor/browser/mergeEditor.ts | 4 +- .../mergeEditor/browser/mergeEditorInput.ts | 24 ++++++------ .../mergeEditor/browser/mergeEditorModel.ts | 39 +++++++++++-------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 93918c9ae43..81b82849c05 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -163,8 +163,8 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.inputOneView.setModel(model.inputOne, localize('yours', 'Yours'), undefined); - this.inputTwoView.setModel(model.inputTwo, localize('theirs', 'Theirs',), undefined); + this.inputOneView.setModel(model.input1, localize('yours', 'Yours'), undefined); + this.inputTwoView.setModel(model.input2, localize('theirs', 'Theirs',), undefined); this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 8943def8741..da2ada49ee0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -35,8 +35,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { constructor( private readonly _anchestor: URI, - private readonly _inputOne: URI, - private readonly _inputTwo: URI, + private readonly _input1: URI, + private readonly _input2: URI, private readonly _result: URI, @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService private readonly _textModelService: ITextModelService, @@ -94,21 +94,21 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { if (!this._model) { const anchestor = await this._textModelService.createModelReference(this._anchestor); - const inputOne = await this._textModelService.createModelReference(this._inputOne); - const inputTwo = await this._textModelService.createModelReference(this._inputTwo); + const input1 = await this._textModelService.createModelReference(this._input1); + const input2 = await this._textModelService.createModelReference(this._input2); const result = await this._textModelService.createModelReference(this._result); this._model = await this.mergeEditorModelFactory.create( anchestor.object.textEditorModel, - inputOne.object.textEditorModel, - inputTwo.object.textEditorModel, + input1.object.textEditorModel, + input2.object.textEditorModel, result.object.textEditorModel ); this._store.add(this._model); this._store.add(anchestor); - this._store.add(inputOne); - this._store.add(inputTwo); + this._store.add(input1); + this._store.add(input2); this._store.add(result); // result.object. @@ -121,16 +121,16 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { return false; } return isEqual(this._anchestor, otherInput._anchestor) - && isEqual(this._inputOne, otherInput._inputOne) - && isEqual(this._inputTwo, otherInput._inputTwo) + && isEqual(this._input1, otherInput._input1) + && isEqual(this._input2, otherInput._input2) && isEqual(this._result, otherInput._result); } toJSON(): MergeEditorInputJSON { return { anchestor: this._anchestor, - inputOne: this._inputOne, - inputTwo: this._inputTwo, + inputOne: this._input1, + inputTwo: this._input2, result: this._result, }; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index ce2eb6b3afb..13fd032a5ea 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -19,43 +19,43 @@ export class MergeEditorModelFactory { public async create( ancestor: ITextModel, - inputOne: ITextModel, - inputTwo: ITextModel, + input1: ITextModel, + input2: ITextModel, result: ITextModel, ): Promise { - const ancestorToInputOneDiffPromise = this._editorWorkerService.computeDiff( + const ancestorToInput1DiffPromise = this._editorWorkerService.computeDiff( ancestor.uri, - inputOne.uri, + input1.uri, false, 1000 ); - const ancestorToInputTwoDiffPromise = this._editorWorkerService.computeDiff( + const ancestorToInput2DiffPromise = this._editorWorkerService.computeDiff( ancestor.uri, - inputTwo.uri, + input2.uri, false, 1000 ); - const [ancestorToInputOneDiff, ancestorToInputTwoDiff] = await Promise.all([ - ancestorToInputOneDiffPromise, - ancestorToInputTwoDiffPromise, + const [ancestorToInput1Diff, ancestorToInput2Diff] = await Promise.all([ + ancestorToInput1DiffPromise, + ancestorToInput2DiffPromise, ]); const changesInput1 = - ancestorToInputOneDiff?.changes.map((c) => - LineDiff.fromLineChange(c, ancestor, inputOne) + ancestorToInput1Diff?.changes.map((c) => + LineDiff.fromLineChange(c, ancestor, input1) ) || []; const changesInput2 = - ancestorToInputTwoDiff?.changes.map((c) => - LineDiff.fromLineChange(c, ancestor, inputTwo) + ancestorToInput2Diff?.changes.map((c) => + LineDiff.fromLineChange(c, ancestor, input2) ) || []; return new MergeEditorModel( InternalSymbol, ancestor, - inputOne, - inputTwo, + input1, + input2, result, changesInput1, changesInput2, @@ -71,8 +71,8 @@ export class MergeEditorModel extends EditorModel { constructor( symbol: typeof InternalSymbol, readonly ancestor: ITextModel, - readonly inputOne: ITextModel, - readonly inputTwo: ITextModel, + readonly input1: ITextModel, + readonly input2: ITextModel, readonly result: ITextModel, private readonly inputOneLinesDiffs: readonly LineDiff[], private readonly inputTwoLinesDiffs: readonly LineDiff[], @@ -108,6 +108,11 @@ export class MergeEditorModel extends EditorModel { public readonly mergeableDiffs = MergeableDiff.fromDiffs(this.inputOneLinesDiffs, this.inputTwoLinesDiffs); public getState(conflict: MergeableDiff): MergeState | undefined { + const existingDiff = this.resultEdits.findConflictingDiffs(conflict.originalRange); + if (existingDiff) { + //existingDiff. + } + return undefined; } From b2a380b59d788e234de12243955c07f539477971 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Wed, 11 May 2022 09:43:00 -0700 Subject: [PATCH 041/350] Show policy info in Settings editor --- .../browser/media/settingsEditor2.css | 10 ++++- .../preferences/browser/settingsTree.ts | 37 +++++++++++++++++-- .../preferences/browser/settingsTreeModels.ts | 13 +++++-- .../preferences/common/preferences.ts | 1 + .../preferences/common/preferencesModels.ts | 1 + 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 92733560d7b..bc6fdef7bc5 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -399,13 +399,15 @@ -webkit-user-select: text; } -.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description { +.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description, +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description { display: flex; font-weight: 600; margin: 6px 0 12px 0; } -.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span { +.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span, +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span { padding-right: 5px; } @@ -413,6 +415,10 @@ color: var(--workspace-trust-state-untrusted-color) !important; } +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span.codicon.codicon-lock { + color: var(--organization-policy-icon-color) !important; +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-validation-message { display: none; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 7b551ab1466..40cf05955e4 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -584,6 +584,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { containerElement: HTMLElement; categoryElement: HTMLElement; labelElement: SimpleIconLabel; + policyWarningElement: HTMLElement; descriptionElement: HTMLElement; controlElement: HTMLElement; deprecationWarningElement: HTMLElement; @@ -605,6 +606,7 @@ type ISettingNumberItemTemplate = ISettingTextItemTemplate; interface ISettingEnumItemTemplate extends ISettingItemTemplate { selectBox: SelectBox; + selectElement: HTMLSelectElement | null; enumDescriptionElement: HTMLElement; } @@ -781,9 +783,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category')); const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label')); const labelElement = new SimpleIconLabel(labelElementContainer); - const miscLabel = new SettingsTreeMiscLabel(titleElement); - const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); @@ -795,6 +795,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const toDispose = new DisposableStore(); + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -805,6 +813,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre containerElement: container, categoryElement, labelElement, + policyWarningElement, descriptionElement, controlElement, deprecationWarningElement, @@ -908,6 +917,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); })); + template.policyWarningElement.hidden = !element.setting.hasPolicyValue; + this.updateSettingTabbable(element, template); template.elementDisposables.add(element.onDidChangeTabbable(() => { this.updateSettingTabbable(element, template); @@ -1538,6 +1549,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); + template.inputBox.inputElement.disabled = !!dataElement.setting.hasPolicyValue; template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(value); @@ -1633,6 +1645,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const template: ISettingEnumItemTemplate = { ...common, selectBox, + selectElement, enumDescriptionElement }; @@ -1704,6 +1717,10 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre } }; + if (template.selectElement) { + template.selectElement.disabled = !!dataElement.setting.hasPolicyValue; + } + template.enumDescriptionElement.innerText = ''; } } @@ -1757,6 +1774,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); + template.inputBox.setEnabled(!dataElement.setting.hasPolicyValue); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(nullNumParseFn(value)); @@ -1789,7 +1807,6 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); - const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = new DisposableStore(); @@ -1819,6 +1836,14 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const toolbar = this.renderSettingToolbar(toolbarContainer); toDispose.add(toolbar); + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + const template: ISettingBoolItemTemplate = { toDispose, elementDisposables: new DisposableStore(), @@ -1828,6 +1853,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre labelElement, controlElement, checkbox, + policyWarningElement, descriptionElement, deprecationWarningElement, miscLabel, @@ -1852,6 +1878,11 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre template.onChange = undefined; template.checkbox.checked = dataElement.value; template.checkbox.setTitle(dataElement.setting.key); + if (dataElement.setting.hasPolicyValue) { + template.checkbox.disable(); + } else { + template.checkbox.enable(); + } template.onChange = onChange; } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6c4169e6b7c..cdf7ede3a9e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -181,7 +181,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } update(inspectResult: IInspectResult, isWorkspaceTrusted: boolean): void { - const { isConfigured, inspected, targetSelector, inspectedLanguageOverrides, languageSelector } = inspectResult; + let { isConfigured, inspected, targetSelector, inspectedLanguageOverrides, languageSelector } = inspectResult; switch (targetSelector) { case 'workspaceFolderValue': @@ -213,20 +213,25 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } - if (languageSelector && this.languageOverrideValues.has(languageSelector)) { + const policyValue = inspected.policyValue; + if (inspected.policyValue) { + isConfigured = false; // The user did not manually configure the setting themselves. + displayValue = policyValue; + this.scopeValue = policyValue; + this.defaultValue = inspected.defaultValue; + } else if (languageSelector && this.languageOverrideValues.has(languageSelector)) { const overrideValues = this.languageOverrideValues.get(languageSelector)!; // In the worst case, go back to using the previous display value. // Also, sometimes the override is in the form of a default value override, so consider that second. displayValue = (isConfigured ? overrideValues[targetSelector] : overrideValues.defaultValue) ?? displayValue; - this.value = displayValue; this.scopeValue = isConfigured && overrideValues[targetSelector]; this.defaultValue = overrideValues.defaultValue ?? inspected.defaultValue; } else { - this.value = displayValue; this.scopeValue = isConfigured && inspected[targetSelector]; this.defaultValue = inspected.defaultValue; } + this.value = displayValue; this.isConfigured = isConfigured; if (isConfigured || this.setting.tags || this.tags || this.setting.restricted) { // Don't create an empty Set for all 1000 settings, only if needed diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 5aaf1198fe4..c33637b5934 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -91,6 +91,7 @@ export interface ISetting { isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; + hasPolicyValue?: boolean; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 000a74b77ca..cd9c8d784ef 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -741,6 +741,7 @@ export class DefaultSettings extends Disposable { tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, restricted: prop.restricted, + hasPolicyValue: !!prop.policy, extensionInfo: extensionInfo, deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, From 7e34708dcccfbec7837c825ed85cc470ec20df89 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 11 May 2022 11:46:20 -0700 Subject: [PATCH 042/350] prepare for removing proxy kernel. --- .../api/browser/mainThreadNotebookKernels.ts | 8 ++++- .../browser/mainThreadNotebookProxyKernels.ts | 10 +++++- .../workbench/api/common/extHost.api.impl.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 6 ++++ .../api/common/extHostNotebookKernels.ts | 9 +++++ src/vs/workbench/api/common/extHostTypes.ts | 5 +++ .../editorStatusBar/editorStatusBar.ts | 8 ++--- .../notebook/browser/controller/apiActions.ts | 6 ++-- .../browser/view/cellParts/cellExecution.ts | 3 +- .../view/renderers/backLayerWebView.ts | 12 +++---- .../notebookEditorWidgetContextKeys.ts | 4 +-- .../viewParts/notebookKernelActionViewItem.ts | 34 ++++++++----------- .../notebook/common/notebookKernelService.ts | 13 +++++++ ...code.proposed.notebookProxyController.d.ts | 8 +++++ 14 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index a8809c58016..fc5501b8162 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -17,7 +17,7 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { IResolvedNotebookKernel, INotebookKernelChangeEvent, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookControllerState } from '../common/extHost.protocol'; abstract class MainThreadKernel implements IResolvedNotebookKernel { readonly type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; @@ -35,6 +35,7 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel { description?: string; detail?: string; kind?: string; + state?: NotebookControllerState; supportedLanguages: string[]; implementsExecutionOrder: boolean; localResourceRoot: URI; @@ -57,6 +58,7 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel { this.description = data.description; this.detail = data.detail; this.kind = data.kind; + this.state = data.state; this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredLanguageIds(); this.implementsExecutionOrder = data.supportsExecutionOrder ?? false; this.localResourceRoot = URI.revive(data.extensionLocation); @@ -83,6 +85,10 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel { this.kind = data.kind; event.kind = true; } + if (data.state !== undefined) { + this.state = data.state; + event.state = true; + } if (data.supportedLanguages !== undefined) { this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._languageService.getRegisteredLanguageIds(); event.supportedLanguages = true; diff --git a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts index 74e7290161d..e7a83bb8631 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts @@ -7,9 +7,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { INotebookKernelService, INotebookProxyKernel, INotebookProxyKernelChangeEvent, ProxyKernelState, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, INotebookProxyKernel, INotebookProxyKernelChangeEvent, ProxyKernelState, NotebookKernelType, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { ExtHostContext, ExtHostNotebookProxyKernelsShape, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from '../common/extHost.protocol'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; abstract class MainThreadProxyKernel implements INotebookProxyKernel { readonly type: NotebookKernelType.Proxy = NotebookKernelType.Proxy; @@ -19,11 +20,16 @@ abstract class MainThreadProxyKernel implements INotebookProxyKernel { readonly viewType: string; readonly extension: ExtensionIdentifier; readonly preloadProvides: string[] = []; + readonly preloadUris: URI[] = []; label: string; description?: string; detail?: string; kind?: string; supportedLanguages: string[] = []; + localResourceRoot: URI; + state?: NotebookControllerState | undefined; + implementsInterrupt?: boolean | undefined; + implementsExecutionOrder?: boolean | undefined; connectionState: ProxyKernelState; constructor(data: INotebookProxyKernelDto) { @@ -35,10 +41,12 @@ abstract class MainThreadProxyKernel implements INotebookProxyKernel { this.description = data.description; this.detail = data.detail; this.kind = data.kind; + this.localResourceRoot = URI.revive(data.extensionLocation); this.connectionState = ProxyKernelState.Disconnected; } + update(data: Partial) { const event: INotebookProxyKernelChangeEvent = Object.create(null); if (data.label !== undefined) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index bba2d04f64e..86cb018e404 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1320,6 +1320,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem, NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, + NotebookControllerState: extHostTypes.NotebookControllerState, PortAttributes: extHostTypes.PortAttributes, LinkedEditingRanges: extHostTypes.LinkedEditingRanges, TestResultState: extHostTypes.TestResultState, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9fa08b4b6c3..25f33cb3f4a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -970,6 +970,11 @@ export interface MainThreadNotebookDocumentsShape extends IDisposable { $trySaveNotebook(uri: UriComponents): Promise; } +export enum NotebookControllerState { + Idle = 1, + Connecting = 2 +} + export interface INotebookKernelDto2 { id: string; notebookType: string; @@ -979,6 +984,7 @@ export interface INotebookKernelDto2 { detail?: string; description?: string; kind?: string; + state?: NotebookControllerState; supportedLanguages?: string[]; supportsInterrupt?: boolean; supportsExecutionOrder?: boolean; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index f99ead59ac7..4e4ca9640c5 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -196,6 +196,15 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { get rendererScripts() { return data.preloads ? data.preloads.map(extHostTypeConverters.NotebookRendererScript.to) : []; }, + get state() { + checkProposedApiEnabled(extension, 'notebookProxyController'); + return data.state; + }, + set state(value) { + checkProposedApiEnabled(extension, 'notebookProxyController'); + data.state = value; + _update(); + }, get executeHandler() { return _executeHandler; }, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 977f0cdf142..10844646688 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3369,6 +3369,11 @@ export class NotebookRendererScript { } } +export enum NotebookControllerState { + Idle = 1, + Connecting = 2 +} + //#endregion //#region Timeline diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 920669194f6..22233100d5e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -28,7 +28,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -205,8 +205,8 @@ registerAction2(class extends Action2 { }); } - if (!all.find(item => item.type === NotebookKernelType.Resolved)) { - // there is no resolved kernel, show the install from marketplace + if (!all.length) { + // there is no kernel, show the install from marketplace quickPickItems.push({ id: 'install', label: nls.localize('installKernels', "Install kernels from the marketplace"), @@ -379,7 +379,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { this._kernelInfoElement.add(this._statusbarService.addEntry( { name: nls.localize('notebook.info', "Notebook Kernel Info"), - text: `$(notebook-kernel-select) ${kernel.label}`, + text: `$(notebook-kernel-select) ${kernel.label}` + (kernel.state === NotebookControllerState.Idle ? '' : ' Connecting...'), ariaLabel: kernel.label, tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, command: SELECT_KERNEL_ID, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts index 68d9d0f8dcc..d0c6379a54b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts @@ -7,7 +7,7 @@ import * as glob from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { isDocumentExcludePattern, TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor): { @@ -66,13 +66,13 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg const uri = URI.revive(args.uri as UriComponents); const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType }); - return kernels.all.filter(kernel => kernel.type === NotebookKernelType.Resolved).map((provider) => ({ + return kernels.all.map(provider => ({ id: provider.id, label: provider.label, kind: provider.kind, description: provider.description, detail: provider.detail, isPreferred: false, // todo@jrieken,@rebornix - preloads: (provider as IResolvedNotebookKernel).preloadUris, + preloads: provider.preloadUris, })); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index 469e7cbef3d..1d9865937ea 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -9,7 +9,6 @@ import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/no import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class CellExecutionPart extends CellPart { private kernelDisposables = this._register(new DisposableStore()); @@ -42,7 +41,7 @@ export class CellExecutionPart extends CellPart { } private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata): void { - if (this._notebookEditor.activeKernel?.type === NotebookKernelType.Resolved && this._notebookEditor.activeKernel?.implementsExecutionOrder) { + if (this._notebookEditor.activeKernel?.implementsExecutionOrder) { const executionOrderLabel = typeof internalMetadata.executionOrder === 'number' ? `[${internalMetadata.executionOrder}]` : '[ ]'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index cc2eb8bf93a..1d82b850c6d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -37,7 +37,7 @@ import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebo import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; @@ -907,7 +907,7 @@ var requirejs = (function() { } this._preloadsCache.clear(); - if (this._currentKernel?.type === NotebookKernelType.Resolved) { + if (this._currentKernel) { this._updatePreloadsFromKernel(this._currentKernel); } @@ -1407,14 +1407,14 @@ var requirejs = (function() { const previousKernel = this._currentKernel; this._currentKernel = kernel; - if (previousKernel?.type === NotebookKernelType.Resolved && previousKernel.preloadUris.length > 0) { + if (previousKernel && previousKernel.preloadUris.length > 0) { this.webview?.reload(); // preloads will be restored after reload - } else if (kernel?.type === NotebookKernelType.Resolved) { + } else if (kernel) { this._updatePreloadsFromKernel(kernel); } } - private _updatePreloadsFromKernel(kernel: IResolvedNotebookKernel) { + private _updatePreloadsFromKernel(kernel: INotebookKernel) { const resources: IControllerPreload[] = []; for (const preload of kernel.preloadUris) { const uri = this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https') @@ -1440,7 +1440,7 @@ var requirejs = (function() { const mixedResourceRoots = [ ...(this.localResourceRootsCache || []), - ...(this._currentKernel?.type === NotebookKernelType.Resolved ? [this._currentKernel.localResourceRoot] : []), + ...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []), ]; this.webview.localResourcesRoot = mixedResourceRoots; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index b1cf70a4946..a90477db542 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -8,7 +8,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class NotebookEditorContextKeys { @@ -148,7 +148,7 @@ export class NotebookEditorContextKeys { const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.textModel); this._notebookKernelCount.set(all.length); - this._interruptibleKernel.set((selected?.type === NotebookKernelType.Resolved && selected.implementsInterrupt) ?? false); + this._interruptibleKernel.set(selected?.implementsInterrupt ?? false); this._notebookKernelSelected.set(Boolean(selected)); this._notebookKernel.set(selected?.id ?? ''); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index e5814ce98ec..69917ca972b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { INotebookKernelMatchResult, INotebookKernelService, NotebookKernelType, ProxyKernelState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -68,32 +68,20 @@ export class NotebooKernelActionViewItem extends ActionViewItem { private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { this._kernelDisposable.clear(); this._action.enabled = true; - const selectedOrSuggested = info.selected ?? ((info.suggestions.length === 1 && info.suggestions[0].type === NotebookKernelType.Resolved) ? info.suggestions[0] : undefined); + const selectedOrSuggested = info.selected ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined); if (selectedOrSuggested) { // selected or suggested kernel - this._action.label = selectedOrSuggested.label; + this._action.label = this._generateKenrelLabel(selectedOrSuggested); this._action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? ''; if (!info.selected) { // special UI for selected kernel? } - if (selectedOrSuggested.type === NotebookKernelType.Proxy) { - if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { - this._action.label = localize('initializing', "Initializing..."); - } else { - this._action.label = selectedOrSuggested.label; + this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => { + if (e.state) { + this._action.label = this._generateKenrelLabel(selectedOrSuggested); } - - this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => { - if (e.connectionState) { - if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { - this._action.label = localize('initializing', "Initializing..."); - } else { - this._action.label = selectedOrSuggested.label; - } - } - })); - } + })); } else { // many kernels or no kernels this._action.label = localize('select', "Select Kernel"); @@ -101,6 +89,14 @@ export class NotebooKernelActionViewItem extends ActionViewItem { } } + private _generateKenrelLabel(kernel: INotebookKernel) { + if (kernel.state === NotebookControllerState.Connecting) { + return localize('kernelconnecting', "Connecting..."); + } else { + return kernel.label; + } + } + private _resetAction(): void { this._action.enabled = false; this._action.label = ''; diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 4f5304600b0..7a23d36ecd5 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -27,6 +27,7 @@ export interface INotebookKernelChangeEvent { description?: true; detail?: true; kind?: true; + state?: true; supportedLanguages?: true; hasExecutionOrder?: true; } @@ -36,6 +37,11 @@ export const enum NotebookKernelType { Proxy = 1 } +export enum NotebookControllerState { + Idle = 1, + Connecting = 2 +} + export interface IResolvedNotebookKernel { readonly type: NotebookKernelType.Resolved; readonly id: string; @@ -51,6 +57,7 @@ export interface IResolvedNotebookKernel { description?: string; detail?: string; kind?: string; + state?: NotebookControllerState; supportedLanguages: string[]; implementsInterrupt?: boolean; implementsExecutionOrder?: boolean; @@ -74,13 +81,19 @@ export interface INotebookProxyKernel { readonly id: string; readonly viewType: string; readonly extension: ExtensionIdentifier; + readonly localResourceRoot: URI; + readonly preloadUris: URI[]; readonly preloadProvides: string[]; + readonly onDidChange: Event>; label: string; description?: string; detail?: string; kind?: string; + state?: NotebookControllerState; supportedLanguages: string[]; + implementsInterrupt?: boolean; + implementsExecutionOrder?: boolean; connectionState: ProxyKernelState; resolveKernel(uri: URI): Promise; } diff --git a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts index 07f8e833f15..3fe7cac5428 100644 --- a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts @@ -5,6 +5,14 @@ declare module 'vscode' { + export enum NotebookControllerState { + Idle = 1, + Connecting = 2 + } + + export interface NotebookController { + state?: NotebookControllerState; + } export interface NotebookProxyController { /** From 3982c0ed0b9db59dad3129516bf450a923aacf97 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 May 2022 12:28:08 +0200 Subject: [PATCH 043/350] - change policy file location next to argv.json - introduce __enable-file-policy` flag to enable file based policies --- src/vs/platform/environment/common/argv.ts | 2 +- .../environment/common/environmentService.ts | 12 +++++++++++- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/platform/policy/common/filePolicyService.ts | 6 ++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 3834247b45b..1c903e2d25f 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,7 +88,7 @@ export interface NativeParsedArgs { 'sync'?: 'on' | 'off'; '__sandbox'?: boolean; 'logsPath'?: string; - 'policy-file'?: string; + '__enable-file-policy'?: boolean; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index d3214f7036f..1d7d75afbb9 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -238,7 +238,17 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } @memoize - get policyFile(): URI | undefined { return this.args['policy-file'] ? URI.file(this.args['policy-file']) : undefined; } + get policyFile(): URI | undefined { + if (this.args['__enable-file-policy']) { + const vscodePortable = env['VSCODE_PORTABLE']; + if (vscodePortable) { + return URI.file(join(vscodePortable, 'policy.json')); + } + + return joinPath(this.userHome, this.productService.dataFolderName, 'policy.json'); + } + return undefined; + } get args(): NativeParsedArgs { return this._args; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 0a69e9cd2c4..239aaaf7045 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -73,7 +73,6 @@ export const OPTIONS: OptionDescriptions> = { 'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, 'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable an extension.") }, 'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] }, - 'policy-file': { type: 'string', cat: 't', description: localize('policyFile', "Path of the file defining policies") }, 'inspect-extensions': { type: 'string', deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, @@ -126,6 +125,7 @@ export const OPTIONS: OptionDescriptions> = { 'open-devtools': { type: 'boolean' }, '__sandbox': { type: 'boolean' }, 'logsPath': { type: 'string' }, + '__enable-file-policy': { type: 'boolean' }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 1fbec3f40cd..44d3105dad3 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -9,7 +9,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { Disposable } from 'vs/base/common/lifecycle'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; @@ -69,7 +69,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { policies.set(key, raw[key]); } } catch (error) { - this.logService.error(`[FilePolicyService] Failed to read policies`, error); + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(`[FilePolicyService] Failed to read policies`, error); + } } return policies; From 281d6155e5b03cba5523b83fe504ee9f8635039b Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 12 May 2022 17:15:10 -0700 Subject: [PATCH 044/350] Tweak UI and fix hasPolicy detection --- .../browser/media/settingsEditor2.css | 4 ++++ .../preferences/browser/settingsTree.ts | 24 +++++++++---------- .../preferences/browser/settingsTreeModels.ts | 11 ++++++--- .../preferences/common/preferences.ts | 1 - .../preferences/common/preferencesModels.ts | 1 - 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index bc6fdef7bc5..7b4684b2961 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -406,6 +406,10 @@ margin: 6px 0 12px 0; } +.settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description[hidden] { + display: none; +} + .settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-untrusted > .setting-item-contents .setting-item-trust-description > span, .settings-editor > .settings-body .settings-tree-container .setting-item > .setting-item-contents .setting-item-policy-description > span { padding-right: 5px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 40cf05955e4..673de4bf5aa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -36,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorBackground, editorErrorForeground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, editorErrorForeground, editorInfoForeground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; @@ -797,11 +797,11 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); })); const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -917,7 +917,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); })); - template.policyWarningElement.hidden = !element.setting.hasPolicyValue; + template.policyWarningElement.hidden = !element.hasPolicyValue; this.updateSettingTabbable(element, template); template.elementDisposables.add(element.onDidChangeTabbable(() => { @@ -1549,7 +1549,7 @@ abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer imple template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); - template.inputBox.inputElement.disabled = !!dataElement.setting.hasPolicyValue; + template.inputBox.inputElement.disabled = !!dataElement.hasPolicyValue; template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(value); @@ -1718,7 +1718,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre }; if (template.selectElement) { - template.selectElement.disabled = !!dataElement.setting.hasPolicyValue; + template.selectElement.disabled = !!dataElement.hasPolicyValue; } template.enumDescriptionElement.innerText = ''; @@ -1774,7 +1774,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT template.onChange = undefined; template.inputBox.value = dataElement.value; template.inputBox.setAriaLabel(dataElement.setting.key); - template.inputBox.setEnabled(!dataElement.setting.hasPolicyValue); + template.inputBox.setEnabled(!dataElement.hasPolicyValue); template.onChange = value => { if (!renderValidations(dataElement, template, false)) { onChange(nullNumParseFn(value)); @@ -1838,11 +1838,11 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorErrorForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorErrorForeground?.toString() || ''); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); })); const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is not configurable due to your organization's policy."); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); const template: ISettingBoolItemTemplate = { toDispose, @@ -1878,7 +1878,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre template.onChange = undefined; template.checkbox.checked = dataElement.value; template.checkbox.setTitle(dataElement.setting.key); - if (dataElement.setting.hasPolicyValue) { + if (dataElement.hasPolicyValue) { template.checkbox.disable(); } else { template.checkbox.enable(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index cdf7ede3a9e..6e0c5b34b43 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -138,6 +138,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { */ isUntrusted = false; + /** + * Whether the setting is under a policy that blocks all changes. + */ + hasPolicyValue = false; + tags?: Set; overriddenScopeList: string[] = []; languageOverrideValues: Map> = new Map>(); @@ -213,11 +218,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } - const policyValue = inspected.policyValue; if (inspected.policyValue) { + this.hasPolicyValue = true; isConfigured = false; // The user did not manually configure the setting themselves. - displayValue = policyValue; - this.scopeValue = policyValue; + displayValue = inspected.policyValue; + this.scopeValue = inspected.policyValue; this.defaultValue = inspected.defaultValue; } else if (languageSelector && this.languageOverrideValues.has(languageSelector)) { const overrideValues = this.languageOverrideValues.get(languageSelector)!; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index c33637b5934..5aaf1198fe4 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -91,7 +91,6 @@ export interface ISetting { isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; - hasPolicyValue?: boolean; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index cd9c8d784ef..000a74b77ca 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -741,7 +741,6 @@ export class DefaultSettings extends Disposable { tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, restricted: prop.restricted, - hasPolicyValue: !!prop.policy, extensionInfo: extensionInfo, deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, From e00030977e6eb14422594c0fa475eb133d4cbc39 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 13 May 2022 10:19:59 +0200 Subject: [PATCH 045/350] Implements glyph buttons, alignment and decorations. --- .../browser/breakpointEditorContribution.ts | 2 +- .../contrib/mergeEditor/browser/icons.ts | 10 + .../mergeEditor/browser/media/mergeEditor.css | 31 ++- .../mergeEditor/browser/mergeEditor.ts | 221 +++++++++++++++--- .../mergeEditor/browser/mergeEditorModel.ts | 47 +++- .../contrib/mergeEditor/browser/model.ts | 128 ++++++++-- 6 files changed, 375 insertions(+), 64 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/icons.ts diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 4eab6791a7f..a5bc41d7cb0 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -433,7 +433,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (decorations) { for (const { options } of decorations) { const clz = options.glyphMarginClassName; - if (clz && (!clz.includes('codicon-') || clz.includes('codicon-testing-'))) { + if (clz && (!clz.includes('codicon-') || clz.includes('codicon-testing-') || clz.includes('codicon-merge-'))) { return false; } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/icons.ts b/src/vs/workbench/contrib/mergeEditor/browser/icons.ts new file mode 100644 index 00000000000..a37f0e66faf --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/icons.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; + +export const acceptConflictIcon = registerIcon('merge-accept-conflict-icon', Codicon.check, localize('acceptConflictIcon', 'TODO.')); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index 2a1dd749cbb..749b947af55 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -3,13 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .merge-editor .code-view>.title { +.monaco-workbench .merge-editor .code-view > .title { padding: 0 0 0 10px; height: 30px; display: flex; align-content: center; } -.monaco-workbench .merge-editor .code-view>.title .monaco-icon-label { +.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label { margin: auto 0; } + +.monaco-workbench .merge-editor .code-view > .container { + display: flex; + flex-direction: row; +} + +.monaco-workbench .merge-editor .code-view > .container > .gutter { + min-width: 20px; + /* background-color: yellow; */ + margin-right: 3px; + position: relative; +} + +.monaco-editor .merge-accept-conflict-glyph { + cursor: pointer; +} + +.merge-accept-foo { + background-color: rgb(170 175 170 / 15%); +} + +.merge-accept-gutter-marker { + min-height: 30px; + min-width: 20px; + background-color: yellow; + position: absolute; +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 81b82849c05..c846f45c87f 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/mergeEditor'; -import { Dimension, reset } from 'vs/base/browser/dom'; +import { $, Dimension, reset } from 'vs/base/browser/dom'; import { Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -19,7 +19,7 @@ import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/merge import { DisposableStore } from 'vs/base/common/lifecycle'; import { Direction, Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { ITextModel } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -30,10 +30,15 @@ import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IAction } from 'vs/base/common/actions'; +import { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; +import { acceptConflictIcon } from 'vs/workbench/contrib/mergeEditor/browser/icons'; +import { ConflictGroup, MergeState } from 'vs/workbench/contrib/mergeEditor/browser/model'; + export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); @@ -46,8 +51,8 @@ export class MergeEditor extends EditorPane { private _grid!: Grid; - private readonly inputOneView = this.instantiation.createInstance(CodeEditorView, { readonly: true }); - private readonly inputTwoView = this.instantiation.createInstance(CodeEditorView, { readonly: true }); + private readonly input1View = this.instantiation.createInstance(CodeEditorView, { readonly: true }); + private readonly input2View = this.instantiation.createInstance(CodeEditorView, { readonly: true }); private readonly inputResultView = this.instantiation.createInstance(CodeEditorView, { readonly: false }); private readonly _ctxIsMergeEditor: IContextKey; @@ -68,18 +73,18 @@ export class MergeEditor extends EditorPane { this._ctxUsesColumnLayout = ctxUsesColumnLayout.bindTo(_contextKeyService); const reentrancyBarrier = new ReentrancyBarrier(); - this._store.add(this.inputOneView.editor.onDidScrollChange(c => { + this._store.add(this.input1View.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { - this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); }); } })); - this._store.add(this.inputTwoView.editor.onDidScrollChange(c => { + this._store.add(this.input2View.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { - this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); }); } @@ -87,8 +92,8 @@ export class MergeEditor extends EditorPane { this._store.add(this.inputResultView.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { - this.inputOneView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); - this.inputTwoView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); }); } })); @@ -131,9 +136,9 @@ export class MergeEditor extends EditorPane { { size: 38, groups: [{ - data: this.inputOneView + data: this.input1View }, { - data: this.inputTwoView + data: this.input2View }] }, { @@ -163,9 +168,122 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.inputOneView.setModel(model.input1, localize('yours', 'Yours'), undefined); - this.inputTwoView.setModel(model.input2, localize('theirs', 'Theirs',), undefined); + this.input1View.setModel(model.input1, localize('yours', 'Yours'), undefined); + this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), undefined); this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); + + + const input1LineNumberToMergeableDiffMap = new Map(); + + this.input1View.editor.onMouseDown(e => { + if (e.target.element && e.target.element.className.includes('merge-')) { + const lineNumber = e.target.position?.lineNumber; + if (lineNumber) { + const diff = input1LineNumberToMergeableDiffMap.get(lineNumber); + if (diff) { + const state = (model.getState(diff) || new MergeState(false, false, false)).toggleInput1(); + model.setConflictResolutionStatus(diff, state); + } + } + } + }); + + this.input1View.editor.changeDecorations(c => { + for (const mergeableDiff of model.mergeableDiffs) { + if (mergeableDiff.input1FullDiff) { + const lineNumber = mergeableDiff.input1FullDiff.modifiedRange.startLineNumber; + input1LineNumberToMergeableDiffMap.set(lineNumber, mergeableDiff); + c.addDecoration(Range.fromPositions(new Position(lineNumber, 0)), { + description: 'foo', + glyphMarginClassName: ThemeIcon.asClassName(acceptConflictIcon) + ' merge-accept-conflict-glyph', + }); + } + } + }); + + + + const input2LineNumberToMergeableDiffMap = new Map(); + + this.input2View.editor.onMouseDown(e => { + if (e.target.element && e.target.element.className.includes('merge-')) { + const lineNumber = e.target.position?.lineNumber; + if (lineNumber) { + const diff = input2LineNumberToMergeableDiffMap.get(lineNumber); + if (diff) { + const state = (model.getState(diff) || new MergeState(false, false, false)).toggleInput2(); + model.setConflictResolutionStatus(diff, state); + } + } + } + }); + + + + this.input2View.editor.changeDecorations(c => { + for (const mergeableDiff of model.mergeableDiffs) { + if (mergeableDiff.input2FullDiff) { + const lineNumber = mergeableDiff.input2FullDiff.modifiedRange.startLineNumber; + input2LineNumberToMergeableDiffMap.set(lineNumber, mergeableDiff); + c.addDecoration(Range.fromPositions(new Position(lineNumber, 1)), { + description: 'foo', + glyphMarginClassName: ThemeIcon.asClassName(acceptConflictIcon) + ' merge-accept-conflict-glyph', + }); + } + } + }); + + + let input1Decorations = new Array(); + let input2Decorations = new Array(); + + + for (const m of model.mergeableDiffs) { + + if (!m.totalInput1Range.isEmpty) { + input1Decorations.push({ + range: new Range(m.totalInput1Range.startLineNumber, 1, m.totalInput1Range.endLineNumberExclusive - 1, 1), + options: { + isWholeLine: true, + className: 'merge-accept-foo', + description: 'foo2' + } + }); + } + + if (!m.totalInput2Range.isEmpty) { + input2Decorations.push({ + range: new Range(m.totalInput2Range.startLineNumber, 1, m.totalInput2Range.endLineNumberExclusive - 1, 1), + options: { + isWholeLine: true, + className: 'merge-accept-foo', + description: 'foo2' + } + }); + } + + const max = Math.max(m.totalInput1Range.lineCount, m.totalInput2Range.lineCount, 1); + + this.input1View.editor.changeViewZones(a => { + a.addZone({ + afterLineNumber: m.totalInput1Range.endLineNumberExclusive - 1, + heightInLines: max - m.totalInput1Range.lineCount, + domNode: $('div.diagonal-fill'), + }); + }); + + this.input2View.editor.changeViewZones(a => { + a.addZone({ + afterLineNumber: m.totalInput2Range.endLineNumberExclusive - 1, + heightInLines: max - m.totalInput2Range.lineCount, + domNode: $('div.diagonal-fill'), + }); + }); + } + + this.input1View.editor.deltaDecorations([], input1Decorations); + this.input2View.editor.deltaDecorations([], input2Decorations); + } protected override setEditorVisible(visible: boolean): void { @@ -175,7 +293,7 @@ export class MergeEditor extends EditorPane { // ---- interact with "outside world" via `getControl`, `scopedContextKeyService` override getControl(): IEditorControl | undefined { - for (const view of [this.inputOneView, this.inputTwoView, this.inputResultView]) { + for (const view of [this.input1View, this.input2View, this.inputResultView]) { if (view.editor.hasWidgetFocus()) { return view.editor; } @@ -196,10 +314,10 @@ export class MergeEditor extends EditorPane { toggleLayout(): void { if (!this._usesColumnLayout) { - this._grid.moveView(this.inputResultView, Sizing.Distribute, this.inputOneView, Direction.Right); + this._grid.moveView(this.inputResultView, Sizing.Distribute, this.input1View, Direction.Right); } else { - this._grid.moveView(this.inputResultView, this._grid.height * .62, this.inputOneView, Direction.Down); - this._grid.moveView(this.inputTwoView, Sizing.Distribute, this.inputOneView, Direction.Right); + this._grid.moveView(this.inputResultView, this._grid.height * .62, this.input1View, Direction.Down); + this._grid.moveView(this.input2View, Sizing.Distribute, this.input1View, Direction.Right); } this._usesColumnLayout = !this._usesColumnLayout; this._ctxUsesColumnLayout.set(this._usesColumnLayout); @@ -215,9 +333,10 @@ class CodeEditorView implements IView { // preferredWidth?: number | undefined; // preferredHeight?: number | undefined; - element: HTMLElement = document.createElement('div'); - private _titleElement = document.createElement('div'); - private _editorElement = document.createElement('div'); + element: HTMLElement; + private _titleElement: HTMLElement; + private _editorElement: HTMLElement; + private _gutterDiv: HTMLElement; minimumWidth: number = 10; maximumWidth: number = Number.MAX_SAFE_INTEGER; @@ -229,24 +348,36 @@ class CodeEditorView implements IView { private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - private _title = new IconLabel(this._titleElement, { supportIcons: true }); + private _title: IconLabel; - public readonly editor = this.instantiationService.createInstance( - CodeEditorWidget, - this._editorElement, - { minimap: { enabled: false }, readOnly: this._options.readonly }, - {} - ); + public readonly editor: CodeEditorWidget; + private readonly gutter: EditorGutter; constructor( private readonly _options: ICodeEditorViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - this.element.classList.add('code-view'); - this._titleElement.classList.add('title'); - this.element.appendChild(this._titleElement); - this.element.appendChild(this._editorElement); + this.element = $( + 'div.code-view', + {}, + this._titleElement = $('div.title'), + $('div.container', {}, + this._gutterDiv = $('div.gutter'), + this._editorElement = $('div'), + ), + ); + + this.editor = this.instantiationService.createInstance( + CodeEditorWidget, + this._editorElement, + { minimap: { enabled: false }, readOnly: this._options.readonly, /*glyphMargin: false,*/ lineNumbersMinChars: 2 }, + { contributions: [] } + ); + + this._title = new IconLabel(this._titleElement, { supportIcons: true }); + this.gutter = new EditorGutter(this.editor, this._gutterDiv); + console.log(this.gutter); // keep alive } public setModel(model: ITextModel, title: string, description: string | undefined): void { @@ -259,7 +390,7 @@ class CodeEditorView implements IView { this.element.style.height = `${height}px`; this.element.style.top = `${top}px`; this.element.style.left = `${left}px`; - this.editor.layout({ width, height: height - this._titleElement.clientHeight }); + this.editor.layout({ width: width - this._gutterDiv.clientWidth, height: height - this._titleElement.clientHeight }); } } @@ -278,3 +409,23 @@ class ReentrancyBarrier { } } } + +class EditorGutter { + constructor( + private readonly _editor: ICodeEditor, + private readonly _domNode: HTMLElement, + ) { + this._editor.onDidScrollChange(() => { + this.render(); + }); + } + + private node = $('div.merge-accept-gutter-marker'); + + private render(): void { + this._domNode.append(this.node); + + const top = this._editor.getTopForLineNumber(10) - this._editor.getScrollTop(); + this.node.style.top = `${top}px`; + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 13fd032a5ea..081869931c3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -8,7 +8,7 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { MergeableDiff, LineEdit, LineEdits, LineDiff, MergeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { ConflictGroup, LineEdit, LineEdits, LineDiff, MergeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; export class MergeEditorModelFactory { constructor( @@ -75,7 +75,7 @@ export class MergeEditorModel extends EditorModel { readonly input2: ITextModel, readonly result: ITextModel, private readonly inputOneLinesDiffs: readonly LineDiff[], - private readonly inputTwoLinesDiffs: readonly LineDiff[], + private readonly inputTwoLinesDiffs: readonly LineDiff[] ) { super(); @@ -105,25 +105,52 @@ export class MergeEditorModel extends EditorModel { return this.resultEdits.diffs; } - public readonly mergeableDiffs = MergeableDiff.fromDiffs(this.inputOneLinesDiffs, this.inputTwoLinesDiffs); + public readonly mergeableDiffs = ConflictGroup.partitionDiffs( + this.ancestor, + this.input1, + this.inputOneLinesDiffs, + this.input2, + this.inputTwoLinesDiffs + ); - public getState(conflict: MergeableDiff): MergeState | undefined { - const existingDiff = this.resultEdits.findConflictingDiffs(conflict.originalRange); - if (existingDiff) { - //existingDiff. + public getState(conflict: ConflictGroup): MergeState | undefined { + const existingDiff = this.resultEdits.findConflictingDiffs( + conflict.totalOriginalRange + ); + if (!existingDiff) { + return new MergeState(false, false, false); + } + + const input1Edit = conflict.getInput1LineEdit(); + if (input1Edit && existingDiff.getLineEdit().equals(input1Edit)) { + return new MergeState(true, false, false); + } + + const input2Edit = conflict.getInput2LineEdit(); + if (input2Edit && existingDiff.getLineEdit().equals(input2Edit)) { + return new MergeState(false, true, false); } return undefined; } // Undo all edits of result that conflict with the conflict!! - public setConflictResolutionStatus(conflict: MergeableDiff, status: MergeState): void { - const existingDiff = this.resultEdits.findConflictingDiffs(conflict.originalRange); + public setConflictResolutionStatus( + conflict: ConflictGroup, + status: MergeState + ): void { + const existingDiff = this.resultEdits.findConflictingDiffs( + conflict.totalOriginalRange + ); if (existingDiff) { this.resultEdits.removeDiff(existingDiff); } - const edit = status.input1 ? conflict.getInput1LineEdit() : conflict.getInput2LineEdit(); + const edit = status.input1 + ? conflict.getInput1LineEdit() + : status.input2 + ? conflict.getInput2LineEdit() + : undefined; if (edit) { this.resultEdits.applyEditRelativeToOriginal(edit); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model.ts b/src/vs/workbench/contrib/mergeEditor/browser/model.ts index c26686a0466..deae7ace505 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ArrayQueue, compareBy, numberComparator } from 'vs/base/common/arrays'; +import { ArrayQueue, compareBy, equals, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Range } from 'vs/editor/common/core/range'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; @@ -19,7 +19,7 @@ export class LineEdits { null, this.edits.map((e) => ({ range: new Range(e.range.startLineNumber, 1, e.range.endLineNumberExclusive, 1), - text: e.newLines.map(l => l + '\n').join(), + text: e.newLines.map(l => l + '\n').join(''), })), () => null ); @@ -27,6 +27,10 @@ export class LineEdits { } export class LineEdit { + equals(other: LineEdit) { + return this.range.equals(other.range) && equals(this.newLines, other.newLines); + } + constructor( public readonly range: LineRange, public readonly newLines: string[], @@ -34,11 +38,18 @@ export class LineEdit { ) { } } -export class MergeableDiff { - public static fromDiffs( +export class ConflictGroup { + /** + * diffs1 and diffs2 together with the conflict relation form a bipartite graph. + * This method computes strongly connected components of that graph while maintaining the side of each diff. + */ + public static partitionDiffs( + originalTextModel: ITextModel, + input1TextModel: ITextModel, diffs1: readonly LineDiff[], + input2TextModel: ITextModel, diffs2: readonly LineDiff[] - ): MergeableDiff[] { + ): ConflictGroup[] { const compareByStartLineNumber = compareBy( (d) => d.originalRange.startLineNumber, numberComparator @@ -51,7 +62,7 @@ export class MergeableDiff { diffs2.slice().sort(compareByStartLineNumber) ); - const result = new Array(); + const result = new Array(); while (true) { const lastDiff1 = queueDiffs1.peekLast(); @@ -79,7 +90,15 @@ export class MergeableDiff { moreConflictingWith.push(lastDiff1); result.push( - new MergeableDiff(moreConflictingWith, otherConflictingWith) + new ConflictGroup( + originalTextModel, + input1TextModel, + moreConflictingWith, + queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + input2TextModel, + otherConflictingWith, + queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + ) ); } else if (lastDiff2) { queueDiffs2.removeLast(); @@ -98,7 +117,15 @@ export class MergeableDiff { moreConflictingWith.push(lastDiff2); result.push( - new MergeableDiff(otherConflictingWith, moreConflictingWith) + new ConflictGroup( + originalTextModel, + input1TextModel, + otherConflictingWith, + queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + input2TextModel, + moreConflictingWith, + queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + ) ); } else { break; @@ -110,16 +137,49 @@ export class MergeableDiff { return result; } - public readonly originalRange = LineRange.hull( - this.input1Diffs - .map((d) => d.originalRange) - .concat(this.input2Diffs.map((d) => d.originalRange)) - )!; + public readonly input1FullDiff = LineDiff.hull(this.input1Diffs); + public readonly input2FullDiff = LineDiff.hull(this.input2Diffs); + + public readonly totalOriginalRange: LineRange; + public readonly totalInput1Range: LineRange; + public readonly totalInput2Range: LineRange; constructor( + public readonly originalTextModel: ITextModel, + public readonly input1TextModel: ITextModel, public readonly input1Diffs: readonly LineDiff[], - public readonly input2Diffs: readonly LineDiff[] - ) { } + public readonly input1DeltaLineCount: number, + public readonly input2TextModel: ITextModel, + public readonly input2Diffs: readonly LineDiff[], + public readonly input2DeltaLineCount: number, + ) { + if (this.input1Diffs.length === 0 && this.input2Diffs.length === 0) { + throw new BugIndicatingError('must have at least one diff'); + } + + const input1Diff = + this.input1FullDiff || + new LineDiff( + originalTextModel, + this.input2FullDiff!.originalRange, + input1TextModel, + this.input2FullDiff!.originalRange.delta(input1DeltaLineCount) + ); + + const input2Diff = + this.input2FullDiff || + new LineDiff( + originalTextModel, + this.input1FullDiff!.originalRange, + input1TextModel, + this.input1FullDiff!.originalRange.delta(input2DeltaLineCount) + ); + + const results = LineDiff.alignOriginalRegion([input1Diff, input2Diff]); + this.totalOriginalRange = results[0].originalRange; + this.totalInput1Range = results[0].modifiedRange; + this.totalInput2Range = results[1].modifiedRange; + } public get isConflicting(): boolean { return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; @@ -198,6 +258,10 @@ export class LineRange { public toString() { return `[${this.startLineNumber},${this.endLineNumberExclusive})`; } + + public equals(originalRange: LineRange) { + return this.startLineNumber === originalRange.startLineNumber && this.lineCount === originalRange.lineCount; + } } export class LineDiff { @@ -226,7 +290,7 @@ export class LineDiff { ); } - public static hull(lineDiffs: LineDiff[]): LineDiff | undefined { + public static hull(lineDiffs: readonly LineDiff[]): LineDiff | undefined { if (lineDiffs.length === 0) { return undefined; } @@ -239,6 +303,26 @@ export class LineDiff { ); } + public static alignOriginalRegion(lineDiffs: readonly LineDiff[]): LineDiff[] { + if (lineDiffs.length === 0) { + return []; + } + const originalRange = LineRange.hull(lineDiffs.map((d) => d.originalRange))!; + return lineDiffs.map(l => { + const startDelta = originalRange.startLineNumber - l.originalRange.startLineNumber; + const endDelta = originalRange.endLineNumberExclusive - l.originalRange.endLineNumberExclusive; + return new LineDiff( + l.originalTextModel, + originalRange, + l.modifiedTextModel, + new LineRange( + l.modifiedRange.startLineNumber + startDelta, + l.modifiedRange.lineCount - startDelta + endDelta + ) + ); + }); + } + constructor( public readonly originalTextModel: ITextModel, public readonly originalRange: LineRange, @@ -247,6 +331,10 @@ export class LineDiff { ) { } + public get resultingDeltaFromOriginalToModified(): number { + return this.modifiedRange.endLineNumberExclusive - this.originalRange.endLineNumberExclusive; + } + private ensureSameOriginalModel(other: LineDiff): void { if (this.originalTextModel !== other.originalTextModel) { // Both changes must refer to the same original model @@ -321,6 +409,14 @@ export class MergeState { ); } + public toggleInput1(): MergeState { + return this.withInput1(!this.input1); + } + + public toggleInput2(): MergeState { + return this.withInput2(!this.input2); + } + public get isEmpty(): boolean { return !this.input1 && !this.input2; } From d3637bb80429e8a4d0a3b42902a147b321528ef3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 13:06:02 -0700 Subject: [PATCH 046/350] Update xterm for contrast ratio luminance change Fixes #149495 --- package.json | 6 ++--- remote/package.json | 6 ++--- remote/web/package.json | 4 ++-- remote/web/yarn.lock | 16 ++++++------- remote/yarn.lock | 24 +++++++++---------- .../terminal/common/terminalColorRegistry.ts | 3 +-- yarn.lock | 24 +++++++++---------- 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index acc64a5ce02..1422323d7b0 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.41", + "xterm": "4.19.0-beta.43", "xterm-addon-search": "0.9.0-beta.35", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.33", - "xterm-headless": "4.19.0-beta.41", + "xterm-addon-webgl": "0.12.0-beta.34", + "xterm-headless": "4.19.0-beta.43", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 2d7ace1fd91..80e835671ce 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.41", + "xterm": "4.19.0-beta.43", "xterm-addon-search": "0.9.0-beta.35", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.33", - "xterm-headless": "4.19.0-beta.41", + "xterm-addon-webgl": "0.12.0-beta.34", + "xterm-headless": "4.19.0-beta.43", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 4a477cd72d7..cdbbf667adf 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,9 +10,9 @@ "tas-client-umd": "0.1.5", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.41", + "xterm": "4.19.0-beta.43", "xterm-addon-search": "0.9.0-beta.35", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.33" + "xterm-addon-webgl": "0.12.0-beta.34" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index a852b0f91e9..8316dc85a0e 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -123,12 +123,12 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.33: - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.33.tgz#cb539db9e41f06087b692f0f42491a73bc4bd013" - integrity sha512-seOm06exR36U0/EvR/CUNGuy99RAndoyWEdXg6S16rgEZ4G2Yj9iov/QdCtc4gwq9hFzVETFPlDW+Ge8xeHIzA== +xterm-addon-webgl@0.12.0-beta.34: + version "0.12.0-beta.34" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" + integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== -xterm@4.19.0-beta.41: - version "4.19.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.41.tgz#acb6009028898e9cfac41d4aa2865f81f6f56c5f" - integrity sha512-WY1NuxF/yUVN3l0TgzQGjrGM26eOu5g0Dbfam8GCkgdK5yrsgPF0xwM7UEj8sDjp5FbxEkSm//X86IIsgzqqFw== +xterm@4.19.0-beta.43: + version "4.19.0-beta.43" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" + integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index 968449c7184..a6201c6d433 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -929,20 +929,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.33: - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.33.tgz#cb539db9e41f06087b692f0f42491a73bc4bd013" - integrity sha512-seOm06exR36U0/EvR/CUNGuy99RAndoyWEdXg6S16rgEZ4G2Yj9iov/QdCtc4gwq9hFzVETFPlDW+Ge8xeHIzA== +xterm-addon-webgl@0.12.0-beta.34: + version "0.12.0-beta.34" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" + integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== -xterm-headless@4.19.0-beta.41: - version "4.19.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.41.tgz#f495ff173c7952aafa0c785acf15f20942c6fdc7" - integrity sha512-j09IFsM4tBSpjgY5OQSB1llojwEGyFFxgD36MYXZtopmB8p9+0l5GFq5hYfJojGfHCNaB/RwWAexGUxBK2ABRA== +xterm-headless@4.19.0-beta.43: + version "4.19.0-beta.43" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.43.tgz#12fe4abe624265240a7de8a922bfc4fd28c5f92a" + integrity sha512-4T8TlWy5u+sS23aPtd8gBHJ0BVljbNQRPMFHzLigDNOMCwc4uWa9JsxYmKteKifcG5aMm11ALPUTxWZCgpATww== -xterm@4.19.0-beta.41: - version "4.19.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.41.tgz#acb6009028898e9cfac41d4aa2865f81f6f56c5f" - integrity sha512-WY1NuxF/yUVN3l0TgzQGjrGM26eOu5g0Dbfam8GCkgdK5yrsgPF0xwM7UEj8sDjp5FbxEkSm//X86IIsgzqqFw== +xterm@4.19.0-beta.43: + version "4.19.0-beta.43" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" + integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 9390d7ccdd7..bdfbe2ad4d0 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -26,8 +26,7 @@ export const TERMINAL_CURSOR_BACKGROUND_COLOR = registerColor('terminalCursor.ba export const TERMINAL_SELECTION_BACKGROUND_COLOR = registerColor('terminal.selectionBackground', { light: editorSelectionBackground, dark: editorSelectionBackground, - // TODO: hcDark should be editorSelectionBackground (white) when min contrast ratio moves luminance the other way when needed https://github.com/xtermjs/xterm.js/issues/3720 - hcDark: '#FFFFFF80', + hcDark: editorSelectionBackground, hcLight: editorSelectionBackground }, nls.localize('terminal.selectionBackground', 'The selection background color of the terminal.')); export const TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.defaultBackground', { diff --git a/yarn.lock b/yarn.lock index 8b7a2c516e6..c4c6630ffe3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12209,20 +12209,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.33: - version "0.12.0-beta.33" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.33.tgz#cb539db9e41f06087b692f0f42491a73bc4bd013" - integrity sha512-seOm06exR36U0/EvR/CUNGuy99RAndoyWEdXg6S16rgEZ4G2Yj9iov/QdCtc4gwq9hFzVETFPlDW+Ge8xeHIzA== +xterm-addon-webgl@0.12.0-beta.34: + version "0.12.0-beta.34" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" + integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== -xterm-headless@4.19.0-beta.41: - version "4.19.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.41.tgz#f495ff173c7952aafa0c785acf15f20942c6fdc7" - integrity sha512-j09IFsM4tBSpjgY5OQSB1llojwEGyFFxgD36MYXZtopmB8p9+0l5GFq5hYfJojGfHCNaB/RwWAexGUxBK2ABRA== +xterm-headless@4.19.0-beta.43: + version "4.19.0-beta.43" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.43.tgz#12fe4abe624265240a7de8a922bfc4fd28c5f92a" + integrity sha512-4T8TlWy5u+sS23aPtd8gBHJ0BVljbNQRPMFHzLigDNOMCwc4uWa9JsxYmKteKifcG5aMm11ALPUTxWZCgpATww== -xterm@4.19.0-beta.41: - version "4.19.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.41.tgz#acb6009028898e9cfac41d4aa2865f81f6f56c5f" - integrity sha512-WY1NuxF/yUVN3l0TgzQGjrGM26eOu5g0Dbfam8GCkgdK5yrsgPF0xwM7UEj8sDjp5FbxEkSm//X86IIsgzqqFw== +xterm@4.19.0-beta.43: + version "4.19.0-beta.43" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" + integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== y18n@^3.2.1: version "3.2.2" From 027b7bf21e056d25e50365fa0fa87f93a47c1d7f Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 15 May 2022 18:24:06 -0700 Subject: [PATCH 047/350] Notebook state only. --- .../api/browser/mainThreadNotebookKernels.ts | 9 +++-- .../browser/mainThreadNotebookProxyKernels.ts | 16 +++++++++ .../browser/notebookExecutionServiceImpl.ts | 36 +++++++------------ .../notebook/common/notebookKernelService.ts | 4 +++ 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index fc5501b8162..35ffe7bb7ec 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -25,7 +25,8 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel { private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI; provides: string[] }[]; readonly onDidChange: Event = this._onDidChange.event; - + private readonly _onDispose = new Emitter(); + readonly onDispose = this._onDispose.event; readonly id: string; readonly viewType: string; readonly extension: ExtensionIdentifier; @@ -100,6 +101,10 @@ abstract class MainThreadKernel implements IResolvedNotebookKernel { this._onDidChange.fire(event); } + dispose() { + this._onDispose.fire(); + } + abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; } @@ -225,7 +230,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape }); const registration = this._notebookKernelService.registerKernel(kernel); - this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); + this._kernels.set(handle, [kernel, combinedDisposable(kernel, listener, registration)]); } $updateKernel(handle: number, data: Partial): void { diff --git a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts index e7a83bb8631..8d13aa86f72 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts @@ -16,6 +16,8 @@ abstract class MainThreadProxyKernel implements INotebookProxyKernel { readonly type: NotebookKernelType.Proxy = NotebookKernelType.Proxy; protected readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDispose = new Emitter(); + readonly onDispose = this._onDispose.event; readonly id: string; readonly viewType: string; readonly extension: ExtensionIdentifier; @@ -69,7 +71,13 @@ abstract class MainThreadProxyKernel implements INotebookProxyKernel { this._onDidChange.fire(event); } + dispose() { + this._onDispose.fire(); + } + abstract resolveKernel(): Promise; + abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; + abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; } @extHostNamedCustomer(MainContext.MainThreadNotebookProxyKernels) @@ -115,6 +123,8 @@ export class MainThreadNotebookProxyKernels implements MainThreadNotebookProxyKe return null; } } + async executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise { } + async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { } }(data); const registration = this._notebookKernelService.registerKernel(proxyKernel); @@ -135,4 +145,10 @@ export class MainThreadNotebookProxyKernels implements MainThreadNotebookProxyKe this._proxyKernels.delete(handle); } } + + async executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise { + } + + async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 2d4fe4cebe0..732b53f22e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -48,29 +48,6 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis return; } - if (kernel.type === NotebookKernelType.Proxy) { - this._activeProxyKernelExecutionToken?.dispose(true); - const tokenSource = this._activeProxyKernelExecutionToken = new CancellationTokenSource(); - const resolved = await kernel.resolveKernel(notebook.uri); - const kernels = this._notebookKernelService.getMatchingKernel(notebook); - const newlyMatchedKernel = kernels.all.find(k => k.id === resolved); - - if (!newlyMatchedKernel) { - return; - } - - kernel = newlyMatchedKernel; - if (tokenSource.token.isCancellationRequested) { - // execution was cancelled but we still need to update the active kernel - this._notebookKernelService.selectKernelForNotebook(kernel, notebook); - return; - } - } - - if (kernel.type === NotebookKernelType.Proxy) { - return; - } - const executeCells: NotebookCellTextModel[] = []; for (const cell of cellsArr) { const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri); @@ -83,11 +60,24 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis executeCells.push(cell); } + if (kernel.state !== undefined) { + // kernel has connection state management + const kernelId = kernel.id; + kernel.onDispose(() => { + // proxy kernel scenario, kernel disposed, we should now make way for new preferred kernel + const exes = executeCells.map(c => this._notebookExecutionStateService.createCellExecution(kernelId, notebook.uri, c.handle)); + exes.forEach(e => e.complete({})); + + this.executeNotebookCells(notebook, executeCells); + }); + } + if (executeCells.length > 0) { this._notebookKernelService.selectKernelForNotebook(kernel, notebook); const exes = executeCells.map(c => this._notebookExecutionStateService.createCellExecution(kernel!.id, notebook.uri, c.handle)); await kernel.executeNotebookCellsRequest(notebook.uri, executeCells.map(c => c.handle)); + // the connecting state can change before the kernel resolves executeNotebookCellsRequest const unconfirmed = exes.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed); if (unconfirmed.length) { this._logService.debug(`NotebookExecutionService#executeNotebookCells completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`); diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 7a23d36ecd5..081eab0180e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -64,6 +64,7 @@ export interface IResolvedNotebookKernel { executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; + onDispose: Event; } export const enum ProxyKernelState { @@ -96,6 +97,9 @@ export interface INotebookProxyKernel { implementsExecutionOrder?: boolean; connectionState: ProxyKernelState; resolveKernel(uri: URI): Promise; + executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; + cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; + onDispose: Event; } export type INotebookKernel = IResolvedNotebookKernel | INotebookProxyKernel; From bd99c998a14e34dc17a3a745bb60f00a13391564 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 15 May 2022 18:56:22 -0700 Subject: [PATCH 048/350] Remove proxy controller. --- .../api/browser/mainThreadNotebookKernels.ts | 6 +- .../browser/mainThreadNotebookProxyKernels.ts | 154 ----------------- .../workbench/api/common/extHost.api.impl.ts | 6 - .../api/common/extHostNotebookProxyKernels.ts | 157 ------------------ .../browser/notebookExecutionServiceImpl.ts | 8 +- .../notebook/common/notebookKernelService.ts | 35 +--- .../browser/notebookExecutionService.test.ts | 11 +- .../notebookExecutionStateService.test.ts | 6 +- .../browser/notebookKernelService.test.ts | 7 +- ...code.proposed.notebookProxyController.d.ts | 48 ------ 10 files changed, 19 insertions(+), 419 deletions(-) delete mode 100644 src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts delete mode 100644 src/vs/workbench/api/common/extHostNotebookProxyKernels.ts diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 35ffe7bb7ec..38c81e4ba0e 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -15,13 +15,11 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IResolvedNotebookKernel, INotebookKernelChangeEvent, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookControllerState } from '../common/extHost.protocol'; -abstract class MainThreadKernel implements IResolvedNotebookKernel { - readonly type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; - +abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI; provides: string[] }[]; readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts deleted file mode 100644 index 8d13aa86f72..00000000000 --- a/src/vs/workbench/api/browser/mainThreadNotebookProxyKernels.ts +++ /dev/null @@ -1,154 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { INotebookKernelService, INotebookProxyKernel, INotebookProxyKernelChangeEvent, ProxyKernelState, NotebookKernelType, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { ExtHostContext, ExtHostNotebookProxyKernelsShape, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from '../common/extHost.protocol'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { URI } from 'vs/base/common/uri'; - -abstract class MainThreadProxyKernel implements INotebookProxyKernel { - readonly type: NotebookKernelType.Proxy = NotebookKernelType.Proxy; - protected readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - private readonly _onDispose = new Emitter(); - readonly onDispose = this._onDispose.event; - readonly id: string; - readonly viewType: string; - readonly extension: ExtensionIdentifier; - readonly preloadProvides: string[] = []; - readonly preloadUris: URI[] = []; - label: string; - description?: string; - detail?: string; - kind?: string; - supportedLanguages: string[] = []; - localResourceRoot: URI; - state?: NotebookControllerState | undefined; - implementsInterrupt?: boolean | undefined; - implementsExecutionOrder?: boolean | undefined; - connectionState: ProxyKernelState; - - constructor(data: INotebookProxyKernelDto) { - this.id = data.id; - this.viewType = data.notebookType; - this.extension = data.extensionId; - - this.label = data.label; - this.description = data.description; - this.detail = data.detail; - this.kind = data.kind; - this.localResourceRoot = URI.revive(data.extensionLocation); - - this.connectionState = ProxyKernelState.Disconnected; - } - - - update(data: Partial) { - const event: INotebookProxyKernelChangeEvent = Object.create(null); - if (data.label !== undefined) { - this.label = data.label; - event.label = true; - } - if (data.description !== undefined) { - this.description = data.description; - event.description = true; - } - if (data.detail !== undefined) { - this.detail = data.detail; - event.detail = true; - } - if (data.kind !== undefined) { - this.kind = data.kind; - event.kind = true; - } - - this._onDidChange.fire(event); - } - - dispose() { - this._onDispose.fire(); - } - - abstract resolveKernel(): Promise; - abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; - abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; -} - -@extHostNamedCustomer(MainContext.MainThreadNotebookProxyKernels) -export class MainThreadNotebookProxyKernels implements MainThreadNotebookProxyKernelsShape { - - private readonly _disposables = new DisposableStore(); - - private readonly _proxyKernels = new Map(); - private readonly _proxyKernelProxy: ExtHostNotebookProxyKernelsShape; - - constructor( - extHostContext: IExtHostContext, - @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, - ) { - this._proxyKernelProxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookProxyKernels); - } - - dispose(): void { - this._disposables.dispose(); - - for (let [, registration] of this._proxyKernels.values()) { - registration.dispose(); - } - } - - // -- Proxy kernel - - async $addProxyKernel(handle: number, data: INotebookProxyKernelDto): Promise { - const that = this; - const proxyKernel = new class extends MainThreadProxyKernel { - async resolveKernel(): Promise { - try { - this.connectionState = ProxyKernelState.Initializing; - this._onDidChange.fire({ connectionState: true }); - const delegateKernel = await that._proxyKernelProxy.$resolveKernel(handle); - this.connectionState = ProxyKernelState.Connected; - this._onDidChange.fire({ connectionState: true }); - return delegateKernel; - } catch (err) { - onUnexpectedError(err); - this.connectionState = ProxyKernelState.Disconnected; - this._onDidChange.fire({ connectionState: true }); - return null; - } - } - async executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise { } - async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { } - }(data); - - const registration = this._notebookKernelService.registerKernel(proxyKernel); - this._proxyKernels.set(handle, [proxyKernel, registration]); - } - - $updateProxyKernel(handle: number, data: Partial): void { - const tuple = this._proxyKernels.get(handle); - if (tuple) { - tuple[0].update(data); - } - } - - $removeProxyKernel(handle: number): void { - const tuple = this._proxyKernels.get(handle); - if (tuple) { - tuple[1].dispose(); - this._proxyKernels.delete(handle); - } - } - - async executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise { - } - - async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise { - } -} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 86cb018e404..43e08b3776b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -93,7 +93,6 @@ import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; -import { ExtHostNotebookProxyKernels } from 'vs/workbench/api/common/extHostNotebookProxyKernels'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -161,7 +160,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostNotebook)); const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, rpcProtocol, extHostNotebook)); const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostCommands, extHostLogService)); - const extHostNotebookProxyKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookProxyKernels, new ExtHostNotebookProxyKernels(rpcProtocol, extHostNotebookKernels, extHostLogService)); const extHostNotebookRenderers = rpcProtocol.set(ExtHostContext.ExtHostNotebookRenderers, new ExtHostNotebookRenderers(rpcProtocol, extHostNotebook)); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); @@ -1153,10 +1151,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeNotebookCellExecutionState(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension, 'notebookCellExecutionState'); return extHostNotebookKernels.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables); - }, - createNotebookProxyController(id: string, notebookType: string, label: string, handler: () => vscode.NotebookController | string | Thenable) { - checkProposedApiEnabled(extension, 'notebookProxyController'); - return extHostNotebookProxyKernels.createNotebookProxyController(extension, id, notebookType, label, handler); } }; diff --git a/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts b/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts deleted file mode 100644 index 786101bf360..00000000000 --- a/src/vs/workbench/api/common/extHostNotebookProxyKernels.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Emitter } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostNotebookProxyKernelsShape, IMainContext, INotebookProxyKernelDto, MainContext, MainThreadNotebookProxyKernelsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { createKernelId, ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import * as vscode from 'vscode'; - -interface IProxyKernelData { - extensionId: ExtensionIdentifier; - controller: vscode.NotebookProxyController; - onDidChangeSelection: Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>; - associatedNotebooks: ResourceMap; -} - -export type SelectKernelReturnArgs = ControllerInfo | { notebookEditorId: string } | ControllerInfo & { notebookEditorId: string } | undefined; -type ControllerInfo = { id: string; extension: string }; - - -export class ExtHostNotebookProxyKernels implements ExtHostNotebookProxyKernelsShape { - - private readonly _proxy: MainThreadNotebookProxyKernelsShape; - - private readonly _proxyKernelData: Map = new Map(); - private _handlePool: number = 0; - - private readonly _onDidChangeCellExecutionState = new Emitter(); - readonly onDidChangeNotebookCellExecutionState = this._onDidChangeCellExecutionState.event; - - constructor( - mainContext: IMainContext, - private readonly extHostNotebook: ExtHostNotebookKernels, - @ILogService private readonly _logService: ILogService - ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadNotebookProxyKernels); - } - - createNotebookProxyController(extension: IExtensionDescription, id: string, viewType: string, label: string, handler: () => vscode.NotebookController | string | Thenable): vscode.NotebookProxyController { - const handle = this._handlePool++; - - let isDisposed = false; - const commandDisposables = new DisposableStore(); - const onDidChangeSelection = new Emitter<{ selected: boolean; notebook: vscode.NotebookDocument }>(); - - const data: INotebookProxyKernelDto = { - id: createKernelId(extension.identifier, id), - notebookType: viewType, - extensionId: extension.identifier, - extensionLocation: extension.extensionLocation, - label: label || extension.identifier.value, - }; - - let _resolveHandler = handler; - - this._proxy.$addProxyKernel(handle, data).catch(err => { - // this can happen when a kernel with that ID is already registered - console.log(err); - isDisposed = true; - }); - - let tokenPool = 0; - const _update = () => { - if (isDisposed) { - return; - } - const myToken = ++tokenPool; - Promise.resolve().then(() => { - if (myToken === tokenPool) { - this._proxy.$updateProxyKernel(handle, data); - } - }); - }; - - // notebook documents that are associated to this controller - const associatedNotebooks = new ResourceMap(); - - const controller: vscode.NotebookProxyController = { - get id() { return id; }, - get notebookType() { return data.notebookType; }, - onDidChangeSelectedNotebooks: onDidChangeSelection.event, - get label() { - return data.label; - }, - set label(value) { - data.label = value ?? extension.displayName ?? extension.name; - _update(); - }, - get detail() { - return data.detail ?? ''; - }, - set detail(value) { - data.detail = value; - _update(); - }, - get description() { - return data.description ?? ''; - }, - set description(value) { - data.description = value; - _update(); - }, - get kind() { - checkProposedApiEnabled(extension, 'notebookControllerKind'); - return data.kind ?? ''; - }, - set kind(value) { - checkProposedApiEnabled(extension, 'notebookControllerKind'); - data.kind = value; - _update(); - }, - get resolveHandler() { - return _resolveHandler; - }, - dispose: () => { - if (!isDisposed) { - this._logService.trace(`NotebookProxyController[${handle}], DISPOSED`); - isDisposed = true; - this._proxyKernelData.delete(handle); - commandDisposables.dispose(); - onDidChangeSelection.dispose(); - this._proxy.$removeProxyKernel(handle); - } - } - }; - - this._proxyKernelData.set(handle, { - extensionId: extension.identifier, - controller, - onDidChangeSelection, - associatedNotebooks - }); - return controller; - } - - async $resolveKernel(handle: number): Promise { - const obj = this._proxyKernelData.get(handle); - if (!obj) { - // extension can dispose kernels in the meantime - return null; - } - - const controller = await obj.controller.resolveHandler(); - if (typeof controller === 'string') { - return controller; - } else { - return this.extHostNotebook.getIdByController(controller); - } - } -} - diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 732b53f22e4..c92696d34eb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -14,7 +14,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class NotebookExecutionService implements INotebookExecutionService, IDisposable { declare _serviceBrand: undefined; @@ -91,11 +91,7 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (kernel) { - if (kernel.type === NotebookKernelType.Proxy) { - this._activeProxyKernelExecutionToken?.dispose(true); - } else { - await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); - } + await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 081eab0180e..13fd479ef1a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -32,18 +32,12 @@ export interface INotebookKernelChangeEvent { hasExecutionOrder?: true; } -export const enum NotebookKernelType { - Resolved, - Proxy = 1 -} - export enum NotebookControllerState { Idle = 1, Connecting = 2 } -export interface IResolvedNotebookKernel { - readonly type: NotebookKernelType.Resolved; +export interface INotebookKernel { readonly id: string; readonly viewType: string; readonly onDidChange: Event>; @@ -77,33 +71,6 @@ export interface INotebookProxyKernelChangeEvent extends INotebookKernelChangeEv connectionState?: true; } -export interface INotebookProxyKernel { - readonly type: NotebookKernelType.Proxy; - readonly id: string; - readonly viewType: string; - readonly extension: ExtensionIdentifier; - readonly localResourceRoot: URI; - readonly preloadUris: URI[]; - readonly preloadProvides: string[]; - - readonly onDidChange: Event>; - label: string; - description?: string; - detail?: string; - kind?: string; - state?: NotebookControllerState; - supportedLanguages: string[]; - implementsInterrupt?: boolean; - implementsExecutionOrder?: boolean; - connectionState: ProxyKernelState; - resolveKernel(uri: URI): Promise; - executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; - cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - onDispose: Event; -} - -export type INotebookKernel = IResolvedNotebookKernel | INotebookProxyKernel; - export interface INotebookTextModelLike { uri: URI; viewType: string } export const INotebookKernelService = createDecorator('INotebookKernelService'); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 8d3bff7a83a..9ceec332a7a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -20,7 +20,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, IResolvedNotebookKernel, ISelectedNotebooksChangeEvent, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -166,8 +166,7 @@ suite('NotebookExecutionService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = 'test'; label: string = ''; viewType = '*'; @@ -179,14 +178,18 @@ class TestNotebookKernel implements IResolvedNotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; + onDispose = Event.None; executeNotebookCellsRequest(): Promise { throw new Error('Method not implemented.'); } cancelNotebookCellExecution(): Promise { throw new Error('Method not implemented.'); } - constructor(opts?: { languages: string[] }) { this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID]; } + kind?: string | undefined; + state?: NotebookControllerState | undefined; + implementsInterrupt?: boolean | undefined; + implementsExecutionOrder?: boolean | undefined; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index e5b7f84b8bf..4763ee855c6 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -21,7 +21,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -170,8 +170,7 @@ suite('NotebookExecutionStateService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = 'test'; label: string = ''; viewType = '*'; @@ -183,6 +182,7 @@ class TestNotebookKernel implements IResolvedNotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; + onDispose = Event.None; async executeNotebookCellsRequest(): Promise { } async cancelNotebookCellExecution(): Promise { } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index 62d6afecb8b..54b3b52cb3c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { mock } from 'vs/base/test/common/mock'; @@ -159,8 +159,7 @@ suite('NotebookKernelService', () => { }); }); -class TestNotebookKernel implements IResolvedNotebookKernel { - type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; +class TestNotebookKernel implements INotebookKernel { id: string = Math.random() + 'kernel'; label: string = 'test-label'; viewType = '*'; @@ -172,6 +171,8 @@ class TestNotebookKernel implements IResolvedNotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; + state?: NotebookControllerState | undefined = undefined; + onDispose = Event.None; executeNotebookCellsRequest(): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts index 3fe7cac5428..df429123049 100644 --- a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts @@ -13,52 +13,4 @@ declare module 'vscode' { export interface NotebookController { state?: NotebookControllerState; } - - export interface NotebookProxyController { - /** - * The identifier of this notebook controller. - * - * _Note_ that controllers are remembered by their identifier and that extensions should use - * stable identifiers across sessions. - */ - readonly id: string; - - /** - * The notebook type this controller is for. - */ - readonly notebookType: string; - - /** - * The human-readable label of this notebook controller. - */ - label: string; - - /** - * The human-readable description which is rendered less prominent. - */ - description?: string; - - /** - * The human-readable detail which is rendered less prominent. - */ - detail?: string; - - /** - * The human-readable label used to categorise controllers. - */ - kind?: string; - - resolveHandler: () => NotebookController | string | Thenable; - - readonly onDidChangeSelectedNotebooks: Event<{ readonly notebook: NotebookDocument; readonly selected: boolean }>; - - /** - * Dispose and free associated resources. - */ - dispose(): void; - } - - export namespace notebooks { - export function createNotebookProxyController(id: string, notebookType: string, label: string, resolveHandler: () => NotebookController | string | Thenable): NotebookProxyController; - } } From f790b3ba1f03b7071b77000213f386c5e92205b4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 16 May 2022 14:15:23 +0200 Subject: [PATCH 049/350] Implements gutter buttons. --- .../mergeEditor/browser/editorGutterWidget.ts | 100 ++++ .../mergeEditor/browser/media/mergeEditor.css | 49 +- .../mergeEditor/browser/mergeEditor.ts | 234 +++++----- .../mergeEditor/browser/mergeEditorModel.ts | 158 ++++--- .../contrib/mergeEditor/browser/model.ts | 434 ++++++++++-------- 5 files changed, 596 insertions(+), 379 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts diff --git a/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts b/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts new file mode 100644 index 00000000000..9b3954eed48 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; + +export class EditorGutterWidget { + constructor( + private readonly _editor: ICodeEditor, + private readonly _domNode: HTMLElement, + private readonly itemProvider: IGutterItemProvider, + ) { + this._domNode.className = 'gutter'; + + this._editor.onDidScrollChange(() => { + this.render(); + }); + } + + private readonly views = new Map(); + + private render(): void { + const visibleRange = this._editor.getVisibleRanges()[0]; + const visibleRange2 = new LineRange( + visibleRange.startLineNumber, + visibleRange.endLineNumber - visibleRange.startLineNumber + ); + + const gutterItems = this.itemProvider.getIntersectingGutterItems(visibleRange2); + + const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); + + const unusedIds = new Set(this.views.keys()); + for (const gutterItem of gutterItems) { + if (!gutterItem.range.touches(visibleRange2)) { + continue; + } + + unusedIds.delete(gutterItem.id); + let view = this.views.get(gutterItem.id); + if (!view) { + const viewDomNode = document.createElement('div'); + viewDomNode.className = 'gutter-item'; + this._domNode.appendChild(viewDomNode); + const itemView = this.itemProvider.createView(gutterItem, viewDomNode); + view = new ManagedGutterItemView(itemView, viewDomNode); + this.views.set(gutterItem.id, view); + } + + const scrollTop = this._editor.getScrollTop(); + const top = this._editor.getTopForLineNumber(gutterItem.range.startLineNumber) - scrollTop; + const height = lineHeight * gutterItem.range.lineCount; + + view.domNode.style.top = `${top}px`; + view.domNode.style.height = `${height}px`; + + view.gutterItemView.layout(top, height, 0, -1); + } + + for (const id of unusedIds) { + const view = this.views.get(id)!; + view.gutterItemView.dispose(); + this._domNode.removeChild(view.domNode); + this.views.delete(id); + } + } +} + +class ManagedGutterItemView { + constructor( + public readonly gutterItemView: IGutterItemView, + public readonly domNode: HTMLDivElement + ) { } +} + +export interface IGutterItemProvider { + // onDidChange + getIntersectingGutterItems(range: LineRange): TItem[]; + + createView(item: TItem, target: HTMLElement): IGutterItemView; +} + +export interface IGutterItemInfo { + id: string; + range: LineRange; + + // To accommodate view zones: + offsetInPx: number; + additionalHeightInPx: number; +} + +export interface IGutterItemView extends IDisposable { + update(item: T): void; + layout(top: number, height: number, viewTop: number, viewHeight: number): void; +} + diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index 749b947af55..2e3929e236c 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -20,10 +20,12 @@ } .monaco-workbench .merge-editor .code-view > .container > .gutter { - min-width: 20px; - /* background-color: yellow; */ - margin-right: 3px; + width: 24px; position: relative; + overflow: hidden; + + flex-shrink: 0; + flex-grow: 0; } .monaco-editor .merge-accept-conflict-glyph { @@ -34,9 +36,42 @@ background-color: rgb(170 175 170 / 15%); } -.merge-accept-gutter-marker { - min-height: 30px; - min-width: 20px; - background-color: yellow; +.gutter-item { position: absolute; } + +.merge-accept-gutter-marker { + width: 28px; + /* background-color: yellow; */ + + + margin-left: 4px; +} + +.merge-accept-gutter-marker .background { + height: 100%; + width: 50%; + position: absolute; +} + +.merge-accept-gutter-marker.multi-line .background { + border: 2px solid #323232FF; + border-right: 0; + left: 8px; + width: 10px; +} + +.merge-accept-gutter-marker .checkbox { + height: 100%; + width: 100%; + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; +} + +.merge-accept-gutter-marker .checkbox .accept-conflict-group.monaco-custom-toggle.monaco-checkbox { + margin: 0; + padding: 0; + background-color: #3C3C3CFF; +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index c846f45c87f..b3f0ffcbbab 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -16,7 +16,7 @@ import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { BugIndicatingError } from 'vs/base/common/errors'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Direction, Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; @@ -30,15 +30,17 @@ import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IAction } from 'vs/base/common/actions'; import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { acceptConflictIcon } from 'vs/workbench/contrib/mergeEditor/browser/icons'; -import { ConflictGroup, MergeState } from 'vs/workbench/contrib/mergeEditor/browser/model'; - +import { Checkbox, Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { EditorGutterWidget, IGutterItemInfo, IGutterItemView } from './editorGutterWidget'; +import { noBreakWhitespace } from 'vs/base/common/strings'; +import { ModifiedBaseRange, ModifiedBaseRangeState, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { autorun, derivedObservable, derivedWritebaleObservable, IObservable, ITransaction } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { Codicon } from 'vs/base/common/codicons'; export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); @@ -172,77 +174,13 @@ export class MergeEditor extends EditorPane { this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), undefined); this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); - - const input1LineNumberToMergeableDiffMap = new Map(); - - this.input1View.editor.onMouseDown(e => { - if (e.target.element && e.target.element.className.includes('merge-')) { - const lineNumber = e.target.position?.lineNumber; - if (lineNumber) { - const diff = input1LineNumberToMergeableDiffMap.get(lineNumber); - if (diff) { - const state = (model.getState(diff) || new MergeState(false, false, false)).toggleInput1(); - model.setConflictResolutionStatus(diff, state); - } - } - } - }); - - this.input1View.editor.changeDecorations(c => { - for (const mergeableDiff of model.mergeableDiffs) { - if (mergeableDiff.input1FullDiff) { - const lineNumber = mergeableDiff.input1FullDiff.modifiedRange.startLineNumber; - input1LineNumberToMergeableDiffMap.set(lineNumber, mergeableDiff); - c.addDecoration(Range.fromPositions(new Position(lineNumber, 0)), { - description: 'foo', - glyphMarginClassName: ThemeIcon.asClassName(acceptConflictIcon) + ' merge-accept-conflict-glyph', - }); - } - } - }); - - - - const input2LineNumberToMergeableDiffMap = new Map(); - - this.input2View.editor.onMouseDown(e => { - if (e.target.element && e.target.element.className.includes('merge-')) { - const lineNumber = e.target.position?.lineNumber; - if (lineNumber) { - const diff = input2LineNumberToMergeableDiffMap.get(lineNumber); - if (diff) { - const state = (model.getState(diff) || new MergeState(false, false, false)).toggleInput2(); - model.setConflictResolutionStatus(diff, state); - } - } - } - }); - - - - this.input2View.editor.changeDecorations(c => { - for (const mergeableDiff of model.mergeableDiffs) { - if (mergeableDiff.input2FullDiff) { - const lineNumber = mergeableDiff.input2FullDiff.modifiedRange.startLineNumber; - input2LineNumberToMergeableDiffMap.set(lineNumber, mergeableDiff); - c.addDecoration(Range.fromPositions(new Position(lineNumber, 1)), { - description: 'foo', - glyphMarginClassName: ThemeIcon.asClassName(acceptConflictIcon) + ' merge-accept-conflict-glyph', - }); - } - } - }); - - let input1Decorations = new Array(); let input2Decorations = new Array(); - - for (const m of model.mergeableDiffs) { - - if (!m.totalInput1Range.isEmpty) { + for (const m of model.modifiedBaseRanges) { + if (!m.input1Range.isEmpty) { input1Decorations.push({ - range: new Range(m.totalInput1Range.startLineNumber, 1, m.totalInput1Range.endLineNumberExclusive - 1, 1), + range: new Range(m.input1Range.startLineNumber, 1, m.input1Range.endLineNumberExclusive - 1, 1), options: { isWholeLine: true, className: 'merge-accept-foo', @@ -251,9 +189,9 @@ export class MergeEditor extends EditorPane { }); } - if (!m.totalInput2Range.isEmpty) { + if (!m.input2Range.isEmpty) { input2Decorations.push({ - range: new Range(m.totalInput2Range.startLineNumber, 1, m.totalInput2Range.endLineNumberExclusive - 1, 1), + range: new Range(m.input2Range.startLineNumber, 1, m.input2Range.endLineNumberExclusive - 1, 1), options: { isWholeLine: true, className: 'merge-accept-foo', @@ -262,20 +200,20 @@ export class MergeEditor extends EditorPane { }); } - const max = Math.max(m.totalInput1Range.lineCount, m.totalInput2Range.lineCount, 1); + const max = Math.max(m.input1Range.lineCount, m.input2Range.lineCount, 1); this.input1View.editor.changeViewZones(a => { a.addZone({ - afterLineNumber: m.totalInput1Range.endLineNumberExclusive - 1, - heightInLines: max - m.totalInput1Range.lineCount, + afterLineNumber: m.input1Range.endLineNumberExclusive - 1, + heightInLines: max - m.input1Range.lineCount, domNode: $('div.diagonal-fill'), }); }); this.input2View.editor.changeViewZones(a => { a.addZone({ - afterLineNumber: m.totalInput2Range.endLineNumberExclusive - 1, - heightInLines: max - m.totalInput2Range.lineCount, + afterLineNumber: m.input2Range.endLineNumberExclusive - 1, + heightInLines: max - m.input2Range.lineCount, domNode: $('div.diagonal-fill'), }); }); @@ -284,6 +222,61 @@ export class MergeEditor extends EditorPane { this.input1View.editor.deltaDecorations([], input1Decorations); this.input2View.editor.deltaDecorations([], input2Decorations); + new EditorGutterWidget(this.input1View.editor, this.input1View._gutterDiv, { + getIntersectingGutterItems: (range) => + model.modifiedBaseRanges + .filter((r) => r.input1Diffs.length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + additionalHeightInPx: 0, + offsetInPx: 0, + range: baseRange.input1Range, + toggleState: derivedObservable( + 'toggle', + (reader) => model.getState(baseRange).read(reader)?.input1 + ), + setState(value, tx) { + model.setState( + baseRange, + ( + model.getState(baseRange).get() || + new ModifiedBaseRangeState(false, false, false) + ).withInput1(value), + tx + ); + }, + })), + createView: (item, target) => new ButtonView(item, target), + }); + + new EditorGutterWidget(this.input2View.editor, this.input2View._gutterDiv, { + getIntersectingGutterItems: (range) => + model.modifiedBaseRanges + .filter((r) => r.input2Diffs.length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + additionalHeightInPx: 0, + offsetInPx: 0, + range: baseRange.input2Range, + baseRange, + toggleState: derivedObservable( + 'toggle', + (reader) => model.getState(baseRange).read(reader)?.input2 + ), + setState(value, tx) { + model.setState( + baseRange, + ( + model.getState(baseRange).get() || + new ModifiedBaseRangeState(false, false, false) + ).withInput2(value), + tx + ); + }, + })), + createView: (item, target) => new ButtonView(item, target), + }); + } protected override setEditorVisible(visible: boolean): void { @@ -324,6 +317,43 @@ export class MergeEditor extends EditorPane { } } +interface ButtonViewData extends IGutterItemInfo { + toggleState: IObservable; + setState(value: boolean, tx: ITransaction | undefined): void; +} + +class ButtonView extends Disposable implements IGutterItemView { + constructor(item: ButtonViewData, target: HTMLElement) { + super(); + + target.classList.add('merge-accept-gutter-marker'); + target.classList.add(item.range.lineCount > 1 ? 'multi-line' : 'single-line'); + + const checkBox = new Toggle({ isChecked: false, title: 'TODO', icon: Codicon.check, actionClassName: 'monaco-checkbox' }); + checkBox.domNode.classList.add('accept-conflict-group'); + + this._register( + autorun((reader) => { + const value = item.toggleState.read(reader); + checkBox.checked = value === true; + }, 'Update Toggle State') + ); + + this._register(checkBox.onChange(() => { + item.setState(checkBox.checked, undefined); + })); + + target.appendChild($('div.background', {}, noBreakWhitespace)); + target.appendChild($('div.checkbox', {}, checkBox.domNode)); + } + layout(top: number, height: number, viewTop: number, viewHeight: number): void { + + } + + update(baseRange: ButtonViewData): void { + } +} + interface ICodeEditorViewOptions { readonly: boolean; } @@ -336,7 +366,7 @@ class CodeEditorView implements IView { element: HTMLElement; private _titleElement: HTMLElement; private _editorElement: HTMLElement; - private _gutterDiv: HTMLElement; + public _gutterDiv: HTMLElement; minimumWidth: number = 10; maximumWidth: number = Number.MAX_SAFE_INTEGER; @@ -351,7 +381,7 @@ class CodeEditorView implements IView { private _title: IconLabel; public readonly editor: CodeEditorWidget; - private readonly gutter: EditorGutter; + // private readonly gutter: EditorGutterWidget; constructor( @@ -371,13 +401,11 @@ class CodeEditorView implements IView { this.editor = this.instantiationService.createInstance( CodeEditorWidget, this._editorElement, - { minimap: { enabled: false }, readOnly: this._options.readonly, /*glyphMargin: false,*/ lineNumbersMinChars: 2 }, + { minimap: { enabled: false }, readOnly: this._options.readonly, glyphMargin: false, lineNumbersMinChars: 2 }, { contributions: [] } ); this._title = new IconLabel(this._titleElement, { supportIcons: true }); - this.gutter = new EditorGutter(this.editor, this._gutterDiv); - console.log(this.gutter); // keep alive } public setModel(model: ITextModel, title: string, description: string | undefined): void { @@ -393,39 +421,3 @@ class CodeEditorView implements IView { this.editor.layout({ width: width - this._gutterDiv.clientWidth, height: height - this._titleElement.clientHeight }); } } - -class ReentrancyBarrier { - private isActive = false; - - public runExclusively(fn: () => void): void { - if (this.isActive) { - return; - } - this.isActive = true; - try { - fn(); - } finally { - this.isActive = false; - } - } -} - -class EditorGutter { - constructor( - private readonly _editor: ICodeEditor, - private readonly _domNode: HTMLElement, - ) { - this._editor.onDidScrollChange(() => { - this.render(); - }); - } - - private node = $('div.merge-accept-gutter-marker'); - - private render(): void { - this._domNode.append(this.node); - - const top = this._editor.getTopForLineNumber(10) - this._editor.getScrollTop(); - this.node.style.top = `${top}px`; - } -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 081869931c3..989febccdb7 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -3,62 +3,64 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter } from 'vs/base/common/event'; import { compareBy, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { ConflictGroup, LineEdit, LineEdits, LineDiff, MergeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { ModifiedBaseRange, LineEdit, LineEdits, LineDiff, ModifiedBaseRangeState, LineRange, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; export class MergeEditorModelFactory { constructor( - @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - //@IInstantiationService private readonly instantiationService: IInstantiationService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService ) { } public async create( - ancestor: ITextModel, + base: ITextModel, input1: ITextModel, input2: ITextModel, result: ITextModel, ): Promise { - const ancestorToInput1DiffPromise = this._editorWorkerService.computeDiff( - ancestor.uri, + const baseToInput1DiffPromise = this._editorWorkerService.computeDiff( + base.uri, input1.uri, false, 1000 ); - const ancestorToInput2DiffPromise = this._editorWorkerService.computeDiff( - ancestor.uri, + const baseToInput2DiffPromise = this._editorWorkerService.computeDiff( + base.uri, input2.uri, false, 1000 ); - const [ancestorToInput1Diff, ancestorToInput2Diff] = await Promise.all([ - ancestorToInput1DiffPromise, - ancestorToInput2DiffPromise, + const [baseToInput1Diff, baseToInput2Diff] = await Promise.all([ + baseToInput1DiffPromise, + baseToInput2DiffPromise, ]); const changesInput1 = - ancestorToInput1Diff?.changes.map((c) => - LineDiff.fromLineChange(c, ancestor, input1) + baseToInput1Diff?.changes.map((c) => + LineDiff.fromLineChange(c, base, input1) ) || []; const changesInput2 = - ancestorToInput2Diff?.changes.map((c) => - LineDiff.fromLineChange(c, ancestor, input2) + baseToInput2Diff?.changes.map((c) => + LineDiff.fromLineChange(c, base, input2) ) || []; return new MergeEditorModel( InternalSymbol, - ancestor, + base, input1, input2, result, changesInput1, changesInput2, + this._editorWorkerService ); } } @@ -66,20 +68,29 @@ export class MergeEditorModelFactory { const InternalSymbol = Symbol(); export class MergeEditorModel extends EditorModel { - private resultEdits = new ResultEdits([], this.ancestor, this.result); + private resultEdits = new ResultEdits([], this.base, this.result, this.editorWorkerService); constructor( symbol: typeof InternalSymbol, - readonly ancestor: ITextModel, + readonly base: ITextModel, readonly input1: ITextModel, readonly input2: ITextModel, readonly result: ITextModel, private readonly inputOneLinesDiffs: readonly LineDiff[], - private readonly inputTwoLinesDiffs: readonly LineDiff[] + private readonly inputTwoLinesDiffs: readonly LineDiff[], + private readonly editorWorkerService: IEditorWorkerService ) { super(); - result.setValue(ancestor.getValue()); + result.setValue(base.getValue()); + + this.resultEdits.onDidChange(() => { + transaction(tx => { + for (const [key, value] of this.modifiedBaseRangeStateStores) { + value.set(this.computeState(key), tx); + } + }); + }); /* // Apply all non-conflicts diffs @@ -96,60 +107,75 @@ export class MergeEditorModel extends EditorModel { } new LineEdits(lineEditsArr).apply(result); */ - - console.log(this, 'hello'); - (globalThis as any)['mergeEditorModel'] = this; } public get resultDiffs(): readonly LineDiff[] { return this.resultEdits.diffs; } - public readonly mergeableDiffs = ConflictGroup.partitionDiffs( - this.ancestor, + public readonly modifiedBaseRanges = ModifiedBaseRange.fromDiffs( + this.base, this.input1, this.inputOneLinesDiffs, this.input2, this.inputTwoLinesDiffs ); - public getState(conflict: ConflictGroup): MergeState | undefined { + private readonly modifiedBaseRangeStateStores = new Map>( + this.modifiedBaseRanges.map(s => ([s, new ObservableValue(new ModifiedBaseRangeState(false, false, false), 'State')])) + ); + + private computeState(baseRange: ModifiedBaseRange): ModifiedBaseRangeState | undefined { const existingDiff = this.resultEdits.findConflictingDiffs( - conflict.totalOriginalRange + baseRange.baseRange ); if (!existingDiff) { - return new MergeState(false, false, false); + return new ModifiedBaseRangeState(false, false, false); } - const input1Edit = conflict.getInput1LineEdit(); + const input1Edit = baseRange.getInput1LineEdit(); if (input1Edit && existingDiff.getLineEdit().equals(input1Edit)) { - return new MergeState(true, false, false); + return new ModifiedBaseRangeState(true, false, false); } - const input2Edit = conflict.getInput2LineEdit(); + const input2Edit = baseRange.getInput2LineEdit(); if (input2Edit && existingDiff.getLineEdit().equals(input2Edit)) { - return new MergeState(false, true, false); + return new ModifiedBaseRangeState(false, true, false); } return undefined; } - // Undo all edits of result that conflict with the conflict!! - public setConflictResolutionStatus( - conflict: ConflictGroup, - status: MergeState + public getState(baseRange: ModifiedBaseRange): IObservable { + const existingState = this.modifiedBaseRangeStateStores.get(baseRange); + if (!existingState) { + throw new BugIndicatingError('object must be from this instance'); + } + return existingState; + } + + public setState( + baseRange: ModifiedBaseRange, + state: ModifiedBaseRangeState, + transaction: ITransaction | undefined ): void { + const existingState = this.modifiedBaseRangeStateStores.get(baseRange); + if (!existingState) { + throw new BugIndicatingError('object must be from this instance'); + } + existingState.set(state, transaction); + const existingDiff = this.resultEdits.findConflictingDiffs( - conflict.totalOriginalRange + baseRange.baseRange ); if (existingDiff) { this.resultEdits.removeDiff(existingDiff); } - const edit = status.input1 - ? conflict.getInput1LineEdit() - : status.input2 - ? conflict.getInput2LineEdit() + const edit = state.input1 + ? baseRange.getInput1LineEdit() + : state.input2 + ? baseRange.getInput2LineEdit() : undefined; if (edit) { this.resultEdits.applyEditRelativeToOriginal(edit); @@ -158,12 +184,36 @@ export class MergeEditorModel extends EditorModel { } class ResultEdits { + private readonly barrier = new ReentrancyBarrier(); + private readonly onDidChangeEmitter = new Emitter(); + public readonly onDidChange = this.onDidChangeEmitter.event; + constructor( private _diffs: LineDiff[], - private readonly originalTextModel: ITextModel, - private readonly textModel: ITextModel, + private readonly baseTextModel: ITextModel, + private readonly resultTextModel: ITextModel, + private readonly _editorWorkerService: IEditorWorkerService ) { this._diffs.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator)); + + resultTextModel.onDidChangeContent(e => { + this.barrier.runExclusively(() => { + this._editorWorkerService.computeDiff( + baseTextModel.uri, + resultTextModel.uri, + false, + 1000 + ).then(e => { + const diffs = + e?.changes.map((c) => + LineDiff.fromLineChange(c, baseTextModel, resultTextModel) + ) || []; + this._diffs = diffs; + + this.onDidChangeEmitter.fire(undefined); + }); + }); + }); } public get diffs(): readonly LineDiff[] { @@ -177,8 +227,9 @@ class ResultEdits { throw new BugIndicatingError(); } - const edits = new LineEdits([diffToRemove.getReverseLineEdit()]); - edits.apply(this.textModel); + this.barrier.runExclusivelyOrThrow(() => { + diffToRemove.getReverseLineEdit().apply(this.resultTextModel); + }); this._diffs = this._diffs.map((d) => d.modifiedRange.isAfter(diffToRemove.modifiedRange) @@ -204,16 +255,16 @@ class ResultEdits { for (let i = 0; i < this._diffs.length; i++) { const diff = this._diffs[i]; - if (diff.originalRange.intersects(edit.range)) { + if (diff.originalRange.touches(edit.range)) { throw new BugIndicatingError(); } else if (diff.originalRange.isAfter(edit.range)) { if (!firstAfter) { firstAfter = true; newDiffs.push(new LineDiff( - this.originalTextModel, + this.baseTextModel, edit.range, - this.textModel, + this.resultTextModel, new LineRange(edit.range.startLineNumber + delta, edit.newLines.length) )); } @@ -237,21 +288,22 @@ class ResultEdits { firstAfter = true; newDiffs.push(new LineDiff( - this.originalTextModel, + this.baseTextModel, edit.range, - this.textModel, + this.resultTextModel, new LineRange(edit.range.startLineNumber + delta, edit.newLines.length) )); } this._diffs = newDiffs; - const edits = new LineEdits([new LineEdit(edit.range.delta(delta), edit.newLines, edit.data)]); - edits.apply(this.textModel); + this.barrier.runExclusivelyOrThrow(() => { + new LineEdit(edit.range.delta(delta), edit.newLines).apply(this.resultTextModel); + }); } // TODO return many! public findConflictingDiffs(rangeInOriginalTextModel: LineRange): LineDiff | undefined { // TODO binary search - return this.diffs.find(d => d.originalRange.intersects(rangeInOriginalTextModel)); + return this.diffs.find(d => d.originalRange.touches(rangeInOriginalTextModel)); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model.ts b/src/vs/workbench/contrib/mergeEditor/browser/model.ts index deae7ace505..5d21dc17180 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model.ts @@ -9,11 +9,24 @@ import { Range } from 'vs/editor/common/core/range'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; import { ITextModel } from 'vs/editor/common/model'; -export class LineEdits { - constructor(public readonly edits: readonly LineEdit[]) { +export class LineEdit { + constructor( + public readonly range: LineRange, + public readonly newLines: string[] + ) { } + public equals(other: LineEdit): boolean { + return this.range.equals(other.range) && equals(this.newLines, other.newLines); } + public apply(model: ITextModel): void { + new LineEdits([this]).apply(model); + } +} + +export class LineEdits { + constructor(public readonly edits: readonly LineEdit[]) { } + public apply(model: ITextModel): void { model.pushEditOperations( null, @@ -26,188 +39,6 @@ export class LineEdits { } } -export class LineEdit { - equals(other: LineEdit) { - return this.range.equals(other.range) && equals(this.newLines, other.newLines); - } - - constructor( - public readonly range: LineRange, - public readonly newLines: string[], - public readonly data: T - ) { } -} - -export class ConflictGroup { - /** - * diffs1 and diffs2 together with the conflict relation form a bipartite graph. - * This method computes strongly connected components of that graph while maintaining the side of each diff. - */ - public static partitionDiffs( - originalTextModel: ITextModel, - input1TextModel: ITextModel, - diffs1: readonly LineDiff[], - input2TextModel: ITextModel, - diffs2: readonly LineDiff[] - ): ConflictGroup[] { - const compareByStartLineNumber = compareBy( - (d) => d.originalRange.startLineNumber, - numberComparator - ); - - const queueDiffs1 = new ArrayQueue( - diffs1.slice().sort(compareByStartLineNumber) - ); - const queueDiffs2 = new ArrayQueue( - diffs2.slice().sort(compareByStartLineNumber) - ); - - const result = new Array(); - - while (true) { - const lastDiff1 = queueDiffs1.peekLast(); - const lastDiff2 = queueDiffs2.peekLast(); - - if ( - lastDiff1 && - (!lastDiff2 || - lastDiff1.originalRange.startLineNumber >= - lastDiff2.originalRange.startLineNumber) - ) { - queueDiffs1.removeLast(); - - const otherConflictingWith = - queueDiffs2.takeFromEndWhile((d) => d.conflicts(lastDiff1)) || []; - - const singleLinesDiff = LineDiff.hull(otherConflictingWith); - - const moreConflictingWith = - (singleLinesDiff && - queueDiffs1.takeFromEndWhile((d) => - d.conflicts(singleLinesDiff) - )) || - []; - moreConflictingWith.push(lastDiff1); - - result.push( - new ConflictGroup( - originalTextModel, - input1TextModel, - moreConflictingWith, - queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - input2TextModel, - otherConflictingWith, - queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - ) - ); - } else if (lastDiff2) { - queueDiffs2.removeLast(); - - const otherConflictingWith = - queueDiffs1.takeFromEndWhile((d) => d.conflicts(lastDiff2)) || []; - - const singleLinesDiff = LineDiff.hull(otherConflictingWith); - - const moreConflictingWith = - (singleLinesDiff && - queueDiffs2.takeFromEndWhile((d) => - d.conflicts(singleLinesDiff) - )) || - []; - moreConflictingWith.push(lastDiff2); - - result.push( - new ConflictGroup( - originalTextModel, - input1TextModel, - otherConflictingWith, - queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - input2TextModel, - moreConflictingWith, - queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - ) - ); - } else { - break; - } - } - - result.reverse(); - - return result; - } - - public readonly input1FullDiff = LineDiff.hull(this.input1Diffs); - public readonly input2FullDiff = LineDiff.hull(this.input2Diffs); - - public readonly totalOriginalRange: LineRange; - public readonly totalInput1Range: LineRange; - public readonly totalInput2Range: LineRange; - - constructor( - public readonly originalTextModel: ITextModel, - public readonly input1TextModel: ITextModel, - public readonly input1Diffs: readonly LineDiff[], - public readonly input1DeltaLineCount: number, - public readonly input2TextModel: ITextModel, - public readonly input2Diffs: readonly LineDiff[], - public readonly input2DeltaLineCount: number, - ) { - if (this.input1Diffs.length === 0 && this.input2Diffs.length === 0) { - throw new BugIndicatingError('must have at least one diff'); - } - - const input1Diff = - this.input1FullDiff || - new LineDiff( - originalTextModel, - this.input2FullDiff!.originalRange, - input1TextModel, - this.input2FullDiff!.originalRange.delta(input1DeltaLineCount) - ); - - const input2Diff = - this.input2FullDiff || - new LineDiff( - originalTextModel, - this.input1FullDiff!.originalRange, - input1TextModel, - this.input1FullDiff!.originalRange.delta(input2DeltaLineCount) - ); - - const results = LineDiff.alignOriginalRegion([input1Diff, input2Diff]); - this.totalOriginalRange = results[0].originalRange; - this.totalInput1Range = results[0].modifiedRange; - this.totalInput2Range = results[1].modifiedRange; - } - - public get isConflicting(): boolean { - return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; - } - - public getInput1LineEdit(): LineEdit | undefined { - if (this.input1Diffs.length === 0) { - return undefined; - } - if (this.input1Diffs.length === 1) { - return this.input1Diffs[0].getLineEdit(); - } else { - throw new Error('Method not implemented.'); - } - } - - public getInput2LineEdit(): LineEdit | undefined { - if (this.input2Diffs.length === 0) { - return undefined; - } - if (this.input2Diffs.length === 1) { - return this.input2Diffs[0].getLineEdit(); - } else { - throw new Error('Method not implemented.'); - } - } -} - export class LineRange { public static hull(ranges: LineRange[]): LineRange | undefined { if (ranges.length === 0) { @@ -240,7 +71,10 @@ export class LineRange { return this.lineCount === 0; } - public intersects(other: LineRange): boolean { + /** + * Returns false if there is at least one line between `this` and `other`. + */ + public touches(other: LineRange): boolean { return ( this.endLineNumberExclusive >= other.startLineNumber && other.endLineNumberExclusive >= this.startLineNumber @@ -303,7 +137,7 @@ export class LineDiff { ); } - public static alignOriginalRegion(lineDiffs: readonly LineDiff[]): LineDiff[] { + public static alignOriginalRange(lineDiffs: readonly LineDiff[]): LineDiff[] { if (lineDiffs.length === 0) { return []; } @@ -344,7 +178,7 @@ export class LineDiff { public conflicts(other: LineDiff): boolean { this.ensureSameOriginalModel(other); - return this.originalRange.intersects(other.originalRange); + return this.originalRange.touches(other.originalRange); } public isStrictBefore(other: LineDiff): boolean { @@ -355,16 +189,14 @@ export class LineDiff { public getLineEdit(): LineEdit { return new LineEdit( this.originalRange, - this.getModifiedLines(), - undefined + this.getModifiedLines() ); } public getReverseLineEdit(): LineEdit { return new LineEdit( this.modifiedRange, - this.getOriginalLines(), - undefined + this.getOriginalLines() ); } @@ -386,34 +218,212 @@ export class LineDiff { } -export class MergeState { +/** + * Describes modifications in input 1 and input 2 for a specific range in base. + * + * The UI offers a mechanism to either apply all changes from input 1 or input 2 or both. + * + * Immutable. +*/ +export class ModifiedBaseRange { + /** + * diffs1 and diffs2 together with the conflict relation form a bipartite graph. + * This method computes strongly connected components of that graph while maintaining the side of each diff. + */ + public static fromDiffs( + originalTextModel: ITextModel, + input1TextModel: ITextModel, + diffs1: readonly LineDiff[], + input2TextModel: ITextModel, + diffs2: readonly LineDiff[] + ): ModifiedBaseRange[] { + const compareByStartLineNumber = compareBy( + (d) => d.originalRange.startLineNumber, + numberComparator + ); + + const queueDiffs1 = new ArrayQueue( + diffs1.slice().sort(compareByStartLineNumber) + ); + const queueDiffs2 = new ArrayQueue( + diffs2.slice().sort(compareByStartLineNumber) + ); + + const result = new Array(); + + while (true) { + const lastDiff1 = queueDiffs1.peekLast(); + const lastDiff2 = queueDiffs2.peekLast(); + + if ( + lastDiff1 && + (!lastDiff2 || + lastDiff1.originalRange.startLineNumber >= + lastDiff2.originalRange.startLineNumber) + ) { + queueDiffs1.removeLast(); + + const otherConflictingWith = + queueDiffs2.takeFromEndWhile((d) => d.conflicts(lastDiff1)) || []; + + const singleLinesDiff = LineDiff.hull(otherConflictingWith); + + const moreConflictingWith = + (singleLinesDiff && + queueDiffs1.takeFromEndWhile((d) => + d.conflicts(singleLinesDiff) + )) || + []; + moreConflictingWith.push(lastDiff1); + + result.push( + new ModifiedBaseRange( + originalTextModel, + input1TextModel, + moreConflictingWith, + queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + input2TextModel, + otherConflictingWith, + queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + ) + ); + } else if (lastDiff2) { + queueDiffs2.removeLast(); + + const otherConflictingWith = + queueDiffs1.takeFromEndWhile((d) => d.conflicts(lastDiff2)) || []; + + const singleLinesDiff = LineDiff.hull(otherConflictingWith); + + const moreConflictingWith = + (singleLinesDiff && + queueDiffs2.takeFromEndWhile((d) => + d.conflicts(singleLinesDiff) + )) || + []; + moreConflictingWith.push(lastDiff2); + + result.push( + new ModifiedBaseRange( + originalTextModel, + input1TextModel, + otherConflictingWith, + queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + input2TextModel, + moreConflictingWith, + queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, + ) + ); + } else { + break; + } + } + + result.reverse(); + + return result; + } + + private readonly input1FullDiff = LineDiff.hull(this.input1Diffs); + private readonly input2FullDiff = LineDiff.hull(this.input2Diffs); + + public readonly baseRange: LineRange; + public readonly input1Range: LineRange; + public readonly input2Range: LineRange; + + constructor( + public readonly baseTextModel: ITextModel, + public readonly input1TextModel: ITextModel, + public readonly input1Diffs: readonly LineDiff[], + public readonly input1DeltaLineCount: number, + public readonly input2TextModel: ITextModel, + public readonly input2Diffs: readonly LineDiff[], + public readonly input2DeltaLineCount: number, + ) { + if (this.input1Diffs.length === 0 && this.input2Diffs.length === 0) { + throw new BugIndicatingError('must have at least one diff'); + } + + const input1Diff = + this.input1FullDiff || + new LineDiff( + baseTextModel, + this.input2FullDiff!.originalRange, + input1TextModel, + this.input2FullDiff!.originalRange.delta(input1DeltaLineCount) + ); + + const input2Diff = + this.input2FullDiff || + new LineDiff( + baseTextModel, + this.input1FullDiff!.originalRange, + input1TextModel, + this.input1FullDiff!.originalRange.delta(input2DeltaLineCount) + ); + + const results = LineDiff.alignOriginalRange([input1Diff, input2Diff]); + this.baseRange = results[0].originalRange; + this.input1Range = results[0].modifiedRange; + this.input2Range = results[1].modifiedRange; + } + + public get isConflicting(): boolean { + return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; + } + + public getInput1LineEdit(): LineEdit | undefined { + if (this.input1Diffs.length === 0) { + return undefined; + } + //new LineDiff(this.baseTextModel, this.tota) + if (this.input1Diffs.length === 1) { + return this.input1Diffs[0].getLineEdit(); + } else { + throw new Error('Method not implemented.'); + } + } + + public getInput2LineEdit(): LineEdit | undefined { + if (this.input2Diffs.length === 0) { + return undefined; + } + if (this.input2Diffs.length === 1) { + return this.input2Diffs[0].getLineEdit(); + } else { + throw new Error('Method not implemented.'); + } + } +} + +export class ModifiedBaseRangeState { constructor( public readonly input1: boolean, public readonly input2: boolean, public readonly input2First: boolean ) { } - public withInput1(value: boolean): MergeState { - return new MergeState( + public withInput1(value: boolean): ModifiedBaseRangeState { + return new ModifiedBaseRangeState( value, this.input2, value && this.isEmpty ? false : this.input2First ); } - public withInput2(value: boolean): MergeState { - return new MergeState( + public withInput2(value: boolean): ModifiedBaseRangeState { + return new ModifiedBaseRangeState( this.input1, value, value && this.isEmpty ? true : this.input2First ); } - public toggleInput1(): MergeState { + public toggleInput1(): ModifiedBaseRangeState { return this.withInput1(!this.input1); } - public toggleInput2(): MergeState { + public toggleInput2(): ModifiedBaseRangeState { return this.withInput2(!this.input2); } @@ -435,3 +445,31 @@ export class MergeState { return arr.join(','); } } + +export class ReentrancyBarrier { + private isActive = false; + + public runExclusively(fn: () => void): void { + if (this.isActive) { + return; + } + this.isActive = true; + try { + fn(); + } finally { + this.isActive = false; + } + } + + public runExclusivelyOrThrow(fn: () => void): void { + if (this.isActive) { + throw new BugIndicatingError(); + } + this.isActive = true; + try { + fn(); + } finally { + this.isActive = false; + } + } +} From 3601028fd6582dc3483c4d9d3a345879f5d0acc3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 16 May 2022 16:06:32 +0200 Subject: [PATCH 050/350] Fixes unused imports. --- .../mergeEditor/browser/mergeEditor.ts | 56 +++++++++---------- .../mergeEditor/browser/mergeEditorModel.ts | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index b3f0ffcbbab..541b8995161 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -3,44 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/mergeEditor'; import { $, Dimension, reset } from 'vs/base/browser/dom'; +import { Direction, Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IAction } from 'vs/base/common/actions'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon } from 'vs/base/common/codicons'; +import { Color } from 'vs/base/common/color'; +import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { noBreakWhitespace } from 'vs/base/common/strings'; +import 'vs/css!./media/mergeEditor'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Range } from 'vs/editor/common/core/range'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { BugIndicatingError } from 'vs/base/common/errors'; +import { autorun, derivedObservable, IObservable, ITransaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { Direction, Grid, IView, IViewSize, SerializableGrid } from 'vs/base/browser/ui/grid/grid'; -import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ModifiedBaseRangeState, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { Color } from 'vs/base/common/color'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { localize } from 'vs/nls'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IAction } from 'vs/base/common/actions'; -import { Range } from 'vs/editor/common/core/range'; -import { Checkbox, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { EditorGutterWidget, IGutterItemInfo, IGutterItemView } from './editorGutterWidget'; -import { noBreakWhitespace } from 'vs/base/common/strings'; -import { ModifiedBaseRange, ModifiedBaseRangeState, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; -import { autorun, derivedObservable, derivedWritebaleObservable, IObservable, ITransaction } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { Codicon } from 'vs/base/common/codicons'; export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 989febccdb7..2305e1b80d5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { ModifiedBaseRange, LineEdit, LineEdits, LineDiff, ModifiedBaseRangeState, LineRange, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { ModifiedBaseRange, LineEdit, LineDiff, ModifiedBaseRangeState, LineRange, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; export class MergeEditorModelFactory { constructor( From d8e76554d1d4b36ebc216ce9689cba609660a397 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 16 May 2022 17:08:31 +0200 Subject: [PATCH 051/350] Avoid symbol instantiation. --- .../workbench/contrib/mergeEditor/browser/mergeEditorModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 2305e1b80d5..eb8a9f23856 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -65,7 +65,7 @@ export class MergeEditorModelFactory { } } -const InternalSymbol = Symbol(); +const InternalSymbol: unique symbol = null!; export class MergeEditorModel extends EditorModel { private resultEdits = new ResultEdits([], this.base, this.result, this.editorWorkerService); From f5f8a85c8771bfd5987cf03d91079f478c32a8db Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 May 2022 17:09:01 +0200 Subject: [PATCH 052/350] support deprecated extensions --- .../abstractExtensionManagementService.ts | 6 ++-- .../common/extensionGalleryService.ts | 23 ++------------ .../common/extensionManagement.ts | 2 +- .../common/unsupportedExtensionsMigration.ts | 11 +++++-- .../extensions/browser/extensionEditor.ts | 2 +- .../extensions/browser/extensionsActions.ts | 30 ++++++++++++++----- .../extensions/browser/extensionsList.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 5 +++- .../browser/extensionsWorkbenchService.ts | 18 ++++------- .../contrib/extensions/common/extensions.ts | 2 +- .../browser/webExtensionsScannerService.ts | 11 +++---- 11 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 81fa42981c2..d096eaa5ff7 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -10,6 +10,7 @@ import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { isBoolean } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { @@ -363,8 +364,9 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - if (!!report.unsupportedPreReleaseExtensions && !!report.unsupportedPreReleaseExtensions[extension.identifier.id]) { - throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is no longer supported. It is now part of the '{1}' extension as a pre-release version.", extension.identifier.id, report.unsupportedPreReleaseExtensions[extension.identifier.id].displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); + const deprecated = report.deprecated ? report.deprecated[extension.identifier.id.toLowerCase()] : undefined; + if (deprecated && !isBoolean(deprecated)) { + throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is deprecated. Instead use '{1}' extension.", extension.identifier.id, deprecated.displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); } if (!await this.canInstall(extension)) { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index eba50f040e4..b75a76752b8 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -535,11 +535,9 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller }; } -type PreReleaseMigrationInfo = { id: string; displayName: string; migrateStorage?: boolean; engine?: string }; interface IRawExtensionsControlManifest { malicious: string[]; - unsupported?: IStringDictionary; - migrateToPreRelease?: IStringDictionary; + deprecated?: IStringDictionary; } abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { @@ -1150,30 +1148,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const result = await asJson(context); const malicious: IExtensionIdentifier[] = []; - const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string; displayName: string; migrateStorage?: boolean }> = {}; - if (result) { for (const id of result.malicious) { malicious.push({ id }); } - if (result.unsupported) { - for (const extensionId of Object.keys(result.unsupported)) { - const value = result.unsupported[extensionId]; - if (!isBoolean(value)) { - unsupportedPreReleaseExtensions[extensionId.toLowerCase()] = value.preReleaseExtension; - } - } - } - if (result.migrateToPreRelease) { - for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) { - if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) { - unsupportedPreReleaseExtensions[unsupportedPreReleaseExtensionId.toLowerCase()] = preReleaseExtensionInfo; - } - } - } } - return { malicious, unsupportedPreReleaseExtensions }; + return { malicious, deprecated: result?.deprecated }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index b28421b4bf1..061de6580a9 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -284,7 +284,7 @@ export const enum StatisticType { export interface IExtensionsControlManifest { malicious: IExtensionIdentifier[]; - unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string; displayName: string; migrateStorage?: boolean }>; + deprecated?: IStringDictionary; } export const enum InstallOperation { diff --git a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts index 15ea0319efc..87eaf1447a6 100644 --- a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts +++ b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { isBoolean } from 'vs/base/common/types'; import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; @@ -20,18 +21,22 @@ import { ILogService } from 'vs/platform/log/common/log'; export async function migrateUnsupportedExtensions(extensionManagementService: IExtensionManagementService, galleryService: IExtensionGalleryService, extensionStorageService: IExtensionStorageService, extensionEnablementService: IGlobalExtensionEnablementService, logService: ILogService): Promise { try { const extensionsControlManifest = await extensionManagementService.getExtensionsControlManifest(); - if (!extensionsControlManifest.unsupportedPreReleaseExtensions) { + if (!extensionsControlManifest.deprecated) { return; } const installed = await extensionManagementService.getInstalled(ExtensionType.User); - for (const [unsupportedExtensionId, { id: preReleaseExtensionId, migrateStorage }] of Object.entries(extensionsControlManifest.unsupportedPreReleaseExtensions)) { + for (const [unsupportedExtensionId, deprecated] of Object.entries(extensionsControlManifest.deprecated)) { + if (isBoolean(deprecated)) { + continue; + } + const { id: preReleaseExtensionId, migrateStorage, preRelease } = deprecated; const unsupportedExtension = installed.find(i => areSameExtensions(i.identifier, { id: unsupportedExtensionId })); // Unsupported Extension is not installed if (!unsupportedExtension) { continue; } - const gallery = (await galleryService.getExtensions([{ id: preReleaseExtensionId, preRelease: true }], { targetPlatform: await extensionManagementService.getTargetPlatform(), compatible: true }, CancellationToken.None))[0]; + const gallery = (await galleryService.getExtensions([{ id: preReleaseExtensionId, preRelease }], { targetPlatform: await extensionManagementService.getTargetPlatform(), compatible: true }, CancellationToken.None))[0]; if (!gallery) { logService.info(`Skipping migrating '${unsupportedExtension.identifier.id}' extension because, the comaptible target '${preReleaseExtensionId}' extension is not found`); continue; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index ba209ca06a9..2f1244e6ebd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -658,7 +658,7 @@ export class ExtensionEditor extends EditorPane { } }; reset(template.recommendation); - if (extension.state === ExtensionState.Installed) { + if (extension.deprecated || extension.state === ExtensionState.Installed) { return; } updateRecommendationText(false); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 6e4013f7272..06033ae52a7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -250,6 +250,7 @@ export abstract class AbstractInstallAction extends ExtensionAction { @IExtensionService private readonly runtimeExtensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @ILabelService private readonly labelService: ILabelService, + @IDialogService private readonly dialogService: IDialogService, ) { super(id, localize('install', "Install"), cssClass, false); this.update(); @@ -274,6 +275,19 @@ export abstract class AbstractInstallAction extends ExtensionAction { if (!this.extension) { return; } + + if (this.extension.deprecated && isBoolean(this.extension.deprecated)) { + const result = await this.dialogService.confirm({ + type: 'warning', + title: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), + message: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), + primaryButton: localize('install anyway', "Install Anyway"), + }); + if (!result.confirmed) { + return; + } + } + this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.installPreReleaseVersion }); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -373,12 +387,13 @@ export class InstallAction extends AbstractInstallAction { @IExtensionService runtimeExtensionService: IExtensionService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, + @IDialogService dialogService: IDialogService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IWorkbenchExtensionManagementService private readonly workbenchExtensioManagementService: IWorkbenchExtensionManagementService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super(`extensions.install`, installPreReleaseVersion, InstallAction.Class, - extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService); this.updateLabel(); this._register(labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this._register(Event.any(userDataSyncEnablementService.onDidChangeEnablement, @@ -438,11 +453,12 @@ export class InstallAndSyncAction extends AbstractInstallAction { @IExtensionService runtimeExtensionService: IExtensionService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, + @IDialogService dialogService: IDialogService, @IProductService productService: IProductService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super('extensions.installAndSync', installPreReleaseVersion, AbstractInstallAction.Class, - extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService); this.tooltip = localize({ key: 'install everywhere tooltip', comment: ['Placeholder is the name of the product. Eg: Visual Studio Code or Visual Studio Code - Insiders'] }, "Install this extension in all your synced {0} instances", productService.nameLong); this._register(Event.any(userDataSyncEnablementService.onDidChangeEnablement, Event.filter(userDataSyncEnablementService.onDidChangeResourceEnablement, e => e[0] === SyncResource.Extensions))(() => this.update())); @@ -2156,13 +2172,13 @@ export class ExtensionStatusAction extends ExtensionAction { return; } - if (this.extension.isUnsupported) { - if (isBoolean(this.extension.isUnsupported)) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension no longer supported.")) }, true); + if (this.extension.deprecated) { + if (isBoolean(this.extension.deprecated)) { + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension is no longer being maintained and is deprecated.")) }, true); } else { - const link = `[${this.extension.isUnsupported.preReleaseExtension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.isUnsupported.preReleaseExtension.id]))}`)})`; + const link = `[${this.extension.deprecated.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecated.id]))}`)})`; if (this.extension.state !== ExtensionState.Installed) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension is now part of the {0} extension as a pre-release version.", link)) }, true); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension has been deprecated. Instead use {0}.", link)) }, true); } } return; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 9a31eb974cc..4755c672581 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -188,7 +188,7 @@ export class Renderer implements IPagedRenderer { const updateEnablement = async () => { let isDisabled = false; if (extension.state === ExtensionState.Uninstalled) { - isDisabled = !(await this.extensionsWorkbenchService.canInstall(extension)); + isDisabled = !!extension.deprecated || !(await this.extensionsWorkbenchService.canInstall(extension)); } else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtensions = await this.extensionService.getExtensions(); const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 9b38a120296..e4592383fe6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -185,7 +185,7 @@ export class RecommendationWidget extends ExtensionWidget { render(): void { this.clear(); - if (!this.extension || this.extension.state === ExtensionState.Installed) { + if (!this.extension || this.extension.state === ExtensionState.Installed || this.extension.deprecated) { return; } const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); @@ -549,6 +549,9 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.state === ExtensionState.Installed) { return undefined; } + if (extension.deprecated) { + return undefined; + } const recommendation = this.extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]; if (!recommendation?.reasonText) { return undefined; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 8f96b5e7fcf..3aacb593829 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -188,7 +188,7 @@ export class Extension implements IExtension { } public isMalicious: boolean = false; - public isUnsupported: boolean | { preReleaseExtension: { id: string; displayName: string } } = false; + public deprecated: boolean | { id: string; displayName: string } = false; get installCount(): number | undefined { return this.gallery ? this.gallery.installCount : undefined; @@ -390,17 +390,9 @@ class Extensions extends Disposable { static updateExtensionFromControlManifest(extension: Extension, extensionsControlManifest: IExtensionsControlManifest): void { const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); - if (extension.isMalicious !== isMalicious) { - extension.isMalicious = isMalicious; - } - const unsupportedPreRelease = extensionsControlManifest.unsupportedPreReleaseExtensions ? extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()] : undefined; - if (unsupportedPreRelease) { - if (isBoolean(extension.isUnsupported) || !areSameExtensions({ id: extension.isUnsupported.preReleaseExtension.id }, { id: unsupportedPreRelease.id })) { - extension.isUnsupported = { preReleaseExtension: unsupportedPreRelease }; - } - } else if (extension.isUnsupported) { - extension.isUnsupported = false; - } + extension.isMalicious = isMalicious; + const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; + extension.deprecated = deprecated ?? false; } private readonly _onChange: Emitter<{ extension: Extension; operation?: InstallOperation } | undefined> = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); @@ -1155,7 +1147,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } - if (extension.isUnsupported) { + if (extension.deprecated && !isBoolean(extension.deprecated)) { return false; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index b3846d038fc..23744cd4f9b 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -78,7 +78,7 @@ export interface IExtension { readonly local?: ILocalExtension; gallery?: IGalleryExtension; readonly isMalicious: boolean; - readonly isUnsupported: boolean | { preReleaseExtension: { id: string; displayName: string } }; + readonly deprecated: boolean | { id: string; displayName: string }; } export const SERVICE_ID = 'extensionsWorkbenchService'; diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index 8438e2f3377..aa1eb99bc0e 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -21,7 +21,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { localize } from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; -import { isString } from 'vs/base/common/types'; +import { isBoolean, isString } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ResourceMap } from 'vs/base/common/map'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; @@ -140,10 +140,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten this.logService.info(`Checking additional builtin extensions: Ignoring '${extension.id}' because it is reported to be malicious.`); continue; } - if (extensionsControlManifest.unsupportedPreReleaseExtensions && extensionsControlManifest.unsupportedPreReleaseExtensions[extension.id.toLowerCase()]) { - const preReleaseExtensionId = extensionsControlManifest.unsupportedPreReleaseExtensions[extension.id.toLowerCase()].id; - this.logService.info(`Checking additional builtin extensions: '${extension.id}' is no longer supported, instead using '${preReleaseExtensionId}'`); - result.push({ id: preReleaseExtensionId, preRelease: true }); + const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.id.toLowerCase()] : undefined; + if (deprecated && !isBoolean(deprecated)) { + const preReleaseExtensionId = deprecated.id; + this.logService.info(`Checking additional builtin extensions: '${extension.id}' is deprecated, instead using '${preReleaseExtensionId}'`); + result.push({ id: preReleaseExtensionId, preRelease: !!deprecated.preRelease }); } else { result.push(extension); } From d90d07adef925477222c83ae090a233544ab35e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 16 May 2022 17:46:39 +0200 Subject: [PATCH 053/350] wip: policy --- .eslintrc.json | 1 + .../configuration/common/configurations.ts | 5 +- src/vs/platform/policy/common/policy.ts | 6 -- src/vs/platform/policy/common/policyIpc.ts | 73 +++++++++++++++++ .../policy/node/vscode-policy-watcher.d.ts | 30 +++++++ .../policy/node/windowsPolicyService.ts | 79 +++++++++++++++++++ 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 src/vs/platform/policy/common/policyIpc.ts create mode 100644 src/vs/platform/policy/node/vscode-policy-watcher.d.ts create mode 100644 src/vs/platform/policy/node/windowsPolicyService.ts diff --git a/.eslintrc.json b/.eslintrc.json index 22157840d84..da169cdcc0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -248,6 +248,7 @@ "url", "util", "v8-inspect-profiler", + "vscode-policy-watcher", "vscode-proxy-agent", "vscode-regexpp", "vscode-textmate", diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 353ad2070d3..dad918f94e1 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -109,7 +109,10 @@ export class PolicyConfiguration extends Disposable { } async reload(): Promise { - await this.policyService.refresh(); + if (this.policyService instanceof FilePolicyService) { + await this.policyService.refresh(); + } + this.update(this.defaultConfiguration.configurationModel.keys, false); return this._configurationModel; } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6963bf22081..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,14 +12,12 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; - refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } - async refresh() { } getPolicyValue() { return undefined; } } @@ -35,10 +33,6 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } - async refresh() { - await Promise.all(this.policyServices.map(p => p.refresh())); - } - getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts new file mode 100644 index 00000000000..00a0913f693 --- /dev/null +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +export class PolicyChannel implements IServerChannel { + + private readonly disposables = new DisposableStore(); + + constructor(private service: IPolicyService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChange': return Event.map( + this.service.onDidChange, + names => new Map( + names + .map(name => [name, this.service.getPolicyValue(name)]) + .filter(pair => pair[1] !== undefined) as [PolicyName, PolicyValue][]), + this.disposables + ); + } + + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string): Promise { + switch (command) { + case 'initialize': return this.service.initialize(); + } + + throw new Error(`Call not found: ${command}`); + } + + dispose() { + this.disposables.dispose(); + } +} + +export class PolicyChannelClient implements IPolicyService { + + declare readonly _serviceBrand: undefined; + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(private readonly channel: IChannel) { + this.channel.listen('onDidChange')(policies => { + for (const [name, value] of policies) { + if (value === undefined) { + this.policies.delete(name); + } else { + this.policies.set(name, value); + } + } + }); + } + + initialize(): Promise { + return this.channel.call('initialize'); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} diff --git a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts new file mode 100644 index 00000000000..c305f178822 --- /dev/null +++ b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode-policy-watcher' { + interface Watcher { + dispose(): void; + } + + interface Policies { + [policyName: string]: { + type: string + }; + } + + interface PolicyValues { + [policyName: string]: string | number | boolean; + } + + function createWatcher( + productName: string, + policies: Policies, + onDidChange: (update: PolicyValues) => void + ): Watcher; + + namespace createWatcher { } + + export = createWatcher; +} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts new file mode 100644 index 00000000000..64e7b8bb7d6 --- /dev/null +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import * as createWatcher from 'vscode-policy-watcher'; +import { IStringDictionary } from 'vs/base/common/collections'; + +export class WindowsPolicyService extends Disposable implements IPolicyService { + + private policies: Policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + constructor( + @IProductService private readonly productService: IProductService + ) { + super(); + } + + initialize(): Promise { + return new Promise(c => { + if (!this.productService.win32RegValueName) { + return; + } + + const policies: IStringDictionary<{ type: string }> = {}; + const configRegistry = Registry.as(Extensions.Configuration); + + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; + + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + policies[policy.name] = { type: config.type }; + } + } + } + } + + let first = true; + + this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + for (const key in update) { + this.policies.set(key, update[key]); + } + + if (first) { + first = false; + c(); + } else { + this._onDidChange.fire(Object.keys(update)); + } + })); + }); + } + + async refresh(): Promise { + // NOOP + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } +} From 9a9240dc36f15365efb854a6e6d770eb7d07731b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 May 2022 18:40:55 +0200 Subject: [PATCH 054/350] do not support refresh for policies --- .../configuration/common/configurations.ts | 6 ------ .../platform/policy/common/filePolicyService.ts | 10 +++------- src/vs/platform/policy/common/policy.ts | 6 ------ .../configuration/browser/configurationService.ts | 6 ------ .../browser/configurationEditingService.test.ts | 4 +++- .../test/browser/configurationService.test.ts | 15 +++------------ 6 files changed, 9 insertions(+), 38 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 353ad2070d3..2dd2290898e 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -108,12 +108,6 @@ export class PolicyConfiguration extends Disposable { return this._configurationModel; } - async reload(): Promise { - await this.policyService.refresh(); - this.update(this.defaultConfiguration.configurationModel.keys, false); - return this._configurationModel; - } - private onDidChangePolicies(policyNames: readonly PolicyName[]): void { const policyConfigurations = Registry.as(Extensions.Configuration).getPolicyConfigurations(); const keys = coalesce(policyNames.map(policyName => policyConfigurations.get(policyName))); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 44d3105dad3..e3f34bafc3d 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -43,15 +43,11 @@ export class FilePolicyService extends Disposable implements IPolicyService { const onDidChangePolicyFile = Event.filter(fileService.onDidFilesChange, e => e.affects(file)); this._register(fileService.watch(file)); - this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.doRefresh()))); + this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } async initialize(): Promise { - await this.doRefresh(); - } - - async refresh(): Promise { - await this.doRefresh(); + await this.refresh(); } private async read(): Promise { @@ -77,7 +73,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { return policies; } - private async doRefresh(): Promise { + private async refresh(): Promise { const policies = await this.read(); const diff = keysDiff(this.policies, policies); this.policies = policies; diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6963bf22081..4febe66904f 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -12,14 +12,12 @@ export type Policies = Map; export interface IPolicyService { readonly onDidChange: Event; initialize(): Promise; - refresh(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async initialize() { } - async refresh() { } getPolicyValue() { return undefined; } } @@ -35,10 +33,6 @@ export class MultiPolicyService implements IPolicyService { await Promise.all(this.policyServices.map(p => p.initialize())); } - async refresh() { - await Promise.all(this.policyServices.map(p => p.refresh())); - } - getPolicyValue(name: PolicyName) { for (const policyService of this.policyServices) { const result = policyService.getPolicyValue(name); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 0db722acbc1..752eec13a27 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -343,7 +343,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat async reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise { if (target === undefined) { this.reloadDefaultConfiguration(); - await this.reloadPolicyConfiguration(); const { local, remote } = await this.reloadUserConfiguration(); await this.reloadWorkspaceConfiguration(); await this.loadConfiguration(local, remote); @@ -358,7 +357,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat switch (target) { case ConfigurationTarget.DEFAULT: this.reloadDefaultConfiguration(); - await this.reloadPolicyConfiguration(); return; case ConfigurationTarget.USER: { @@ -603,10 +601,6 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.onDefaultConfigurationChanged(this.defaultConfiguration.reload()); } - private async reloadPolicyConfiguration(): Promise { - this.onPolicyConfigurationChanged(await this.policyConfiguration.reload()); - } - private async reloadUserConfiguration(): Promise<{ local: ConfigurationModel; remote: ConfigurationModel }> { const [local, remote] = await Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]); return { local, remote }; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 2fba810064f..4b54d2e7a5d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -6,6 +6,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import * as json from 'vs/base/common/json'; +import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -195,8 +196,9 @@ suite('ConfigurationEditingService', () => { }); test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { + const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); - await instantiationService.get(IConfigurationService).reloadConfiguration(); + await promise; try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 7f980fd1d1d..9236183075d 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -966,10 +966,10 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('policy value override all', async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "configurationService.folder.policySetting": "userValue" }')); - await fileService.writeFile(joinPath(workspaceService.getWorkspace().folders[0].uri, '.vscode', 'settings.json'), VSBuffer.fromString('{ "configurationService.folder.policySetting": "workspaceValue" }')); - await testObject.reloadConfiguration(); + const result = await promise; + assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); }); @@ -982,15 +982,6 @@ suite('WorkspaceConfigurationService - Folder', () => { assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, undefined); }); - test('policy change should trigger change event ', async () => { - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - const result = await promise; - assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); - assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); - assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); - }); - test('reload configuration emits events after global configuraiton changes', async () => { await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); const target = sinon.spy(); From 49828c413e38499f3af65dbae30a9682368ffb0f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 16 May 2022 11:13:50 -0700 Subject: [PATCH 055/350] cleanup --- src/vs/platform/terminal/common/terminal.ts | 3 +-- .../common/xterm/shellIntegrationAddon.ts | 5 ++++- .../terminal/browser/xterm/xtermTerminal.ts | 18 +++++------------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 95527630c52..dda984c4b5d 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -247,8 +247,7 @@ export const enum ShellIntegrationTelemetry { ActivationTimeout = 'terminal/shellIntegrationActivationTimeout', FailureProcessExit = 'terminal/shellIntegrationFailureProcessExit', FailureCustomArgs = 'terminal/shellIntegrationActivationFailureCustomArgs', - Success = 'terminal/shellIntegrationActivationSucceeded', - DisabledByUser = 'terminal/shellIntegrationDisabledByUser' + Success = 'terminal/shellIntegrationActivationSucceeded' } export interface IPtyHostController { diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index aa9aca4d9d7..0b809d33db4 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -125,6 +125,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _terminal?: Terminal; readonly capabilities = new TerminalCapabilityStore(); private _hasUpdatedTelemetry: boolean = false; + private _activationTimeout!: NodeJS.Timeout; constructor( @ILogService private readonly _logService: ILogService, @@ -145,6 +146,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati if (!this._hasUpdatedTelemetry && didHandle) { this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.Success); this._hasUpdatedTelemetry = true; + clearTimeout(this._activationTimeout); } return didHandle; } @@ -225,11 +227,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { - setTimeout(() => { + this._activationTimeout = setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.ActivationTimeout); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } + this._hasUpdatedTelemetry = true; }, 10000); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 1384d20c51c..7f6faec3f24 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -13,7 +13,7 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IShellIntegration, ShellIntegrationTelemetry, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IShellIntegration, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ITerminalFont, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { isSafari } from 'vs/base/browser/browser'; import { ICommandTracker, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -60,7 +60,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { // Always on addons private _commandNavigationAddon: CommandNavigationAddon; - private _shellIntegrationAddon: ShellIntegrationAddon | undefined; + private _shellIntegrationAddon: ShellIntegrationAddon; private _decorationAddon: DecorationAddon | undefined; // Optional addons @@ -80,7 +80,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { readonly onDidChangeSelection = this._onDidChangeSelection.event; get commandTracker(): ICommandTracker { return this._commandNavigationAddon; } - get shellIntegration(): IShellIntegration | undefined { return this._shellIntegrationAddon; } + get shellIntegration(): IShellIntegration { return this._shellIntegrationAddon; } private _target: TerminalLocation | undefined; set target(location: TerminalLocation | undefined) { @@ -175,6 +175,8 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); + this._shellIntegrationAddon = new ShellIntegrationAddon(this._logService, this._telemetryService); + this.raw.loadAddon(this._shellIntegrationAddon); this._updateShellIntegrationAddons(); } @@ -610,9 +612,6 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { const shellIntegrationEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); if (shellIntegrationEnabled) { - if (!this._shellIntegrationAddon) { - this._createShellIntegrationAddon(); - } if (decorationsEnabled && !this._decorationAddon) { this._createDecorationAddon(); } else if (this._decorationAddon && !decorationsEnabled) { @@ -624,14 +623,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { if (this._decorationAddon) { this._decorationAddon.dispose(); this._decorationAddon = undefined; - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.DisabledByUser - ); } } } - - private _createShellIntegrationAddon(): void { - this._shellIntegrationAddon = new ShellIntegrationAddon(this._logService, this._telemetryService); - this.raw.loadAddon(this._shellIntegrationAddon); - } } From 64030273a62b9075b3369eaf0da6cd68251a3215 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 16 May 2022 11:24:05 -0700 Subject: [PATCH 056/350] remove main thread proxy kenrel --- .../api/browser/extensionHost.contribution.ts | 1 - src/vs/workbench/api/common/extHost.protocol.ts | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 2fa247a959b..37afee52e07 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -64,7 +64,6 @@ import './mainThreadWorkspace'; import './mainThreadComments'; import './mainThreadNotebook'; import './mainThreadNotebookKernels'; -import './mainThreadNotebookProxyKernels'; import './mainThreadNotebookDocumentsAndEditors'; import './mainThreadNotebookRenderers'; import './mainThreadInteractive'; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 25f33cb3f4a..607bc5ed01d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1035,12 +1035,6 @@ export interface MainThreadNotebookKernelsShape extends IDisposable { $completeExecution(handle: number, data: SerializableObjectWithBuffers): void; } -export interface MainThreadNotebookProxyKernelsShape extends IDisposable { - $addProxyKernel(handle: number, data: INotebookProxyKernelDto): Promise; - $updateProxyKernel(handle: number, data: Partial): void; - $removeProxyKernel(handle: number): void; -} - export interface MainThreadNotebookRenderersShape extends IDisposable { $postMessage(editorId: string | undefined, rendererId: string, message: unknown): Promise; } @@ -2129,10 +2123,6 @@ export interface ExtHostNotebookKernelsShape { $cellExecutionChanged(uri: UriComponents, cellHandle: number, state: notebookCommon.NotebookCellExecutionState | undefined): void; } -export interface ExtHostNotebookProxyKernelsShape { - $resolveKernel(handle: number): Promise; -} - export interface ExtHostInteractiveShape { $willAddInteractiveDocument(uri: UriComponents, eol: string, languageId: string, notebookUri: UriComponents): void; $willRemoveInteractiveDocument(uri: UriComponents, notebookUri: UriComponents): void; @@ -2309,7 +2299,6 @@ export const MainContext = { MainThreadNotebookDocuments: createProxyIdentifier('MainThreadNotebookDocumentsShape'), MainThreadNotebookEditors: createProxyIdentifier('MainThreadNotebookEditorsShape'), MainThreadNotebookKernels: createProxyIdentifier('MainThreadNotebookKernels'), - MainThreadNotebookProxyKernels: createProxyIdentifier('MainThreadNotebookProxyKernels'), MainThreadNotebookRenderers: createProxyIdentifier('MainThreadNotebookRenderers'), MainThreadInteractive: createProxyIdentifier('MainThreadInteractive'), MainThreadTheming: createProxyIdentifier('MainThreadTheming'), @@ -2362,7 +2351,6 @@ export const ExtHostContext = { ExtHostNotebookDocuments: createProxyIdentifier('ExtHostNotebookDocuments'), ExtHostNotebookEditors: createProxyIdentifier('ExtHostNotebookEditors'), ExtHostNotebookKernels: createProxyIdentifier('ExtHostNotebookKernels'), - ExtHostNotebookProxyKernels: createProxyIdentifier('ExtHostNotebookProxyKernels'), ExtHostNotebookRenderers: createProxyIdentifier('ExtHostNotebookRenderers'), ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), From 4f741d6370be488aa3d68d7eda76376a962ce885 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 16 May 2022 21:44:12 +0200 Subject: [PATCH 057/350] add ability to provide detail and description for `input1` and `input2`. This isn't wire up yet because it requires bigger changes in git-land --- .../browser/mergeEditor.contribution.ts | 90 ++++++++++++++----- .../mergeEditor/browser/mergeEditor.ts | 4 +- .../mergeEditor/browser/mergeEditorInput.ts | 28 ++++-- .../mergeEditor/browser/mergeEditorModel.ts | 14 ++- .../browser/mergeEditorSerializer.ts | 10 ++- 5 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 3f72bfca524..b4495ccc657 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { ctxIsMergeEditor, ctxUsesColumnLayout, MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditor'; -import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { MergeEditorSerializer } from './mergeEditorSerializer'; import { Codicon } from 'vs/base/common/codicons'; @@ -71,38 +71,84 @@ registerAction2(class Open extends Action2 { }); } run(accessor: ServicesAccessor, ...args: any[]): void { - const validatedArgs = ITestMergeEditorArgs.validate(args[0]); - - function normalize(uri: URI | UriComponents | string): URI { - if (typeof uri === 'string') { - return URI.parse(uri); - } else { - return URI.revive(uri); - } - } + const validatedArgs = IRelaxedOpenArgs.validate(args[0]); const instaService = accessor.get(IInstantiationService); const input = instaService.createInstance( MergeEditorInput, - normalize(validatedArgs.ancestor), - normalize(validatedArgs.input1), - normalize(validatedArgs.input2), - normalize(validatedArgs.output), + validatedArgs.ancestor, + validatedArgs.input1, + validatedArgs.input2, + validatedArgs.output, ); accessor.get(IEditorService).openEditor(input); } }); -namespace ITestMergeEditorArgs { - export function validate(args: any): ITestMergeEditorArgs { - return args as ITestMergeEditorArgs; +namespace IRelaxedOpenArgs { + function toUri(obj: unknown): URI { + if (typeof obj === 'string') { + return URI.parse(obj, true); + } else if (obj && typeof obj === 'object') { + return URI.revive(obj); + } + throw new TypeError('invalid argument'); + } + + function isUriComponents(obj: unknown): obj is UriComponents { + if (!obj || typeof obj !== 'object') { + return false; + } + return typeof (obj).scheme === 'string' + && typeof (obj).authority === 'string' + && typeof (obj).path === 'string' + && typeof (obj).query === 'string' + && typeof (obj).fragment === 'string'; + } + + function toInputResource(obj: unknown): MergeEditorInputData { + if (typeof obj === 'string') { + return new MergeEditorInputData(URI.parse(obj, true), undefined, undefined); + } + if (!obj || typeof obj !== 'object') { + throw new TypeError('invalid argument'); + } + + if (isUriComponents(obj)) { + return new MergeEditorInputData(URI.revive(obj), undefined, undefined); + } + + let uri = toUri((obj).uri); + let detail = (obj).detail; + let description = (obj).description; + return new MergeEditorInputData(uri, detail, description); + } + + export function validate(obj: unknown): IOpenEditorArgs { + if (!obj || typeof obj !== 'object') { + throw new TypeError('invalid argument'); + } + const ancestor = toUri((obj).ancestor); + const output = toUri((obj).output); + const input1 = toInputResource((obj).input1); + const input2 = toInputResource((obj).input2); + return { ancestor, input1, input2, output }; } } -interface ITestMergeEditorArgs { - ancestor: URI | string; - input1: URI | string; - input2: URI | string; - output: URI | string; +type IRelaxedInputData = { uri: UriComponents; detail?: string; description?: string }; + +type IRelaxedOpenArgs = { + ancestor: UriComponents | string; + input1: IRelaxedInputData | string; + input2: IRelaxedInputData | string; + output: UriComponents | string; +}; + +interface IOpenEditorArgs { + ancestor: URI; + input1: MergeEditorInputData; + input2: MergeEditorInputData; + output: URI; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 541b8995161..bb5cb6334e6 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -170,8 +170,8 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.input1View.setModel(model.input1, localize('yours', 'Yours'), undefined); - this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), undefined); + this.input1View.setModel(model.input1, localize('yours', 'Yours'), model.input1Detail); + this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), model.input2Detail); this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); let input1Decorations = new Array(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index da2ada49ee0..f6b69488351 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -20,11 +20,19 @@ import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/te export interface MergeEditorInputJSON { anchestor: URI; - inputOne: URI; - inputTwo: URI; + inputOne: { uri: URI; detail?: string; description?: string }; + inputTwo: { uri: URI; detail?: string; description?: string }; result: URI; } +export class MergeEditorInputData { + constructor( + readonly uri: URI, + readonly detail: string | undefined, + readonly description: string | undefined, + ) { } +} + export class MergeEditorInput extends AbstractTextResourceEditorInput { static readonly ID = 'mergeEditor.Input'; @@ -35,8 +43,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { constructor( private readonly _anchestor: URI, - private readonly _input1: URI, - private readonly _input2: URI, + private readonly _input1: MergeEditorInputData, + private readonly _input2: MergeEditorInputData, private readonly _result: URI, @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService private readonly _textModelService: ITextModelService, @@ -94,14 +102,18 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { if (!this._model) { const anchestor = await this._textModelService.createModelReference(this._anchestor); - const input1 = await this._textModelService.createModelReference(this._input1); - const input2 = await this._textModelService.createModelReference(this._input2); + const input1 = await this._textModelService.createModelReference(this._input1.uri); + const input2 = await this._textModelService.createModelReference(this._input2.uri); const result = await this._textModelService.createModelReference(this._result); this._model = await this.mergeEditorModelFactory.create( anchestor.object.textEditorModel, input1.object.textEditorModel, + this._input1.detail, + this._input1.description, input2.object.textEditorModel, + this._input2.detail, + this._input2.description, result.object.textEditorModel ); @@ -121,8 +133,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { return false; } return isEqual(this._anchestor, otherInput._anchestor) - && isEqual(this._input1, otherInput._input1) - && isEqual(this._input2, otherInput._input2) + && isEqual(this._input1.uri, otherInput._input1.uri) + && isEqual(this._input2.uri, otherInput._input2.uri) && isEqual(this._result, otherInput._result); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index eb8a9f23856..ef5003afc4c 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -21,7 +21,11 @@ export class MergeEditorModelFactory { public async create( base: ITextModel, input1: ITextModel, + input1Detail: string | undefined, + input1Description: string | undefined, input2: ITextModel, + input2Detail: string | undefined, + input2Description: string | undefined, result: ITextModel, ): Promise { @@ -56,7 +60,11 @@ export class MergeEditorModelFactory { InternalSymbol, base, input1, + input1Detail, + input1Description, input2, + input2Detail, + input2Description, result, changesInput1, changesInput2, @@ -71,10 +79,14 @@ export class MergeEditorModel extends EditorModel { private resultEdits = new ResultEdits([], this.base, this.result, this.editorWorkerService); constructor( - symbol: typeof InternalSymbol, + _symbol: typeof InternalSymbol, readonly base: ITextModel, readonly input1: ITextModel, + readonly input1Detail: string | undefined, + readonly input1Description: string | undefined, readonly input2: ITextModel, + readonly input2Detail: string | undefined, + readonly input2Description: string | undefined, readonly result: ITextModel, private readonly inputOneLinesDiffs: readonly LineDiff[], private readonly inputTwoLinesDiffs: readonly LineDiff[], diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts index 2480f0950ba..d4e52947bb8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorSerializer.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { parse } from 'vs/base/common/marshalling'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorSerializer } from 'vs/workbench/common/editor'; -import { MergeEditorInput, MergeEditorInputJSON } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { MergeEditorInput, MergeEditorInputJSON, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; export class MergeEditorSerializer implements IEditorSerializer { @@ -22,7 +22,13 @@ export class MergeEditorSerializer implements IEditorSerializer { deserialize(instantiationService: IInstantiationService, raw: string): MergeEditorInput | undefined { try { const data = parse(raw); - return instantiationService.createInstance(MergeEditorInput, data.anchestor, data.inputOne, data.inputTwo, data.result); + return instantiationService.createInstance( + MergeEditorInput, + data.anchestor, + new MergeEditorInputData(data.inputOne.uri, data.inputOne.detail, data.inputOne.description), + new MergeEditorInputData(data.inputTwo.uri, data.inputTwo.detail, data.inputTwo.description), + data.result + ); } catch (err) { onUnexpectedError(err); return undefined; From 70b7af01e8a639681b63cb32a5cabc5c80428120 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 21:57:18 +0200 Subject: [PATCH 058/350] wire up policy service --- .../sharedProcess/sharedProcessMain.ts | 8 +- src/vs/code/electron-main/app.ts | 7 ++ src/vs/code/electron-main/main.ts | 20 +++-- src/vs/code/node/cliProcessMain.ts | 9 ++- .../common/configurationService.ts | 8 +- .../configuration/common/configurations.ts | 12 +-- .../test/common/configurationService.test.ts | 27 +++---- .../test/common/policyConfiguration.test.ts | 7 +- .../policy/common/filePolicyService.ts | 2 + src/vs/platform/policy/common/policy.ts | 8 ++ .../policy/node/vscode-policy-watcher.d.ts | 30 -------- .../policy/node/windowsPolicyService.ts | 73 ++++++++++--------- .../test/common/userDataSyncClient.ts | 3 +- .../node/remoteExtensionHostAgentCli.ts | 3 +- src/vs/server/node/serverServices.ts | 3 +- src/vs/workbench/browser/web.main.ts | 3 +- .../electron-sandbox/desktop.main.ts | 20 ++++- .../browser/configurationService.ts | 4 +- .../configurationEditingService.test.ts | 3 +- .../test/browser/configurationService.test.ts | 19 ++--- 20 files changed, 146 insertions(+), 123 deletions(-) delete mode 100644 src/vs/platform/policy/node/vscode-policy-watcher.d.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index aa4c7ad67e7..6b07e7c7ddd 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -100,6 +100,8 @@ import { InspectProfilingService as V8InspectProfilingService } from 'vs/platfor import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profiling'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; +import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; class SharedProcessMain extends Disposable { @@ -180,6 +182,10 @@ class SharedProcessMain extends Disposable { const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); services.set(IMainProcessService, mainProcessService); + // Policies + const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + services.set(IPolicyService, policyService); + // Environment const environmentService = new SharedProcessEnvironmentService(this.configuration.args, productService); services.set(INativeEnvironmentService, environmentService); @@ -222,7 +228,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4bc4a432ceb..64085aad356 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -99,6 +99,8 @@ import { IWorkspacesHistoryMainService, WorkspacesHistoryMainService } from 'vs/ import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IWorkspacesManagementMainService, WorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-main/credentialsMainService'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { PolicyChannel } from 'vs/platform/policy/common/policyIpc'; /** * The main VS Code application. There will only ever be one instance, @@ -695,6 +697,11 @@ export class CodeApplication extends Disposable { const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('diagnostics', diagnosticsChannel); + // Policies (main & shared process) + const policyChannel = new PolicyChannel(accessor.get(IPolicyService)); + mainProcessElectronServer.registerChannel('policy', policyChannel); + sharedProcessClient.then(client => client.registerChannel('policy', policyChannel)); + // Local Files const diskFileSystemProvider = this.fileService.getProvider(Schemas.file); assertType(diskFileSystemProvider instanceof DiskFileSystemProvider); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7b95aaad618..f31b96ebece 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -62,6 +62,8 @@ import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; /** * The main VS Code entry point. @@ -89,13 +91,13 @@ class CodeMain { setUnexpectedErrorHandler(err => console.error(err)); // Create services - const [instantiationService, instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService] = this.createServices(); + const [instantiationService, instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService] = this.createServices(); try { // Init services try { - await this.initServices(environmentMainService, configurationService, stateMainService); + await this.initServices(environmentMainService, policyService, configurationService, stateMainService); } catch (error) { // Show a dialog for errors that can be resolved by the user @@ -137,7 +139,7 @@ class CodeMain { } } - private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateMainService, BufferLogService, IProductService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, IPolicyService, ConfigurationService, StateMainService, BufferLogService, IProductService] { const services = new ServiceCollection(); // Product @@ -166,8 +168,12 @@ class CodeMain { // Logger services.set(ILoggerService, new LoggerService(logService, fileService)); + // Policy + const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + services.set(IPolicyService, policyService); + // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, environmentMainService, logService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService); services.set(IConfigurationService, configurationService); // Lifecycle @@ -192,7 +198,7 @@ class CodeMain { // Protocol services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -212,7 +218,7 @@ class CodeMain { return instanceEnvironment; } - private initServices(environmentMainService: IEnvironmentMainService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { + private initServices(environmentMainService: IEnvironmentMainService, policyService: IPolicyService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { return Promises.settled([ // Environment service (paths) @@ -226,6 +232,8 @@ class CodeMain { environmentMainService.backupHome ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), + policyService.initialize(), + // Configuration service configurationService.initialize(), diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 6ae96fc2047..2217a4b70b1 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -11,6 +11,7 @@ import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/err import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join } from 'vs/base/common/path'; +import { isWindows } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -39,6 +40,8 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -128,8 +131,12 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // Policy + const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + services.set(IPolicyService, policyService); + // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index b3ff50a6d0c..3d0422d38c1 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -11,9 +11,8 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -31,12 +30,11 @@ export class ConfigurationService extends Disposable implements IConfigurationSe constructor( private readonly settingsResource: URI, fileService: IFileService, - environmentService: IEnvironmentService, - logService: ILogService, + policyService: IPolicyService, ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index dad918f94e1..dc2fd2e6079 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -11,11 +11,8 @@ import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; -import { IPolicyService, NullPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -85,19 +82,14 @@ export class PolicyConfiguration extends Disposable { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; - private readonly policyService: IPolicyService; - private _configurationModel = new ConfigurationModel(); get configurationModel() { return this._configurationModel; } constructor( private readonly defaultConfiguration: DefaultConfiguration, - fileService: IFileService, - environmentService: IEnvironmentService, - logService: ILogService + @IPolicyService private readonly policyService: IPolicyService ) { super(); - this.policyService = environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); } async initialize(): Promise { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 04635ed013c..d49af5f41f6 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -12,19 +12,17 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; suite('ConfigurationService', () => { let fileService: IFileService; - let environmentService: IEnvironmentService; let settingsResource: URI; const disposables: DisposableStore = new DisposableStore(); @@ -33,14 +31,13 @@ suite('ConfigurationService', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); settingsResource = URI.file('settings.json'); - environmentService = new TestInstantiationService().mock(IEnvironmentService) as IEnvironmentService; }); teardown(() => disposables.clear()); test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -53,7 +50,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -72,7 +69,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -82,7 +79,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -91,7 +88,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -104,7 +101,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -120,7 +117,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -159,7 +156,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, environmentService, new NullLogService())); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -167,7 +164,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); setting = testObject.getValue(); @@ -195,7 +192,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -230,7 +227,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, environmentService, new NullLogService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 44a8c34894a..784334ffa7c 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -8,7 +8,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; @@ -17,11 +16,14 @@ import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platf import { Registry } from 'vs/platform/registry/common/platform'; import { VSBuffer } from 'vs/base/common/buffer'; import { deepClone } from 'vs/base/common/objects'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; suite('PolicyConfiguration', () => { let testObject: PolicyConfiguration; let fileService: IFileService; + let policyService: IPolicyService; const policyFile = URI.file('policyFile').with({ scheme: 'vscode-tests' }); const disposables = new DisposableStore(); const policyConfigurationNode: IConfigurationNode = { @@ -62,7 +64,8 @@ suite('PolicyConfiguration', () => { fileService = disposables.add(new FileService(new NullLogService())); const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); - testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, fileService, { policyFile } as IEnvironmentService, new NullLogService())); + policyService = new FilePolicyService(policyFile, fileService, new NullLogService()); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService)); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 44d3105dad3..c93a15b365f 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -27,6 +27,8 @@ function keysDiff(a: Map, b: Map): string[] { export class FilePolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + private policies: Policies = new Map(); private readonly _onDidChange = new Emitter(); diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 4febe66904f..3d7af47c262 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -4,18 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; export type PolicyValue = string | boolean | number; export type Policies = Map; +export const IPolicyService = createDecorator('policy'); + export interface IPolicyService { + readonly _serviceBrand: undefined; + readonly onDidChange: Event; initialize(): Promise; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { + readonly _serviceBrand: undefined; readonly onDidChange = Event.None; async initialize() { } getPolicyValue() { return undefined; } @@ -23,6 +29,8 @@ export class NullPolicyService implements IPolicyService { export class MultiPolicyService implements IPolicyService { + readonly _serviceBrand: undefined; + readonly onDidChange: Event; constructor(private policyServices: readonly IPolicyService[]) { diff --git a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts b/src/vs/platform/policy/node/vscode-policy-watcher.d.ts deleted file mode 100644 index c305f178822..00000000000 --- a/src/vs/platform/policy/node/vscode-policy-watcher.d.ts +++ /dev/null @@ -1,30 +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-policy-watcher' { - interface Watcher { - dispose(): void; - } - - interface Policies { - [policyName: string]: { - type: string - }; - } - - interface PolicyValues { - [policyName: string]: string | number | boolean; - } - - function createWatcher( - productName: string, - policies: Policies, - onDidChange: (update: PolicyValues) => void - ): Watcher; - - namespace createWatcher { } - - export = createWatcher; -} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 64e7b8bb7d6..77550a47c3b 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -9,12 +9,15 @@ import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/p import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import * as createWatcher from 'vscode-policy-watcher'; +import { createWatcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; export class WindowsPolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + private policies: Policies = new Map(); + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -26,51 +29,51 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { } initialize(): Promise { - return new Promise(c => { - if (!this.productService.win32RegValueName) { - return; - } + if (!this.init) { + this.init = new Promise(c => { + if (!this.productService.win32RegValueName) { + return; + } - const policies: IStringDictionary<{ type: string }> = {}; - const configRegistry = Registry.as(Extensions.Configuration); + const policies: IStringDictionary<{ type: 'string' | 'number' }> = {}; + const configRegistry = Registry.as(Extensions.Configuration); - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + policies[policy.name] = { type: config.type }; } - - policies[policy.name] = { type: config.type }; } } } - } - let first = true; + let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { - for (const key in update) { - this.policies.set(key, update[key]); - } + this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + for (const key in update) { + this.policies.set(key, update[key]!); + } - if (first) { - first = false; - c(); - } else { - this._onDidChange.fire(Object.keys(update)); - } - })); - }); - } + if (first) { + first = false; + c(); + } else { + this._onDidChange.fire(Object.keys(update)); + } + })); + }); + } - async refresh(): Promise { - // NOOP + return this.init; } getPolicyValue(name: PolicyName): PolicyValue | undefined { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 5d3ba1f21bd..2d888a524c9 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -41,6 +41,7 @@ import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/pl import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; export class UserDataSyncClient extends Disposable { @@ -86,7 +87,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 170fbafa06a..bfd5928609d 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -42,6 +42,7 @@ import { buildHelpMessage, buildVersionMessage, OptionDescriptions } from 'vs/pl import { isWindows } from 'vs/base/common/platform'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; class CliMain extends Disposable { @@ -91,7 +92,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, environmentService, logService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a443d3bc84c..c3ae3e66423 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -70,6 +70,7 @@ import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/workbench/services/remote/co import { ExtensionHostStatusService, IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerService'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; const eventPrefix = 'monacoworkbench'; @@ -107,7 +108,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, environmentService, logService); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService()); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index b8dd198abc8..b8a397edac4 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -73,6 +73,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel'; import { dirname, joinPath } from 'vs/base/common/resources'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; export class BrowserMain extends Disposable { @@ -440,7 +441,7 @@ export class BrowserMain extends Disposable { private async createWorkspaceService(payload: IAnyWorkspaceIdentifier, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData, Schemas.tmp] /* Cache all non native resources */, environmentService, fileService); - const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const workspaceService = new WorkspaceService({ remoteAuthority: this.configuration.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService, new NullPolicyService()); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 98b95174864..42ada97cea0 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -50,6 +50,8 @@ import { isCI, isMacintosh } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class DesktopMain extends Disposable { @@ -155,6 +157,10 @@ export class DesktopMain extends Disposable { const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId)); serviceCollection.set(IMainProcessService, mainProcessService); + // Policies + const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + serviceCollection.set(IPolicyService, policyService); + // Product const productService: IProductService = { _serviceBrand: undefined, ...product }; serviceCollection.set(IProductService, productService); @@ -247,7 +253,7 @@ export class DesktopMain extends Disposable { const payload = this.resolveWorkspaceInitializationPayload(environmentService); const [configurationService, storageService] = await Promise.all([ - this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { + this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService, policyService).then(service => { // Workspace serviceCollection.set(IWorkspaceContextService, service); @@ -325,9 +331,17 @@ export class DesktopMain extends Disposable { return workspaceInitializationPayload; } - private async createWorkspaceService(payload: IAnyWorkspaceIdentifier, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise { + private async createWorkspaceService( + payload: IAnyWorkspaceIdentifier, + environmentService: INativeWorkbenchEnvironmentService, + fileService: FileService, + remoteAgentService: IRemoteAgentService, + uriIdentityService: IUriIdentityService, + logService: ILogService, + policyService: IPolicyService + ): Promise { const configurationCache = new ConfigurationCache([Schemas.file, Schemas.vscodeUserData] /* Cache all non native resources */, environmentService, fileService); - const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService); + const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService, policyService); try { await workspaceService.initialize(payload); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 0db722acbc1..b31cf92ae7c 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -40,6 +40,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -104,6 +105,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService, + policyService: IPolicyService ) { super(); @@ -112,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, fileService, environmentService, logService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 2fba810064f..16e6ddde07b 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -40,6 +40,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -107,7 +108,7 @@ suite('ConfigurationEditingService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await workspaceService.initialize({ id: hash(workspaceFolder.toString()).toString(16), uri: workspaceFolder diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 7f980fd1d1d..794400f725b 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -44,6 +44,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; +import { NullPolicyService } from 'vs/platform/policy/common/policy'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -77,7 +78,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -117,7 +118,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -137,7 +138,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -184,7 +185,7 @@ suite('WorkspaceContextService - Workspace', () => { const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -242,7 +243,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -485,7 +486,7 @@ suite('WorkspaceService - Initialization', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -743,7 +744,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -1440,7 +1441,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -2101,7 +2102,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; - testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); From 9cfa78035f4faa2661b3faa233c3080c045e1c73 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 16 May 2022 13:00:51 -0700 Subject: [PATCH 059/350] remove enum --- src/vs/platform/terminal/common/terminal.ts | 9 --------- .../terminal/common/xterm/shellIntegrationAddon.ts | 6 +++--- .../contrib/terminal/browser/terminalInstance.ts | 4 ++-- .../contrib/terminal/browser/terminalProcessManager.ts | 4 ++-- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index dda984c4b5d..ec3d59f59d7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -241,15 +241,6 @@ export interface IFixedTerminalDimensions { */ rows?: number; } - - -export const enum ShellIntegrationTelemetry { - ActivationTimeout = 'terminal/shellIntegrationActivationTimeout', - FailureProcessExit = 'terminal/shellIntegrationFailureProcessExit', - FailureCustomArgs = 'terminal/shellIntegrationActivationFailureCustomArgs', - Success = 'terminal/shellIntegrationActivationSucceeded' -} - export interface IPtyHostController { readonly onPtyHostExit?: Event; readonly onPtyHostStart?: Event; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 0b809d33db4..0d48530b611 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IShellIntegration, ShellIntegrationTelemetry } from 'vs/platform/terminal/common/terminal'; +import { IShellIntegration } from 'vs/platform/terminal/common/terminal'; import { Disposable } from 'vs/base/common/lifecycle'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; @@ -144,7 +144,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _handleVSCodeSequence(data: string): boolean { const didHandle = this._doHandleVSCodeSequence(data); if (!this._hasUpdatedTelemetry && didHandle) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.Success); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationSucceeded'); this._hasUpdatedTelemetry = true; clearTimeout(this._activationTimeout); } @@ -229,7 +229,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { this._activationTimeout = setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.ActivationTimeout); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationTimeout'); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } this._hasUpdatedTelemetry = true; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ce50ff14aad..1a677dc5f27 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -42,7 +42,7 @@ import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notif import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ProcessPropertyType, ShellIntegrationTelemetry, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -1634,7 +1634,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } if (failedShellIntegrationInjection) { - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.FailureProcessExit); + this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailureProcessExit'); } // First onExit to consumers, this can happen after the terminal has already been disposed. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 8ad3c42cadd..7ae7dc3c4fc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -21,7 +21,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { EnvironmentVariableInfoChangesActive, EnvironmentVariableInfoStale } from 'vs/workbench/contrib/terminal/browser/environmentVariableInfo'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IEnvironmentVariableInfo, IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, ITerminalDimensions, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, ITerminalProcessOptions, TerminalSettingId, ShellIntegrationTelemetry } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, ITerminalDimensions, IProcessReadyEvent, IProcessProperty, ProcessPropertyType, IProcessPropertyMap, ITerminalProcessOptions, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; import { localize } from 'vs/nls'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; @@ -335,7 +335,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._hasChildProcesses = value; break; case ProcessPropertyType.FailedShellIntegrationActivation: - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>(ShellIntegrationTelemetry.FailureCustomArgs); + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationFailureCustomArgs'); break; } this._onDidChangeProperty.fire({ type, value }); From 0af4de3e4139fafb9e8baca6696279180b8e5499 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 22:22:37 +0200 Subject: [PATCH 060/350] works! --- .../platform/policy/common/filePolicyService.ts | 8 ++++---- src/vs/platform/policy/common/policy.ts | 3 +-- src/vs/platform/policy/common/policyIpc.ts | 15 ++++++++------- .../platform/policy/node/windowsPolicyService.ts | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index c93a15b365f..1f563b4fab1 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -11,7 +11,7 @@ import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -29,7 +29,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private policies = new Map(); private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -56,8 +56,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { await this.doRefresh(); } - private async read(): Promise { - const policies: Policies = new Map(); + private async read(): Promise> { + const policies = new Map(); try { const content = await this.fileService.readFile(this.file); diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 3d7af47c262..5071a94f3ed 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -7,8 +7,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; -export type PolicyValue = string | boolean | number; -export type Policies = Map; +export type PolicyValue = string | boolean; export const IPolicyService = createDecorator('policy'); diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 00a0913f693..b3efcfa79d2 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -6,7 +6,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; + +type Policies = { [name: PolicyName]: PolicyValue | undefined }; export class PolicyChannel implements IServerChannel { @@ -18,10 +20,7 @@ export class PolicyChannel implements IServerChannel { switch (event) { case 'onDidChange': return Event.map( this.service.onDidChange, - names => new Map( - names - .map(name => [name, this.service.getPolicyValue(name)]) - .filter(pair => pair[1] !== undefined) as [PolicyName, PolicyValue][]), + names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) }), {}), this.disposables ); } @@ -46,14 +45,16 @@ export class PolicyChannelClient implements IPolicyService { declare readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private policies = new Map(); private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; constructor(private readonly channel: IChannel) { this.channel.listen('onDidChange')(policies => { - for (const [name, value] of policies) { + for (const name in policies) { + const value = policies[name]; + if (value === undefined) { this.policies.delete(name); } else { diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 77550a47c3b..6a324539ed4 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, Policies, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -16,7 +16,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; - private policies: Policies = new Map(); + private readonly policies = new Map(); private init: Promise | undefined; private readonly _onDidChange = new Emitter(); @@ -58,7 +58,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => () => { + this._register(createWatcher(this.productService.win32RegValueName, policies, update => { for (const key in update) { this.policies.set(key, update[key]!); } From fe9f84127a9730f48ca4444abcaa01b27e01b99e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 16 May 2022 22:35:23 +0200 Subject: [PATCH 061/350] figure out proper init --- src/vs/platform/policy/common/filePolicyService.ts | 3 ++- src/vs/platform/policy/common/policy.ts | 7 ++++--- src/vs/platform/policy/common/policyIpc.ts | 12 ++++++++++-- src/vs/platform/policy/node/windowsPolicyService.ts | 7 ++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 96974697594..f46b55cb70f 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -48,8 +48,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async initialize(): Promise { + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { await this.refresh(); + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } private async read(): Promise> { diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 5071a94f3ed..6446f06e9ac 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -15,14 +15,14 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - initialize(): Promise; + initialize(): Promise<{ [name: PolicyName]: PolicyValue }>; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange = Event.None; - async initialize() { } + async initialize() { return {}; } getPolicyValue() { return undefined; } } @@ -37,7 +37,8 @@ export class MultiPolicyService implements IPolicyService { } async initialize() { - await Promise.all(this.policyServices.map(p => p.initialize())); + const result = await Promise.all(this.policyServices.map(p => p.initialize())); + return result.reduce((r, o) => ({ ...r, ...o }), {}); } getPolicyValue(name: PolicyName) { diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index b3efcfa79d2..c163321d085 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -61,11 +61,19 @@ export class PolicyChannelClient implements IPolicyService { this.policies.set(name, value); } } + + this._onDidChange.fire(Object.keys(policies)); }); } - initialize(): Promise { - return this.channel.call('initialize'); + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize'); + + for (const name in result) { + this.policies.set(name, result[name]); + } + + return result; } getPolicyValue(name: PolicyName): PolicyValue | undefined { diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 6a324539ed4..7e943f1c5ff 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -11,13 +11,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { createWatcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; +import { Iterable } from 'vs/base/common/iterator'; export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise | undefined; + private init: Promise<{ [name: PolicyName]: PolicyValue }> | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -28,7 +29,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { super(); } - initialize(): Promise { + initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { if (!this.init) { this.init = new Promise(c => { if (!this.productService.win32RegValueName) { @@ -65,7 +66,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(); + c(Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {})); } else { this._onDidChange.fire(Object.keys(update)); } From 07f313f44aaee8c550a3b8f24047a26552118ef6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 16 May 2022 13:47:32 -0700 Subject: [PATCH 062/350] fix compile error --- src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 0d48530b611..c02ea09dbee 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -125,7 +125,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _terminal?: Terminal; readonly capabilities = new TerminalCapabilityStore(); private _hasUpdatedTelemetry: boolean = false; - private _activationTimeout!: NodeJS.Timeout; + private _activationTimeout!: any; constructor( @ILogService private readonly _logService: ILogService, From 2c0fa7052dcab8a40b611617782bc24014bdf452 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 16 May 2022 15:29:11 -0700 Subject: [PATCH 063/350] Update src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- .../platform/terminal/common/xterm/shellIntegrationAddon.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index c02ea09dbee..8af901f1680 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -146,7 +146,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati if (!this._hasUpdatedTelemetry && didHandle) { this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationSucceeded'); this._hasUpdatedTelemetry = true; - clearTimeout(this._activationTimeout); + if (this._activationTimeout !== undefined) { + clearTimeout(this._activationTimeout); + this._activationTimeout = undefined; + } } return didHandle; } From 063486aafd924efa02eaae26eb7394e317f9e092 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 16 May 2022 15:29:29 -0700 Subject: [PATCH 064/350] revert more changes --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 4 ++-- .../contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 10 ++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index a2b290be7b0..6d1494b10d3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -858,9 +858,9 @@ export interface IXtermTerminal { readonly commandTracker: ICommandTracker; /** - * Reports the status of shell integration and fires events relating to it when enabled. + * Reports the status of shell integration and fires events relating to it. */ - readonly shellIntegration: IShellIntegration | undefined; + readonly shellIntegration: IShellIntegration; readonly onDidChangeSelection: Event; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1a677dc5f27..f845f8ddc33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -725,7 +725,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._areLinksReady = true; this._onLinksReady.fire(this); }); - this._processManager.onRestoreCommands(e => this.xterm?.shellIntegration?.deserialize(e)); + this._processManager.onRestoreCommands(e => this.xterm?.shellIntegration.deserialize(e)); this._loadTypeAheadAddon(xterm); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 7f6faec3f24..3704797e24e 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -618,12 +618,10 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._decorationAddon.dispose(); this._decorationAddon = undefined; } - } else { - this._shellIntegrationAddon?.dispose(); - if (this._decorationAddon) { - this._decorationAddon.dispose(); - this._decorationAddon = undefined; - } + } + if (this._decorationAddon) { + this._decorationAddon.dispose(); + this._decorationAddon = undefined; } } } From 77aebb7f5c630c1ff6aab04a78ceb5cb15529d33 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 16 May 2022 15:35:18 -0700 Subject: [PATCH 065/350] Add bot comment on issues mentioning old vscode version (#149669) --- .github/workflows/on-open.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index 98a739f8c7f..3c6617101e7 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -31,6 +31,7 @@ jobs: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} labelColor: "006b75" labelDescription: Issues found in a recent release of VS Code + oldVersionMessage: "Thanks for creating this issue! It looks like you may be using an old version of VS Code, the latest stable release is {currentVersion}. Please try upgrading to the latest version and checking whether this issue remains.\n\nHappy Coding!" days: 5 - name: Run Clipboard Labeler From fc31613e8a788e08b627340cce0ffdb079811172 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 16 May 2022 15:35:27 -0700 Subject: [PATCH 066/350] clean up --- src/vs/platform/terminal/common/terminal.ts | 1 + .../terminal/common/xterm/shellIntegrationAddon.ts | 8 ++++---- src/vs/platform/terminal/node/ptyService.ts | 2 +- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index ec3d59f59d7..91b3244bd3f 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -241,6 +241,7 @@ export interface IFixedTerminalDimensions { */ rows?: number; } + export interface IPtyHostController { readonly onPtyHostExit?: Event; readonly onPtyHostStart?: Event; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 8af901f1680..c28607fc175 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -125,11 +125,11 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _terminal?: Terminal; readonly capabilities = new TerminalCapabilityStore(); private _hasUpdatedTelemetry: boolean = false; - private _activationTimeout!: any; + private _activationTimeout: number | undefined; constructor( - @ILogService private readonly _logService: ILogService, - private readonly _telemetryService?: ITelemetryService + private readonly _telemetryService: ITelemetryService | undefined, + @ILogService private readonly _logService: ILogService ) { super(); } @@ -230,7 +230,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { - this._activationTimeout = setTimeout(() => { + this._activationTimeout = window.setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationTimeout'); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 0b4301c23e6..a3191a3230c 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -742,7 +742,7 @@ class XtermSerializer implements ITerminalSerializer { this._xterm.writeln(reviveBuffer); } this.setUnicodeVersion(unicodeVersion); - this._shellIntegrationAddon = new ShellIntegrationAddon(logService); + this._shellIntegrationAddon = new ShellIntegrationAddon(undefined, logService); this._xterm.loadAddon(this._shellIntegrationAddon); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 3704797e24e..cf6e1918dce 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -175,7 +175,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); - this._shellIntegrationAddon = new ShellIntegrationAddon(this._logService, this._telemetryService); + this._shellIntegrationAddon = new ShellIntegrationAddon(this._telemetryService, this._logService); this.raw.loadAddon(this._shellIntegrationAddon); this._updateShellIntegrationAddons(); } From dc85061f320571faf371aa685becb240868d137c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 16 May 2022 16:08:39 -0700 Subject: [PATCH 067/350] Update src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- .../workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index cf6e1918dce..509db6cc5a6 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -175,7 +175,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); - this._shellIntegrationAddon = new ShellIntegrationAddon(this._telemetryService, this._logService); + this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, this._telemetryService); this.raw.loadAddon(this._shellIntegrationAddon); this._updateShellIntegrationAddons(); } From d71f6ec0d9de40bc322e2de76263b5fa8069c0d2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 16 May 2022 17:30:39 -0700 Subject: [PATCH 068/350] Update markdown diagnostics when linked files change (#149672) For #146303 This PR updates the markdown diagnostic reporter to watch linked to files. If one of these linked to files is created or deleted, we recompute the diagnostics for all markdown files that linked to it --- .../src/languageFeatures/diagnostics.ts | 138 +++++++++++++++--- .../src/test/diagnostic.test.ts | 18 ++- 2 files changed, 131 insertions(+), 25 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index fd11cce6531..f53a83d670e 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -12,7 +12,7 @@ import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { Limiter } from '../util/limiter'; import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider'; +import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider'; import { tryFindMdDocumentForLink } from './references'; const localize = nls.loadMessageBundle(); @@ -118,6 +118,94 @@ class InflightDiagnosticRequests { } } +class LinkWatcher extends Disposable { + + private readonly _onDidChangeLinkedToFile = this._register(new vscode.EventEmitter>); + /** + * Event fired with a list of document uri when one of the links in the document changes + */ + public readonly onDidChangeLinkedToFile = this._onDidChangeLinkedToFile.event; + + private readonly _watchers = new Map; + }>(); + + override dispose() { + super.dispose(); + + for (const entry of this._watchers.values()) { + entry.watcher.dispose(); + } + this._watchers.clear(); + } + + /** + * Set the known links in a markdown document, adding and removing file watchers as needed + */ + updateLinksForDocument(document: vscode.Uri, links: readonly MdLink[]) { + const linkedToResource = new Set( + links + .filter(link => link.href.kind === 'internal') + .map(link => (link.href as InternalHref).path)); + + // First decrement watcher counter for previous document state + for (const entry of this._watchers.values()) { + entry.documents.delete(document.toString()); + } + + // Then create/update watchers for new document state + for (const path of linkedToResource) { + let entry = this._watchers.get(path.toString()); + if (!entry) { + entry = { + watcher: this.startWatching(path), + documents: new Map(), + }; + this._watchers.set(path.toString(), entry); + } + + entry.documents.set(document.toString(), document); + } + + // Finally clean up watchers for links that are no longer are referenced anywhere + for (const [key, value] of this._watchers) { + if (value.documents.size === 0) { + value.watcher.dispose(); + this._watchers.delete(key); + } + } + } + + deleteDocument(resource: vscode.Uri) { + this.updateLinksForDocument(resource, []); + } + + private startWatching(path: vscode.Uri): vscode.Disposable { + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path, '*'), false, true, false); + const handler = (resource: vscode.Uri) => this.onLinkedResourceChanged(resource); + return vscode.Disposable.from( + watcher, + watcher.onDidDelete(handler), + watcher.onDidCreate(handler), + ); + } + + private onLinkedResourceChanged(resource: vscode.Uri) { + const entry = this._watchers.get(resource.toString()); + if (entry) { + this._onDidChangeLinkedToFile.fire(entry.documents.values()); + } + } +} + export class DiagnosticManager extends Disposable { private readonly collection: vscode.DiagnosticCollection; @@ -126,6 +214,8 @@ export class DiagnosticManager extends Disposable { private readonly pendingDiagnostics = new Set(); private readonly inFlightDiagnostics = this._register(new InflightDiagnosticRequests()); + private readonly linkWatcher = this._register(new LinkWatcher()); + constructor( private readonly computer: DiagnosticComputer, private readonly configuration: DiagnosticConfiguration, @@ -148,10 +238,20 @@ export class DiagnosticManager extends Disposable { this.triggerDiagnostics(e.document); })); - this._register(vscode.workspace.onDidCloseTextDocument(doc => { - this.pendingDiagnostics.delete(doc.uri); - this.inFlightDiagnostics.cancel(doc.uri); - this.collection.delete(doc.uri); + this._register(vscode.workspace.onDidCloseTextDocument(({ uri }) => { + this.pendingDiagnostics.delete(uri); + this.inFlightDiagnostics.cancel(uri); + this.linkWatcher.deleteDocument(uri); + this.collection.delete(uri); + })); + + this._register(this.linkWatcher.onDidChangeLinkedToFile(changedDocuments => { + for (const resource of changedDocuments) { + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString()); + if (doc) { + this.triggerDiagnostics(doc); + } + } })); this.rebuild(); @@ -162,12 +262,12 @@ export class DiagnosticManager extends Disposable { this.pendingDiagnostics.clear(); } - public async getDiagnostics(doc: SkinnyTextDocument, token: vscode.CancellationToken): Promise { + public async recomputeDiagnosticState(doc: SkinnyTextDocument, token: vscode.CancellationToken): Promise<{ diagnostics: readonly vscode.Diagnostic[]; links: readonly MdLink[]; config: DiagnosticOptions }> { const config = this.configuration.getOptions(doc.uri); if (!config.enabled) { - return []; + return { diagnostics: [], links: [], config }; } - return this.computer.getDiagnostics(doc, config, token); + return { ...await this.computer.getDiagnostics(doc, config, token), config }; } private async recomputePendingDiagnostics(): Promise { @@ -178,8 +278,9 @@ export class DiagnosticManager extends Disposable { const doc = vscode.workspace.textDocuments.find(doc => doc.uri.fsPath === resource.fsPath); if (doc) { this.inFlightDiagnostics.trigger(doc.uri, async (token) => { - const diagnostics = await this.getDiagnostics(doc, token); - this.collection.set(doc.uri, diagnostics); + const state = await this.recomputeDiagnosticState(doc, token); + this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFilePaths ? state.links : []); + this.collection.set(doc.uri, state.diagnostics); }); } } @@ -269,17 +370,20 @@ export class DiagnosticComputer { private readonly linkProvider: MdLinkProvider, ) { } - public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise { + public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: MdLink[] }> { const links = await this.linkProvider.getAllLinks(doc, token); if (token.isCancellationRequested) { - return []; + return { links, diagnostics: [] }; } - return (await Promise.all([ - this.validateFileLinks(doc, options, links, token), - Array.from(this.validateReferenceLinks(options, links)), - this.validateOwnHeaderLinks(doc, options, links, token), - ])).flat(); + return { + links, + diagnostics: (await Promise.all([ + this.validateFileLinks(doc, options, links, token), + Array.from(this.validateReferenceLinks(options, links)), + this.validateOwnHeaderLinks(doc, options, links, token), + ])).flat() + }; } private async validateOwnHeaderLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise { diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index ce6357c65a6..a9f54978b7d 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -16,16 +16,18 @@ import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; import { assertRangeEqual, joinLines, workspacePath } from './util'; -function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: MdWorkspaceContents) { +async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: MdWorkspaceContents): Promise { const engine = createNewMarkdownEngine(); const linkProvider = new MdLinkProvider(engine); const computer = new DiagnosticComputer(engine, workspaceContents, linkProvider); - return computer.getDiagnostics(doc, { - enabled: true, - validateFilePaths: DiagnosticLevel.warning, - validateOwnHeaders: DiagnosticLevel.warning, - validateReferences: DiagnosticLevel.warning, - }, noopToken); + return ( + await computer.getDiagnostics(doc, { + enabled: true, + validateFilePaths: DiagnosticLevel.warning, + validateOwnHeaders: DiagnosticLevel.warning, + validateReferences: DiagnosticLevel.warning, + }, noopToken) + ).diagnostics; } function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration()) { @@ -155,7 +157,7 @@ suite('markdown: Diagnostics', () => { )); const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(false)); - const diagnostics = await manager.getDiagnostics(doc1, noopToken); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); assert.deepStrictEqual(diagnostics.length, 0); }); From c2b064538b5016371522f3e4e4a56d23924ec8ce Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 16 May 2022 17:35:16 -0700 Subject: [PATCH 069/350] Don't detect links in markdown code blocks (#149680) Fixes #149678 --- .../languageFeatures/documentLinkProvider.ts | 87 ++++++++++--------- .../src/test/documentLinkProvider.test.ts | 43 +++++++++ 2 files changed, 88 insertions(+), 42 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts index 56195d7a03c..2a239a04491 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts @@ -187,35 +187,38 @@ const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^> const inlineCodePattern = /(?:^|[^`])(`+)(?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1(?:$|[^`])/gm; -interface CodeInDocument { - /** - * code blocks and fences each represented by [line_start,line_end). - */ - readonly multiline: ReadonlyArray<[number, number]>; +class NoLinkRanges { + public static async compute(document: SkinnyTextDocument, engine: MarkdownEngine): Promise { + const tokens = await engine.parse(document); + const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map).map(t => t.map) as [number, number][]; - /** - * inline code spans each represented by {@link vscode.Range}. - */ - readonly inline: readonly vscode.Range[]; + const text = document.getText(); + const inline = [...text.matchAll(inlineCodePattern)].map(match => { + const start = match.index || 0; + return new vscode.Range(document.positionAt(start), document.positionAt(start + match[0].length)); + }); + + return new NoLinkRanges(multiline, inline); + } + + private constructor( + /** + * code blocks and fences each represented by [line_start,line_end). + */ + public readonly multiline: ReadonlyArray<[number, number]>, + + /** + * Inline code spans where links should not be detected + */ + public readonly inline: readonly vscode.Range[] + ) { } + + contains(range: vscode.Range): boolean { + return this.multiline.some(interval => range.start.line >= interval[0] && range.start.line < interval[1]) || + this.inline.some(position => position.intersection(range)); + } } -async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): Promise { - const tokens = await engine.parse(document); - const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence') && !!t.map).map(t => t.map) as [number, number][]; - - const text = document.getText(); - const inline = [...text.matchAll(inlineCodePattern)].map(match => { - const start = match.index || 0; - return new vscode.Range(document.positionAt(start), document.positionAt(start + match[0].length)); - }); - - return { multiline, inline }; -} - -function isLinkInsideCode(code: CodeInDocument, linkHrefRange: vscode.Range) { - return code.multiline.some(interval => linkHrefRange.start.line >= interval[0] && linkHrefRange.start.line < interval[1]) || - code.inline.some(position => position.intersection(linkHrefRange)); -} export class MdLinkProvider implements vscode.DocumentLinkProvider { @@ -262,35 +265,35 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } public async getAllLinks(document: SkinnyTextDocument, token: vscode.CancellationToken): Promise { - const codeInDocument = await findCode(document, this.engine); + const noLinkRanges = await NoLinkRanges.compute(document, this.engine); if (token.isCancellationRequested) { return []; } return Array.from([ - ...this.getInlineLinks(document, codeInDocument), - ...this.getReferenceLinks(document, codeInDocument), - ...this.getLinkDefinitions2(document, codeInDocument), - ...this.getAutoLinks(document, codeInDocument), + ...this.getInlineLinks(document, noLinkRanges), + ...this.getReferenceLinks(document, noLinkRanges), + ...this.getLinkDefinitions2(document, noLinkRanges), + ...this.getAutoLinks(document, noLinkRanges), ]); } - private *getInlineLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { + private *getInlineLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(linkPattern)) { const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); - if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData.source.hrefRange)) { + if (matchImageData && !noLinkRanges.contains(matchImageData.source.hrefRange)) { yield matchImageData; } const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index); - if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData.source.hrefRange)) { + if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange)) { yield matchLinkData; } } } - private *getAutoLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { + private *getAutoLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(autoLinkPattern)) { @@ -301,7 +304,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { const linkStart = document.positionAt(offset); const linkEnd = document.positionAt(offset + link.length); const hrefRange = new vscode.Range(linkStart, linkEnd); - if (isLinkInsideCode(codeInDocument, hrefRange)) { + if (noLinkRanges.contains(hrefRange)) { continue; } yield { @@ -318,7 +321,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } } - private *getReferenceLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { + private *getReferenceLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(referenceLinkPattern)) { let linkStart: vscode.Position; @@ -339,7 +342,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } const hrefRange = new vscode.Range(linkStart, linkEnd); - if (isLinkInsideCode(codeInDocument, hrefRange)) { + if (noLinkRanges.contains(hrefRange)) { continue; } @@ -360,11 +363,11 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { } public async getLinkDefinitions(document: SkinnyTextDocument): Promise> { - const codeInDocument = await findCode(document, this.engine); - return this.getLinkDefinitions2(document, codeInDocument); + const noLinkRanges = await NoLinkRanges.compute(document, this.engine); + return this.getLinkDefinitions2(document, noLinkRanges); } - private *getLinkDefinitions2(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable { + private *getLinkDefinitions2(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(definitionPattern)) { const pre = match[1]; @@ -388,7 +391,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { text = link; } const hrefRange = new vscode.Range(linkStart, linkEnd); - if (isLinkInsideCode(codeInDocument, hrefRange)) { + if (noLinkRanges.contains(hrefRange)) { continue; } const target = parseLink(document, text); diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts index 1df40657d74..1f9960d08a1 100644 --- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts +++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts @@ -270,4 +270,47 @@ suite('markdown.DocumentLinkProvider', () => { const link = links[0]; assertRangeEqual(link.range, new vscode.Range(0, 5, 0, 23)); }); + + test('Should not detect links inside html comment blocks', async () => { + const links = await getLinksForFile(joinLines( + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + )); + assert.strictEqual(links.length, 0); + }); + + test.skip('Should not detect links inside inline html comments', async () => { + // See #149678 + const links = await getLinksForFile(joinLines( + `text text`, + `text text`, + `text text`, + ``, + `text text`, + ``, + `text text`, + ``, + `text text`, + )); + assert.strictEqual(links.length, 0); + }); }); From 5f3e9c120a4407de3e55465588ce788618526eb0 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Mon, 16 May 2022 18:40:33 -0700 Subject: [PATCH 070/350] Update custom menu styles (#149187) * Initial updates * Add border radius * Address PR feedback * Fix typo * Update shadow blur * Update LR padding and use description foreground for shortcuts * Typo * Fix separator padding/margin * fix jumpy items in hc themes * Fix shadow and border radius * Use opacity for keybinding for better color blend * Update min width and container padding T/B * Revert actionbar margin and remove unnecessary menu css file * Ensure menus respect 0 horizontal margin rule * set bg/fg color on menu container * better fix for jumpy menu items * use outline instead of border * clean up dead css in style.css fix opacity for separators in menus * bring back vertical action bar margins * Remove old CSS import Co-authored-by: SteVen Batten --- extensions/theme-defaults/themes/dark_vs.json | 2 +- src/vs/base/browser/ui/menu/menu.ts | 58 ++++++++++--------- .../browser/contextMenuHandler.css | 9 --- .../contextview/browser/contextMenuHandler.ts | 1 - src/vs/platform/theme/common/colorRegistry.ts | 2 +- src/vs/workbench/browser/media/style.css | 44 -------------- 6 files changed, 33 insertions(+), 83 deletions(-) delete mode 100644 src/vs/platform/contextview/browser/contextMenuHandler.css diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 768a6b3038c..8072b0bdd6e 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -12,7 +12,7 @@ "activityBarBadge.background": "#007ACC", "sideBarTitle.foreground": "#BBBBBB", "input.placeholderForeground": "#A6A6A6", - "menu.background": "#252526", + "menu.background": "#303031", "menu.foreground": "#CCCCCC", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D", diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index e3528de5065..24feda55daa 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -287,11 +287,13 @@ export class Menu extends ActionBar { const fgColor = style.foregroundColor ? `${style.foregroundColor}` : ''; const bgColor = style.backgroundColor ? `${style.backgroundColor}` : ''; const border = style.borderColor ? `1px solid ${style.borderColor}` : ''; - const shadow = style.shadowColor ? `0 2px 4px ${style.shadowColor}` : ''; + const borderRadius = '5px'; + const shadow = style.shadowColor ? `0 2px 8px ${style.shadowColor}` : ''; - container.style.border = border; - this.domNode.style.color = fgColor; - this.domNode.style.backgroundColor = bgColor; + container.style.outline = border; + container.style.borderRadius = borderRadius; + container.style.color = fgColor; + container.style.backgroundColor = bgColor; container.style.boxShadow = shadow; if (this.viewItems) { @@ -691,20 +693,19 @@ class BaseMenuActionViewItem extends BaseActionViewItem { const isSelected = this.element && this.element.classList.contains('focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined; - const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : ''; + const outline = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : ''; + const outlineOffset = isSelected && this.menuStyle.selectionBorderColor ? `-1px` : ''; if (this.item) { this.item.style.color = fgColor ? fgColor.toString() : ''; this.item.style.backgroundColor = bgColor ? bgColor.toString() : ''; + this.item.style.outline = outline; + this.item.style.outlineOffset = outlineOffset; } if (this.check) { this.check.style.color = fgColor ? fgColor.toString() : ''; } - - if (this.container) { - this.container.style.border = border; - } } style(style: IMenuStyles): void { @@ -1012,7 +1013,8 @@ function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): string { let result = /* css */` .monaco-menu { font-size: 13px; - + border-radius: 5px; + min-width: 160px; } ${formatRule(Codicon.menuSelection)} @@ -1087,10 +1089,9 @@ ${formatRule(Codicon.menuSubmenu)} .monaco-menu .monaco-action-bar.vertical .action-label.separator { display: block; - border-bottom: 1px solid #bbb; + border-bottom: 1px solid var(--vscode-menu-separatorBackground); padding-top: 1px; - margin-left: .8em; - margin-right: .8em; + padding: 30px; } .monaco-menu .secondary-actions .monaco-action-bar .action-label { @@ -1136,6 +1137,11 @@ ${formatRule(Codicon.menuSubmenu)} position: relative; } +.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding, +.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding { + opacity: unset; +} + .monaco-menu .monaco-action-bar.vertical .action-label { flex: 1 1 auto; text-decoration: none; @@ -1191,12 +1197,9 @@ ${formatRule(Codicon.menuSubmenu)} } .monaco-menu .monaco-action-bar.vertical .action-label.separator { - padding: 0.5em 0 0 0; - margin-bottom: 0.5em; width: 100%; height: 0px !important; - margin-left: .8em !important; - margin-right: .8em !important; + opacity: 1; } .monaco-menu .monaco-action-bar.vertical .action-label.separator.text { @@ -1238,17 +1241,15 @@ ${formatRule(Codicon.menuSubmenu)} outline: 0; } -.monaco-menu .monaco-action-bar.vertical .action-item { - border: thin solid transparent; /* prevents jumping behaviour on hover or focus */ -} - - -/* High Contrast Theming */ +.hc-black .context-view.monaco-menu-container, +.hc-light .context-view.monaco-menu-container, :host-context(.hc-black) .context-view.monaco-menu-container, :host-context(.hc-light) .context-view.monaco-menu-container { box-shadow: none; } +.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused, +.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused, :host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused, :host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused { background: none; @@ -1257,11 +1258,11 @@ ${formatRule(Codicon.menuSubmenu)} /* Vertical Action Bar Styles */ .monaco-menu .monaco-action-bar.vertical { - padding: .5em 0; + padding: .6em 0; } .monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 1.8em; + height: 2em; } .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), @@ -1277,10 +1278,12 @@ ${formatRule(Codicon.menuSubmenu)} .monaco-menu .monaco-action-bar.vertical .action-label.separator { font-size: inherit; - padding: 0.2em 0 0 0; - margin-bottom: 0.2em; + margin: 5px 0 !important; + padding: 0; + border-radius: 0; } +.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator, :host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator { margin-left: 0; margin-right: 0; @@ -1291,6 +1294,7 @@ ${formatRule(Codicon.menuSubmenu)} padding: 0 1.8em; } +.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { :host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator { height: 100%; mask-size: 10px 10px; diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.css b/src/vs/platform/contextview/browser/contextMenuHandler.css deleted file mode 100644 index 51a9e400923..00000000000 --- a/src/vs/platform/contextview/browser/contextMenuHandler.css +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.context-view .monaco-menu { - min-width: 130px; -} - diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index a0f444f666c..6ad90b3af61 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -10,7 +10,6 @@ import { Menu } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IRunEvent, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { isCancellationError } from 'vs/base/common/errors'; import { combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import 'vs/css!./contextMenuHandler'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 6e163e27d16..4696b6036e6 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -470,7 +470,7 @@ export const menuBackground = registerColor('menu.background', { dark: selectBac export const menuSelectionForeground = registerColor('menu.selectionForeground', { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hcDark: listActiveSelectionForeground, hcLight: listActiveSelectionForeground }, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus.")); export const menuSelectionBackground = registerColor('menu.selectionBackground', { dark: listActiveSelectionBackground, light: listActiveSelectionBackground, hcDark: listActiveSelectionBackground, hcLight: listActiveSelectionBackground }, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus.")); export const menuSelectionBorder = registerColor('menu.selectionBorder', { dark: null, light: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('menuSelectionBorder', "Border color of the selected menu item in menus.")); -export const menuSeparatorBackground = registerColor('menu.separatorBackground', { dark: '#BBBBBB', light: '#888888', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('menuSeparatorBackground', "Color of a separator menu item in menus.")); +export const menuSeparatorBackground = registerColor('menu.separatorBackground', { dark: '#606060', light: '#D4D4D4', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('menuSeparatorBackground', "Color of a separator menu item in menus.")); /** * Toolbar colors diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 0f2382f01a4..3d61bbcb255 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -135,50 +135,6 @@ body.web { -webkit-app-region: no-drag; } -.monaco-workbench .monaco-menu .monaco-action-bar.vertical { - padding: .5em 0; -} - -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 1.8em; -} - -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .keybinding { - font-size: inherit; - padding: 0 2em; -} - -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .menu-item-check { - font-size: inherit; - width: 2em; -} - -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-label.separator { - font-size: inherit; - padding: 0.2em 0 0 0; - margin-bottom: 0.2em; -} - -.monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator { - margin-left: 0; - margin-right: 0; -} - -.monaco-workbench .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - font-size: 60%; - padding: 0 1.8em; -} - -.monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - height: 100%; - mask-size: 10px 10px; - -webkit-mask-size: 10px 10px; -} - -.monaco-workbench .monaco-menu .action-item { - cursor: default; -} .monaco-workbench .codicon[class*='codicon-'] { font-size: 16px; /* sets font-size for codicons in workbench (https://github.com/microsoft/vscode/issues/98495) */ From abb896879f4bb1d3e26008026308f41986743796 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 16 May 2022 14:05:05 -0700 Subject: [PATCH 071/350] Add hasPolicy tag --- .../preferences/browser/settingsEditor2.ts | 11 ++-- .../preferences/browser/settingsSearchMenu.ts | 8 ++- .../preferences/browser/settingsTree.ts | 56 ++++++++++++------- .../preferences/browser/settingsTreeModels.ts | 13 ++++- .../contrib/preferences/common/preferences.ts | 1 + 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index d2d2c183503..4bbcafeb7c2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -43,7 +43,7 @@ import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/brow import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -126,6 +126,7 @@ export class SettingsEditor2 extends EditorPane { `@${FEATURE_SETTING_TAG}remote`, `@${FEATURE_SETTING_TAG}timeline`, `@${FEATURE_SETTING_TAG}notebook`, + `@${POLICY_SETTING_TAG}` ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { @@ -866,10 +867,10 @@ export class SettingsEditor2 extends EditorPane { // the element was not found } })); - this._register(this.settingRenderers.onApplyLanguageFilter((lang: string) => { - if (this.searchWidget) { - // Prepend the language filter to the query. - const newQuery = `@${LANGUAGE_SETTING_TAG}${lang} ${this.searchWidget.getValue().trimStart()}`; + this._register(this.settingRenderers.onApplyFilter((filter: string) => { + if (this.searchWidget && !this.searchWidget.getValue().includes(filter)) { + // Prepend the filter to the query. + const newQuery = `${filter} ${this.searchWidget.getValue().trimStart()}`; this.focusSearch(newQuery, false); } })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts index e39d23eb58e..9a1c0ec289c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts @@ -10,7 +10,7 @@ import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestCont import { localize } from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; -import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionViewItem { private readonly suggestController: SuggestController | null; @@ -135,6 +135,12 @@ export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenu localize('onlineSettingsSearch', "Online services"), localize('onlineSettingsSearchTooltip', "Show settings for online services"), '@tag:usesOnlineServices' + ), + this.createToggleAction( + 'policySettingsSearch', + localize('policySettingsSearch', "Policy services"), + localize('policySettingsSearchTooltip', "Show settings for policy services"), + `@${POLICY_SETTING_TAG}` ) ]; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 673de4bf5aa..2e2ee4e7257 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -43,7 +43,7 @@ import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerg import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { inspectSetting, ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { ExcludeSettingWidget, ISettingListChangeEvent, IListDataItem, ListSettingWidget, ObjectSettingDropdownWidget, IObjectDataItem, IObjectEnumOption, ObjectValue, IObjectValueSuggester, IObjectKeySuggester, ObjectSettingCheckboxWidget } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; -import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { LANGUAGE_SETTING_TAG, POLICY_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { getDefaultIgnoredSettings, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; @@ -740,8 +740,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre protected readonly _onDidChangeSettingHeight = this._register(new Emitter()); readonly onDidChangeSettingHeight: Event = this._onDidChangeSettingHeight.event; - protected readonly _onApplyLanguageFilter = this._register(new Emitter()); - readonly onApplyLanguageFilter: Event = this._onApplyLanguageFilter.event; + protected readonly _onApplyFilter = this._register(new Emitter()); + readonly onApplyFilter: Event = this._onApplyFilter.event; private readonly markdownRenderer: MarkdownRenderer; @@ -794,14 +794,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = new DisposableStore(); - - const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); - const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); - })); - const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const policyWarningElement = this.renderPolicyLabel(container, toDispose); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); @@ -848,6 +841,33 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre }); } + protected renderPolicyLabel(container: HTMLElement, toDispose: DisposableStore): HTMLElement { + const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); + const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); + toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { + policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); + })); + const element = DOM.append(policyWarningElement, $('span')); + element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const viewPolicyLabel = localize('viewPolicySettings', "View policy settings"); + const linkElement: HTMLAnchorElement = DOM.append(policyWarningElement, $('a')); + linkElement.textContent = viewPolicyLabel; + linkElement.setAttribute('tabindex', '0'); + linkElement.href = '#'; + toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.CLICK, (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + this._onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); + })); + toDispose.add(DOM.addStandardDisposableListener(linkElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + e.stopPropagation(); + this._onApplyFilter.fire(`@${POLICY_SETTING_TAG}`); + } + })); + return policyWarningElement; + } + protected renderSettingToolbar(container: HTMLElement): ToolBar { const toggleMenuKeybinding = this._keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU); let toggleMenuTitle = localize('settingsContextMenuTitle', "More Actions... "); @@ -1098,7 +1118,7 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I template.elementDisposables.add(template.button.onDidClick(() => { if (isLanguageTagSetting) { - this._onApplyLanguageFilter.fire(plainKey); + this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}:${plainKey}`); } else { this._onDidOpenSettings.fire(dataElement.setting.key); } @@ -1836,13 +1856,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const toolbar = this.renderSettingToolbar(toolbarContainer); toDispose.add(toolbar); - const policyWarningElement = DOM.append(container, $('.setting-item-policy-description')); - const policyIcon = DOM.append(policyWarningElement, $('span.codicon.codicon-lock')); - toDispose.add(attachStylerCallback(this._themeService, { editorInfoForeground }, colors => { - policyIcon.style.setProperty('--organization-policy-icon-color', colors.editorInfoForeground?.toString() || ''); - })); - const element = DOM.append(policyWarningElement, $('span')); - element.textContent = localize('policyLabel', "This setting is managed by your organization."); + const policyWarningElement = this.renderPolicyLabel(container, toDispose); const template: ISettingBoolItemTemplate = { toDispose, @@ -1943,7 +1957,7 @@ export class SettingTreeRenderers { readonly onDidChangeSettingHeight: Event; - readonly onApplyLanguageFilter: Event; + readonly onApplyFilter: Event; readonly allRenderers: ITreeRenderer[]; @@ -1992,7 +2006,7 @@ export class SettingTreeRenderers { this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink)); this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting)); this.onDidChangeSettingHeight = Event.any(...settingRenderers.map(r => r.onDidChangeSettingHeight)); - this.onApplyLanguageFilter = Event.any(...settingRenderers.map(r => r.onApplyLanguageFilter)); + this.onApplyFilter = Event.any(...settingRenderers.map(r => r.onApplyFilter)); this.allRenderers = [ ...settingRenderers, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6e0c5b34b43..b2f6eac0cdc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -11,7 +11,7 @@ import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { 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 { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -238,7 +238,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.value = displayValue; this.isConfigured = isConfigured; - if (isConfigured || this.setting.tags || this.tags || this.setting.restricted) { + if (isConfigured || this.setting.tags || this.tags || this.setting.restricted || this.hasPolicyValue) { // Don't create an empty Set for all 1000 settings, only if needed this.tags = new Set(); if (isConfigured) { @@ -252,6 +252,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { if (this.setting.restricted) { this.tags.add(REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG); } + + if (this.hasPolicyValue) { + this.tags.add(POLICY_SETTING_TAG); + } } this.overriddenScopeList = overriddenScopeList; @@ -905,6 +909,11 @@ export function parseQuery(query: string): IParsedQuery { return ''; }); + query = query.replace(`@${POLICY_SETTING_TAG}`, () => { + tags.push(POLICY_SETTING_TAG); + return ''; + }); + const extensions: string[] = []; const features: string[] = []; const ids: string[] = []; diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 6138098d36f..6b87d1ae46b 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -78,6 +78,7 @@ export const FEATURE_SETTING_TAG = 'feature:'; export const ID_SETTING_TAG = 'id:'; export const LANGUAGE_SETTING_TAG = 'lang:'; export const GENERAL_TAG_SETTING_TAG = 'tag:'; +export const POLICY_SETTING_TAG = 'hasPolicy'; export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust'; export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace'; export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker'; From 558b5fbc2716b1c2339a83e95450c1972eb18ddf Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 08:48:08 +0200 Subject: [PATCH 072/350] fix policy ipc serialization --- src/vs/platform/policy/common/policyIpc.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index c163321d085..2fdb1e62cb1 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -8,8 +8,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -type Policies = { [name: PolicyName]: PolicyValue | undefined }; - export class PolicyChannel implements IServerChannel { private readonly disposables = new DisposableStore(); @@ -20,7 +18,7 @@ export class PolicyChannel implements IServerChannel { switch (event) { case 'onDidChange': return Event.map( this.service.onDidChange, - names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) }), {}), + names => names.reduce((r, name) => ({ ...r, [name]: this.service.getPolicyValue(name) ?? null }), {}), this.disposables ); } @@ -51,11 +49,11 @@ export class PolicyChannelClient implements IPolicyService { readonly onDidChange: Event = this._onDidChange.event; constructor(private readonly channel: IChannel) { - this.channel.listen('onDidChange')(policies => { + this.channel.listen('onDidChange')(policies => { for (const name in policies) { - const value = policies[name]; + const value = policies[name as keyof typeof policies]; - if (value === undefined) { + if (value === null) { this.policies.delete(name); } else { this.policies.set(name, value); From a1090d51c50e6e266e28b69f00dac615e380bc67 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 08:55:41 +0200 Subject: [PATCH 073/350] make sure policy init always returns the right values --- src/vs/platform/policy/node/windowsPolicyService.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 7e943f1c5ff..9b9cba059a9 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -18,7 +18,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise<{ [name: PolicyName]: PolicyValue }> | undefined; + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; @@ -29,7 +29,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { super(); } - initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { if (!this.init) { this.init = new Promise(c => { if (!this.productService.win32RegValueName) { @@ -66,7 +66,7 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {})); + c(); } else { this._onDidChange.fire(Object.keys(update)); } @@ -74,7 +74,8 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { }); } - return this.init; + await this.init; + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } getPolicyValue(name: PolicyName): PolicyValue | undefined { From 2f19fc5c580b56c4f1e40215432df4c682b3d8a5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:00:45 +0200 Subject: [PATCH 074/350] delete policy removals --- src/vs/platform/policy/node/windowsPolicyService.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 9b9cba059a9..36a083dd2d7 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -61,7 +61,13 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { this._register(createWatcher(this.productService.win32RegValueName, policies, update => { for (const key in update) { - this.policies.set(key, update[key]!); + const value = update[key] as any; + + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); + } } if (first) { From 2d1940e1ab04c5b679409f434272c2752d7ce1f5 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:33:32 +0200 Subject: [PATCH 075/350] wip: registerPolicyDefinitions --- src/vs/code/electron-main/main.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../configuration/common/configurations.ts | 32 +++++++++- .../policy/common/filePolicyService.ts | 6 +- src/vs/platform/policy/common/policy.ts | 34 ++--------- src/vs/platform/policy/common/policyIpc.ts | 11 ++-- .../policy/node/windowsPolicyService.ts | 60 +++++++------------ 7 files changed, 66 insertions(+), 83 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f31b96ebece..16a7a727c15 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -169,7 +169,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration @@ -232,8 +232,6 @@ class CodeMain { environmentMainService.backupHome ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), - policyService.initialize(), - // Configuration service configurationService.initialize(), diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 2217a4b70b1..4ea5092c119 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -132,7 +132,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows ? new WindowsPolicyService(productService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 1834735dccf..aed37d85e47 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -11,7 +11,7 @@ import { equals } from 'vs/base/common/objects'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; export class DefaultConfiguration extends Disposable { @@ -91,10 +91,38 @@ export class PolicyConfiguration extends Disposable { super(); } + // TODO@sandy: make nice + private getPolicyDefinitions(): IStringDictionary { + const result: IStringDictionary = {}; + const configRegistry = Registry.as(Extensions.Configuration); + + for (const configuration of configRegistry.getConfigurations()) { + if (configuration.properties) { + for (const key in configuration.properties) { + const config = configuration.properties[key]; + const policy = config.policy; + + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; + } + + result[policy.name] = { type: config.type }; + } + } + } + } + + return result; + } + async initialize(): Promise { - await this.policyService.initialize(); + // TODO@sandy: go through registry + await this.policyService.registerPolicyDefinitions(this.getPolicyDefinitions()); this.update(this.defaultConfiguration.configurationModel.keys, false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + // TODO@sandy: also make sure that policy configurations that are registered after initialize() also call registerPolicyDefinitions() this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); return this._configurationModel; } diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index f46b55cb70f..f3786b547e8 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ThrottledDelayer } from 'vs/base/common/async'; +import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -11,7 +12,7 @@ import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -48,7 +49,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + // TODO@sandeep respect only registered policy definitions + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { await this.refresh(); return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 6446f06e9ac..21ef3302bb0 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; export type PolicyValue = string | boolean; +export type PolicyDefinition = { type: 'string' | 'number' }; export const IPolicyService = createDecorator('policy'); @@ -15,41 +17,13 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - initialize(): Promise<{ [name: PolicyName]: PolicyValue }>; + registerPolicyDefinitions(policies: IStringDictionary): Promise>; getPolicyValue(name: PolicyName): PolicyValue | undefined; } export class NullPolicyService implements IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange = Event.None; - async initialize() { return {}; } + async registerPolicyDefinitions() { return {}; } getPolicyValue() { return undefined; } } - -export class MultiPolicyService implements IPolicyService { - - readonly _serviceBrand: undefined; - - readonly onDidChange: Event; - - constructor(private policyServices: readonly IPolicyService[]) { - this.onDidChange = Event.any(...policyServices.map(p => p.onDidChange)); - } - - async initialize() { - const result = await Promise.all(this.policyServices.map(p => p.initialize())); - return result.reduce((r, o) => ({ ...r, ...o }), {}); - } - - getPolicyValue(name: PolicyName) { - for (const policyService of this.policyServices) { - const result = policyService.getPolicyValue(name); - - if (typeof result !== 'undefined') { - return result; - } - } - - return undefined; - } -} diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 2fdb1e62cb1..177b1d51ae6 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; export class PolicyChannel implements IServerChannel { @@ -26,9 +27,9 @@ export class PolicyChannel implements IServerChannel { throw new Error(`Event not found: ${event}`); } - call(_: unknown, command: string): Promise { + call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'initialize': return this.service.initialize(); + case 'initialize': return this.service.registerPolicyDefinitions(arg as IStringDictionary); } throw new Error(`Call not found: ${command}`); @@ -64,8 +65,8 @@ export class PolicyChannelClient implements IPolicyService { }); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { - const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize'); + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize', policies); for (const name in result) { this.policies.set(name, result[name]); diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts index 36a083dd2d7..23439893dc8 100644 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ b/src/vs/platform/policy/node/windowsPolicyService.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { createWatcher } from 'vscode-policy-watcher'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { createWatcher, Watcher } from 'vscode-policy-watcher'; import { IStringDictionary } from 'vs/base/common/collections'; import { Iterable } from 'vs/base/common/iterator'; @@ -18,48 +15,21 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private readonly policies = new Map(); - private init: Promise | undefined; + private init: Promise | undefined; private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - constructor( - @IProductService private readonly productService: IProductService - ) { + constructor(private readonly productName: string) { super(); } - async initialize(): Promise<{ [name: PolicyName]: PolicyValue }> { + async registerPolicyDefinitions(policies: IStringDictionary): Promise> { if (!this.init) { this.init = new Promise(c => { - if (!this.productService.win32RegValueName) { - return; - } - - const policies: IStringDictionary<{ type: 'string' | 'number' }> = {}; - const configRegistry = Registry.as(Extensions.Configuration); - - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; - - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; - } - - policies[policy.name] = { type: config.type }; - } - } - } - } - let first = true; - this._register(createWatcher(this.productService.win32RegValueName, policies, update => { + const watcher = createWatcher(this.productName, policies, update => { for (const key in update) { const value = update[key] as any; @@ -72,15 +42,25 @@ export class WindowsPolicyService extends Disposable implements IPolicyService { if (first) { first = false; - c(); + c(watcher); } else { this._onDidChange.fire(Object.keys(update)); } - })); + }); + + this._register(watcher); }); + + await this.init; + } else { + const watcher = await this.init; + const promise = Event.toPromise(this.onDidChange); + watcher.addPolicies(policies); + await promise; } - await this.init; + // TODO@joao: heavy cleanup + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } From 9be07b370b7c0b443ee6227a79edb43300c47ded Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 17 May 2022 00:34:27 -0700 Subject: [PATCH 076/350] Post old version comment as bot rather than "github actions" (#149687) --- .github/workflows/on-open.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index 3c6617101e7..af70c86caa0 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -28,6 +28,7 @@ jobs: uses: ./actions/new-release with: label: new release + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} labelColor: "006b75" labelDescription: Issues found in a recent release of VS Code From 5db383efe60fd80328658a9625d39b25de8ebafe Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 17 May 2022 09:35:35 +0200 Subject: [PATCH 077/350] use FilePolicyService --- src/vs/code/electron-main/main.ts | 3 ++- src/vs/code/node/cliProcessMain.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 16a7a727c15..029a5c98f37 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -64,6 +64,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; /** * The main VS Code entry point. @@ -169,7 +170,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 4ea5092c119..184289c296e 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -40,6 +40,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; import product from 'vs/platform/product/common/product'; @@ -132,7 +133,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration From 83213f7bac0a66e30b9c921d5e8ac37b60f4ddb1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 May 2022 10:42:31 +0200 Subject: [PATCH 078/350] Switch agent pools (#149691) --- build/azure-pipelines/product-build-pr.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index db20311683d..53fc0fb3c2e 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -31,8 +31,7 @@ stages: - stage: Compile jobs: - job: Compile - pool: - vmImage: ubuntu-18.04 + pool: vscode-1es-vscode-linux-18.04 variables: VSCODE_ARCH: x64 steps: @@ -42,8 +41,7 @@ stages: - stage: LinuxServerDependencies dependsOn: [] - pool: - vmImage: ubuntu-18.04 + pool: vscode-1es-vscode-linux-18.04 jobs: - job: x64 container: centos7-devtoolset8-x64 @@ -58,8 +56,7 @@ stages: - stage: Windows dependsOn: - Compile - pool: - vmImage: windows-2019 + pool: vscode-1es-vscode-windows-2019 jobs: - job: Windows timeoutInMinutes: 120 @@ -74,8 +71,7 @@ stages: dependsOn: - Compile - LinuxServerDependencies - pool: - vmImage: ubuntu-18.04 + pool: vscode-1es-vscode-linux-18.04 jobs: - job: Linuxx64 container: vscode-bionic-x64 From d728de2e8d7eb57ba15b89c4f8b9741df32ab5e4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 10:51:23 +0200 Subject: [PATCH 079/350] fix wordings --- .../common/abstractExtensionManagementService.ts | 2 +- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index d096eaa5ff7..a73af96a7c8 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -366,7 +366,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const deprecated = report.deprecated ? report.deprecated[extension.identifier.id.toLowerCase()] : undefined; if (deprecated && !isBoolean(deprecated)) { - throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is deprecated. Instead use '{1}' extension.", extension.identifier.id, deprecated.displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); + throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is deprecated. Use '{1}' extension instead.", extension.identifier.id, deprecated.displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); } if (!await this.canInstall(extension)) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 06033ae52a7..d3b1573086e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2178,7 +2178,7 @@ export class ExtensionStatusAction extends ExtensionAction { } else { const link = `[${this.extension.deprecated.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecated.id]))}`)})`; if (this.extension.state !== ExtensionState.Installed) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension has been deprecated. Instead use {0}.", link)) }, true); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension has been deprecated. Use {0} instead.", link)) }, true); } } return; From 4904c88c9d97f3089f2a8a7dcb69f42efd9c10e7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 11:04:38 +0200 Subject: [PATCH 080/350] update distro --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index acc64a5ce02..a0d15674aaa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.68.0", - "distro": "5e85c2493e771e21081d8f4188922225406ed06b", + "distro": "dec8abea6c1191c66ab4a551172264be30a76e26", "author": { "name": "Microsoft Corporation" }, @@ -229,4 +229,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} +} \ No newline at end of file From 38de430140abc164ca2980fc715c47a343da1b2d Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 May 2022 12:41:45 +0200 Subject: [PATCH 081/350] Add scope for settings (#149696) --- extensions/git/package.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index c3c93123829..de341a095ee 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1852,7 +1852,8 @@ "git.branchPrefix": { "type": "string", "description": "%config.branchPrefix%", - "default": "" + "default": "", + "scope": "resource" }, "git.branchProtection": { "type": "array", @@ -1860,7 +1861,8 @@ "items": { "type": "string" }, - "default": [] + "default": [], + "scope": "resource" }, "git.branchProtectionPrompt": { "type": "string", @@ -1875,7 +1877,8 @@ "%config.branchProtectionPrompt.alwaysCommitToNewBranch%", "%config.branchProtectionPrompt.alwaysPrompt%" ], - "default": "alwaysPrompt" + "default": "alwaysPrompt", + "scope": "resource" }, "git.branchValidationRegex": { "type": "string", @@ -1890,7 +1893,8 @@ "git.branchRandomName.enable": { "type": "boolean", "description": "%config.branchRandomNameEnable%", - "default": false + "default": false, + "scope": "resource" }, "git.branchRandomName.dictionary": { "type": "array", @@ -1901,7 +1905,8 @@ "default": [ "adjectives", "animals" - ] + ], + "scope": "resource" }, "git.confirmSync": { "type": "boolean", From f2380f2c909a795f9352ab1b04e17b473828c721 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 17 May 2022 12:49:52 +0200 Subject: [PATCH 082/350] Engineering - Run PR pipeline for the main branch (#149700) * Add trigger for the main branch * Add trigger for release branches --- build/azure-pipelines/product-build-pr.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 53fc0fb3c2e..21fbe295cc9 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -1,4 +1,6 @@ -trigger: none +trigger: + - main + - release/* pr: branches: From 4182f3b2ad6f762f24df8da490bce0e403a0fdce Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 17 May 2022 13:46:32 +0200 Subject: [PATCH 083/350] show `detail` atop of code view --- .../mergeEditor/browser/media/mergeEditor.css | 3 ++- .../mergeEditor/browser/mergeEditor.ts | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index 2e3929e236c..0b9caf7faf8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ .monaco-workbench .merge-editor .code-view > .title { - padding: 0 0 0 10px; + padding: 0 10px; height: 30px; display: flex; align-content: center; + justify-content: space-between; } .monaco-workbench .merge-editor .code-view > .title .monaco-icon-label { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index bb5cb6334e6..3cb4562da9e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -170,9 +170,9 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.input1View.setModel(model.input1, localize('yours', 'Yours'), model.input1Detail); - this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), model.input2Detail); - this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true })); + this.input1View.setModel(model.input1, localize('yours', 'Yours'), model.input1Detail, model.input1Description); + this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input1Description); + this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); let input1Decorations = new Array(); let input2Decorations = new Array(); @@ -364,9 +364,9 @@ class CodeEditorView implements IView { // preferredHeight?: number | undefined; element: HTMLElement; - private _titleElement: HTMLElement; - private _editorElement: HTMLElement; - public _gutterDiv: HTMLElement; + private readonly _titleElement: HTMLElement; + private readonly _editorElement: HTMLElement; + public readonly _gutterDiv: HTMLElement; minimumWidth: number = 10; maximumWidth: number = Number.MAX_SAFE_INTEGER; @@ -378,7 +378,8 @@ class CodeEditorView implements IView { private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; - private _title: IconLabel; + private readonly _title: IconLabel; + private readonly _detail: IconLabel; public readonly editor: CodeEditorWidget; // private readonly gutter: EditorGutterWidget; @@ -406,11 +407,13 @@ class CodeEditorView implements IView { ); this._title = new IconLabel(this._titleElement, { supportIcons: true }); + this._detail = new IconLabel(this._titleElement, { supportIcons: true }); } - public setModel(model: ITextModel, title: string, description: string | undefined): void { + public setModel(model: ITextModel, title: string, description: string | undefined, detail: string | undefined): void { this.editor.setModel(model); this._title.setLabel(title, description); + this._detail.setLabel('', detail); } layout(width: number, height: number, top: number, left: number): void { From 7a511786185e49b39b85e71cf8a01006b3e1e4bb Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 17 May 2022 13:46:42 +0200 Subject: [PATCH 084/350] git should open merge editor with detail and description (tag and commit-hash) --- extensions/git/src/commands.ts | 47 ++++++++++++++++++++++++++++- extensions/git/src/git.ts | 9 ++++-- extensions/git/src/repository.ts | 12 ++------ extensions/git/src/test/git.test.ts | 6 ++++ 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 14138c13bc5..da619d4feb5 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -15,7 +15,7 @@ import { Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; -import { fromGitUri, toGitUri, isGitUri } from './uri'; +import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; import { grep, isDescendant, pathEquals, relativePath } from './util'; import { LogLevel, OutputChannelLogger } from './log'; import { GitTimelineItem } from './timelineProvider'; @@ -405,6 +405,51 @@ export class CommandCenter { } } + @command('_git.openMergeEditor') + async openMergeEditor(uri: unknown) { + if (!(uri instanceof Uri)) { + return; + } + const repo = this.model.getRepository(uri); + if (!repo) { + return; + } + + + type InputData = { uri: Uri; detail?: string; description?: string }; + const mergeUris = toMergeUris(uri); + let input1: InputData = { uri: mergeUris.ours }; + let input2: InputData = { uri: mergeUris.theirs }; + + try { + const [head, mergeHead] = await Promise.all([repo.getCommit('HEAD'), repo.getCommit('MERGE_HEAD')]); + // ours (current branch and commit) + input1.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); + input1.description = head.hash.substring(0, 7); + + // theirs + input2.detail = mergeHead.refNames.join(', '); + input2.description = mergeHead.hash.substring(0, 7); + + } catch (error) { + // not so bad, can continue with just uris + console.error('FAILED to read HEAD, MERGE_HEAD commits'); + console.error(error); + } + + const options = { + ancestor: mergeUris.base, + input1, + input2, + output: uri + }; + + await commands.executeCommand( + '_open.mergeEditor', + options + ); + } + async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise { if (!url || typeof url !== 'string') { url = await pickRemoteSource({ diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 8fdab7f659b..a511db761a6 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -354,7 +354,7 @@ function sanitizePath(path: string): string { return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`); } -const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%B'; +const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B'; export interface ICloneOptions { readonly parentPath: string; @@ -660,6 +660,7 @@ export interface Commit { authorName?: string; authorEmail?: string; commitDate?: Date; + refNames: string[]; } export class GitStatusParser { @@ -790,7 +791,7 @@ export function parseGitmodules(raw: string): Submodule[] { return result; } -const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; +const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; export function parseGitCommits(data: string): Commit[] { let commits: Commit[] = []; @@ -801,6 +802,7 @@ export function parseGitCommits(data: string): Commit[] { let authorDate; let commitDate; let parents; + let refNames; let message; let match; @@ -810,7 +812,7 @@ export function parseGitCommits(data: string): Commit[] { break; } - [, ref, authorName, authorEmail, authorDate, commitDate, parents, message] = match; + [, ref, authorName, authorEmail, authorDate, commitDate, parents, refNames, message] = match; if (message[message.length - 1] === '\n') { message = message.substr(0, message.length - 1); @@ -825,6 +827,7 @@ export function parseGitCommits(data: string): Commit[] { authorName: ` ${authorName}`.substr(1), authorEmail: ` ${authorEmail}`.substr(1), commitDate: new Date(Number(commitDate) * 1000), + refNames: refNames.split(',').map(s => s.trim()) }); } while (true); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4a81c8fac64..ebe1105ab94 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -13,7 +13,7 @@ import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git'; import { StatusBarCommands } from './statusbar'; -import { toGitUri, toMergeUris } from './uri'; +import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { LogLevel, OutputChannelLogger } from './log'; @@ -603,16 +603,10 @@ class ResourceCommandResolver { const title = this.getTitle(resource); if (!resource.leftUri && resource.rightUri && resource.type === Status.BOTH_MODIFIED) { - const mergeUris = toMergeUris(resource.rightUri); return { - command: '_open.mergeEditor', + command: '_git.openMergeEditor', title: localize('open.merge', "Open Merge"), - arguments: [{ - ancestor: mergeUris.base, - input1: mergeUris.ours, - input2: mergeUris.theirs, - output: resource.rightUri - }] + arguments: [resource.rightUri] }; } else if (!resource.leftUri) { return { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index c0d0d2003c9..3b225157c3b 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -205,6 +205,7 @@ john.doe@mail.com 1580811030 1580811031 8e5a374372b8393906c7e380dbb09349c5385554 +main,branch This is a commit message.\x00`; assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_SINGLE_PARENT), [{ @@ -215,6 +216,7 @@ This is a commit message.\x00`; authorName: 'John Doe', authorEmail: 'john.doe@mail.com', commitDate: new Date(1580811031000), + refNames: ['main', 'branch'], }]); }); @@ -225,6 +227,7 @@ john.doe@mail.com 1580811030 1580811031 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 +main This is a commit message.\x00`; assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_MULTIPLE_PARENTS), [{ @@ -235,6 +238,7 @@ This is a commit message.\x00`; authorName: 'John Doe', authorEmail: 'john.doe@mail.com', commitDate: new Date(1580811031000), + refNames: ['main'], }]); }); @@ -245,6 +249,7 @@ john.doe@mail.com 1580811030 1580811031 +main This is a commit message.\x00`; assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_NO_PARENTS), [{ @@ -255,6 +260,7 @@ This is a commit message.\x00`; authorName: 'John Doe', authorEmail: 'john.doe@mail.com', commitDate: new Date(1580811031000), + refNames: ['main'], }]); }); }); From caa463191f8565dc85b2fbd90598dd22bf72bd8f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 17 May 2022 15:20:02 +0200 Subject: [PATCH 085/350] add telemetry event comments and owner (#149070) --- .../html-language-features/server/src/htmlServer.ts | 9 --------- .../json-language-features/client/src/jsonClient.ts | 4 +++- extensions/json-language-features/package.json | 2 +- extensions/json-language-features/yarn.lock | 8 ++++---- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 6713556ae38..b3df5fc8e51 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -321,15 +321,6 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } const doComplete = mode.doComplete; - if (mode.getId() !== 'html') { - /* __GDPR__ - "html.embbedded.complete" : { - "languageId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); - } - const settings = await getDocumentSettings(document, () => doComplete.length > 2); const documentContext = getDocumentContext(document.uri, workspaceFolders); return doComplete(document, textDocumentPosition.position, documentContext, settings); diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 9bf63477ebe..5246257adc8 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -242,7 +242,9 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { /* __GDPR__ "json.schema" : { - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "owner": "aeschli", + "comment": "Measure the use of the Azure resource manager schemas", + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } } */ runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 25aef750a76..4941fced265 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -147,7 +147,7 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "0.5.0", + "@vscode/extension-telemetry": "0.5.1", "request-light": "^0.5.8", "vscode-languageclient": "^7.0.0", "vscode-nls": "^5.0.0" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 98216da36e6..7e56398f062 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.0.tgz#8214171e550393d577fc56326fa986c6800b831b" - integrity sha512-27FsgeVJvC4zVw7Ar3Ub+7vJswDt8RoBFpbgBwf8Xq/B2gaT8G6a+gkw3s2pQmjWGIqyu7TRA8e9rS8/vxv6NQ== +"@vscode/extension-telemetry@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.1.tgz#20150976629663b3d33799a4ad25944a1535f7db" + integrity sha512-cvFq8drxdLRF8KN72WcV4lTEa9GqDiRwy9EbnYuoSCD9Jdk8zHFF49MmACC1qs4R9Ko/C1uMOmeLJmVi8EA0rQ== balanced-match@^1.0.0: version "1.0.0" From 4481afe7d56664fc95376e32c2476eece708ed58 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 06:52:30 -0700 Subject: [PATCH 086/350] Split up link computer cases unit test Part of #149712 --- .../links/terminalUriLinkDetector.test.ts | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts index 949fb57e709..448f145fda2 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts @@ -34,39 +34,44 @@ suite('Workbench - TerminalUriLinkDetector', () => { await assertLinkHelper(text, expected, detector, type); } - test('LinkComputer cases', async () => { - await assertLink(TerminalBuiltinLinkType.Url, 'x = "http://foo.bar";', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = (http://foo.bar);', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = \'http://foo.bar\';', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = http://foo.bar ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = {http://foo.bar};', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, '(see http://foo.bar)', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, '[see http://foo.bar]', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, '{see http://foo.bar}', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, '', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'http://foo.bar', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, '// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', [{ range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' }]); - await assertLink(TerminalBuiltinLinkType.Url, '// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', [{ range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]); - await assertLink(TerminalBuiltinLinkType.Url, '// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', [{ range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' }]); - await assertLink(TerminalBuiltinLinkType.Url, '', [{ range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.', [{ range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.', [{ range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'x = "https://en.wikipedia.org/wiki/Zürich";', [{ range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' }]); - await assertLink(TerminalBuiltinLinkType.Url, '請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', [{ range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]); - await assertLink(TerminalBuiltinLinkType.Url, '(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051)', [{ range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]); - await assertLink(TerminalBuiltinLinkType.LocalFile, 'x = "file:///foo.bar";', [{ range: [[6, 1], [20, 1]], text: 'file:///foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.LocalFile, 'x = "file://c:/foo.bar";', [{ range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.LocalFile, 'x = "file://shares/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.LocalFile, 'x = "file://shäres/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'Some text, then http://www.bing.com.', [{ range: [[17, 1], [35, 1]], text: 'http://www.bing.com' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', [{ range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' }]); - await assertLink(TerminalBuiltinLinkType.Url, '7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', [{ range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'let x = "http://[::1]:5000/connect/token"', [{ range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' }]); - await assertLink(TerminalBuiltinLinkType.Url, '2. Navigate to **https://portal.azure.com**', [{ range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'POST|https://portal.azure.com|2019-12-05|', [{ range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' }]); - await assertLink(TerminalBuiltinLinkType.Url, 'aa https://foo.bar/[this is foo site] aa', [{ range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' }]); - }); + const linkComputerCases: [TerminalBuiltinLinkType, string, (Pick & { range: [number, number][] })[]][] = [ + [TerminalBuiltinLinkType.Url, 'x = "http://foo.bar";', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'x = (http://foo.bar);', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'x = \'http://foo.bar\';', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'x = http://foo.bar ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'x = ;', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'x = {http://foo.bar};', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, '(see http://foo.bar)', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, '[see http://foo.bar]', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, '{see http://foo.bar}', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, '', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'http://foo.bar', [{ range: [[6, 1], [19, 1]], text: 'http://foo.bar' }]], + [TerminalBuiltinLinkType.Url, '// Click here to learn more. https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409', [{ range: [[30, 1], [7, 2]], text: 'https://go.microsoft.com/fwlink/?LinkID=513275&clcid=0x409' }]], + [TerminalBuiltinLinkType.Url, '// Click here to learn more. https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx', [{ range: [[30, 1], [28, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]], + [TerminalBuiltinLinkType.Url, '// https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js', [{ range: [[4, 1], [9, 2]], text: 'https://github.com/projectkudu/kudu/blob/master/Kudu.Core/Scripts/selectNodeVersion.js' }]], + [TerminalBuiltinLinkType.Url, '', [{ range: [[49, 1], [14, 2]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]], + [TerminalBuiltinLinkType.Url, 'For instructions, see https://go.microsoft.com/fwlink/?LinkId=166007.', [{ range: [[23, 1], [68, 1]], text: 'https://go.microsoft.com/fwlink/?LinkId=166007' }]], + [TerminalBuiltinLinkType.Url, 'For instructions, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx.', [{ range: [[23, 1], [21, 2]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx' }]], + [TerminalBuiltinLinkType.Url, 'x = "https://en.wikipedia.org/wiki/Zürich";', [{ range: [[6, 1], [41, 1]], text: 'https://en.wikipedia.org/wiki/Zürich' }]], + [TerminalBuiltinLinkType.Url, '請參閱 http://go.microsoft.com/fwlink/?LinkId=761051。', [{ range: [[8, 1], [53, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]], + [TerminalBuiltinLinkType.Url, '(請參閱 http://go.microsoft.com/fwlink/?LinkId=761051)', [{ range: [[10, 1], [55, 1]], text: 'http://go.microsoft.com/fwlink/?LinkId=761051' }]], + [TerminalBuiltinLinkType.LocalFile, 'x = "file:///foo.bar";', [{ range: [[6, 1], [20, 1]], text: 'file:///foo.bar' }]], + [TerminalBuiltinLinkType.LocalFile, 'x = "file://c:/foo.bar";', [{ range: [[6, 1], [22, 1]], text: 'file://c:/foo.bar' }]], + [TerminalBuiltinLinkType.LocalFile, 'x = "file://shares/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shares/foo.bar' }]], + [TerminalBuiltinLinkType.LocalFile, 'x = "file://shäres/foo.bar";', [{ range: [[6, 1], [26, 1]], text: 'file://shäres/foo.bar' }]], + [TerminalBuiltinLinkType.Url, 'Some text, then http://www.bing.com.', [{ range: [[17, 1], [35, 1]], text: 'http://www.bing.com' }]], + [TerminalBuiltinLinkType.Url, 'let url = `http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items`;', [{ range: [[12, 1], [78, 1]], text: 'http://***/_api/web/lists/GetByTitle(\'Teambuildingaanvragen\')/items' }]], + [TerminalBuiltinLinkType.Url, '7. At this point, ServiceMain has been called. There is no functionality presently in ServiceMain, but you can consult the [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx) to add functionality as desired!', [{ range: [[66, 2], [64, 3]], text: 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms687414(v=vs.85).aspx' }]], + [TerminalBuiltinLinkType.Url, 'let x = "http://[::1]:5000/connect/token"', [{ range: [[10, 1], [40, 1]], text: 'http://[::1]:5000/connect/token' }]], + [TerminalBuiltinLinkType.Url, '2. Navigate to **https://portal.azure.com**', [{ range: [[18, 1], [41, 1]], text: 'https://portal.azure.com' }]], + [TerminalBuiltinLinkType.Url, 'POST|https://portal.azure.com|2019-12-05|', [{ range: [[6, 1], [29, 1]], text: 'https://portal.azure.com' }]], + [TerminalBuiltinLinkType.Url, 'aa https://foo.bar/[this is foo site] aa', [{ range: [[5, 1], [38, 1]], text: 'https://foo.bar/[this is foo site]' }]] + ]; + for (const c of linkComputerCases) { + test('link computer case: `' + c[1] + '`', async () => { + await assertLink(c[0], c[1], c[2]); + }); + } test('should support multiple link results', async () => { await assertLink(TerminalBuiltinLinkType.Url, 'http://foo.bar http://bar.foo', [ From 562c9f6d8b7d1aac3a49aebb6e26fbeabe55dcc0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 16:01:10 +0200 Subject: [PATCH 087/350] register policy definitions from policy configuration --- .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/code/electron-main/main.ts | 6 +- src/vs/code/node/cliProcessMain.ts | 2 +- .../common/configurationService.ts | 4 +- .../configuration/common/configurations.ts | 56 +++++++++---------- .../test/common/configurationService.test.ts | 22 ++++---- .../test/common/policyConfiguration.test.ts | 2 +- .../policy/common/filePolicyService.ts | 17 +++++- .../test/common/userDataSyncClient.ts | 2 +- .../node/remoteExtensionHostAgentCli.ts | 2 +- src/vs/server/node/serverServices.ts | 2 +- .../browser/configurationService.ts | 2 +- 12 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 6b07e7c7ddd..8a2cfbc36dc 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -228,7 +228,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 029a5c98f37..d1563aebe63 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -170,11 +170,13 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) + : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) + : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService); + const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService, logService); services.set(IConfigurationService, configurationService); // Lifecycle diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 184289c296e..f0151458057 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -137,7 +137,7 @@ class CliMain extends Disposable { services.set(IPolicyService, policyService); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService)); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); // Init config diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 3d0422d38c1..a1bd9906ee0 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -12,6 +12,7 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, I import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -31,10 +32,11 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private readonly settingsResource: URI, fileService: IFileService, policyService: IPolicyService, + logService: ILogService, ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index aed37d85e47..9cd8e84917b 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -8,9 +8,11 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; +import { isEmptyObject } from 'vs/base/common/types'; import { addToValueTree, IOverrides, toValuesTree } from 'vs/platform/configuration/common/configuration'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -86,45 +88,41 @@ export class PolicyConfiguration extends Disposable { constructor( private readonly defaultConfiguration: DefaultConfiguration, - @IPolicyService private readonly policyService: IPolicyService + @IPolicyService private readonly policyService: IPolicyService, + @ILogService private readonly logService: ILogService ) { super(); } - // TODO@sandy: make nice - private getPolicyDefinitions(): IStringDictionary { - const result: IStringDictionary = {}; - const configRegistry = Registry.as(Extensions.Configuration); + async initialize(): Promise { + await this.registerPolicyDefinitionsAndUpdate(this.defaultConfiguration.configurationModel.keys, false); + this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.registerPolicyDefinitionsAndUpdate(properties, true))); + return this._configurationModel; + } - for (const configuration of configRegistry.getConfigurations()) { - if (configuration.properties) { - for (const key in configuration.properties) { - const config = configuration.properties[key]; - const policy = config.policy; + private async registerPolicyDefinitionsAndUpdate(properties: string[], trigger: boolean): Promise { + const policyDefinitions: IStringDictionary = {}; + const keys: string[] = []; + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); - if (policy) { - if (config.type !== 'string' && config.type !== 'number') { - console.warn(`Policy ${policy.name} has unsupported type ${config.type}`); - continue; - } - - result[policy.name] = { type: config.type }; - } + for (const key of properties) { + const config = configurationProperties[key]; + const policy = config.policy; + if (policy) { + if (config.type !== 'string' && config.type !== 'number') { + this.logService.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + continue; } + keys.push(key); + policyDefinitions[policy.name] = { type: config.type }; } } - return result; - } - - async initialize(): Promise { - // TODO@sandy: go through registry - await this.policyService.registerPolicyDefinitions(this.getPolicyDefinitions()); - this.update(this.defaultConfiguration.configurationModel.keys, false); - this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - // TODO@sandy: also make sure that policy configurations that are registered after initialize() also call registerPolicyDefinitions() - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.update(properties, true))); - return this._configurationModel; + if (!isEmptyObject(policyDefinitions)) { + await this.policyService.registerPolicyDefinitions(policyDefinitions); + this.update(keys, trigger); + } } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index d49af5f41f6..3b1155bf20a 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -37,7 +37,7 @@ suite('ConfigurationService', () => { test('simple', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -50,7 +50,7 @@ suite('ConfigurationService', () => { test('config gets flattened', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ testworkbench: { @@ -69,7 +69,7 @@ suite('ConfigurationService', () => { test('error case does not explode', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString(',,,,')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string; @@ -79,7 +79,7 @@ suite('ConfigurationService', () => { }); test('missing file does not explode', async () => { - const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); const config = testObject.getValue<{ foo: string }>(); @@ -88,7 +88,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file does not exist', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); return new Promise((c, e) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { @@ -101,7 +101,7 @@ suite('ConfigurationService', () => { }); test('trigger configuration change event when file exists', async () => { - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); @@ -117,7 +117,7 @@ suite('ConfigurationService', () => { test('reloadConfiguration', async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); let config = testObject.getValue<{ foo: string; @@ -156,7 +156,7 @@ suite('ConfigurationService', () => { } }); - let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService())); + let testObject = disposables.add(new ConfigurationService(URI.file('__testFile'), fileService, new NullPolicyService(), new NullLogService())); await testObject.initialize(); let setting = testObject.getValue(); @@ -164,7 +164,7 @@ suite('ConfigurationService', () => { assert.strictEqual(setting.configuration.service.testSetting, 'isSet'); await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "testworkbench.editor.tabs": true }')); - testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); setting = testObject.getValue(); @@ -192,7 +192,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); testObject.initialize(); let res = testObject.inspect('something.missing'); @@ -227,7 +227,7 @@ suite('ConfigurationService', () => { } }); - const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService())); + const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); testObject.initialize(); let res = testObject.inspect('lookup.service.testNullSetting'); diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 784334ffa7c..116322e570d 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -65,7 +65,7 @@ suite('PolicyConfiguration', () => { const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); policyService = new FilePolicyService(policyFile, fileService, new NullLogService()); - testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService)); + testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService, new NullLogService())); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index f3786b547e8..4dadb9a1716 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -30,6 +30,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; + private readonly policyNames: Set = new Set(); private policies = new Map(); private readonly _onDidChange = new Emitter(); @@ -49,9 +50,17 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - // TODO@sandeep respect only registered policy definitions async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - await this.refresh(); + let hasNewPolicies = false; + for (const key of Object.keys(policies)) { + if (!this.policyNames.has(key)) { + hasNewPolicies = true; + this.policyNames.add(key); + } + } + if (hasNewPolicies) { + await this.refresh(); + } return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); } @@ -67,7 +76,9 @@ export class FilePolicyService extends Disposable implements IPolicyService { } for (const key of Object.keys(raw)) { - policies.set(key, raw[key]); + if (this.policyNames.has(key)) { + policies.set(key, raw[key]); + } } } catch (error) { if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 2d888a524c9..12abf923f28 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -87,7 +87,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index bfd5928609d..36c01538db4 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -92,7 +92,7 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService())); + const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); services.set(IConfigurationService, configurationService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index c3ae3e66423..9885e9a1982 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -108,7 +108,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService()); + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService(), logService); services.set(IConfigurationService, configurationService); const extensionHostStatusService = new ExtensionHostStatusService(); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index c4e251483be..26f97a95502 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -114,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService)); + this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; From 8463cd27b3283fda5eeef2e46889ef958ff9710b Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 May 2022 16:06:59 +0200 Subject: [PATCH 088/350] Reduce usages of `editor.deltaDecorations` (#149718) --- .../editor/browser/widget/codeEditorWidget.ts | 4 ++ src/vs/editor/common/editorCommon.ts | 6 ++- .../browser/bracketMatching.ts | 11 ++-- .../colorPicker/browser/colorDetector.ts | 12 ++--- .../browser/colorHoverParticipant.ts | 2 +- .../linkedEditing/browser/linkedEditing.ts | 35 +++++++------ .../browser/unicodeHighlighter.ts | 51 +++++++------------ src/vs/monaco.d.ts | 4 ++ src/vs/workbench/browser/codeeditor.ts | 7 ++- .../browser/breakpointEditorContribution.ts | 25 ++++----- 10 files changed, 79 insertions(+), 78 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 769e04d8b89..801501376ce 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -2179,6 +2179,10 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl return result; } + public has(decoration: IModelDecoration): boolean { + return this._decorationIds.includes(decoration.id); + } + public clear(): void { if (this._decorationIds.length === 0) { // nothing to do diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 8fbe1627bed..4b8f8d58fbc 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -11,7 +11,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation, IModelDeltaDecoration, IModelDecoration } from 'vs/editor/common/model'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IDimension } from 'vs/editor/common/core/dimension'; import { IModelDecorationsChangedEvent } from 'vs/editor/common/textModelEvents'; @@ -538,6 +538,10 @@ export interface IEditorDecorationsCollection { * Get all ranges for decorations. */ getRanges(): Range[]; + /** + * Determine if a decoration is in this collection. + */ + has(decoration: IModelDecoration): boolean; /** * Replace all previous decorations with `newDecorations`. */ diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index 37b38180c3a..0f917f1b5c6 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -13,7 +13,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; 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 { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -105,7 +105,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont private _lastBracketsData: BracketsData[]; private _lastVersionId: number; - private _decorations: string[]; + private readonly _decorations: IEditorDecorationsCollection; private readonly _updateBracketsSoon: RunOnceScheduler; private _matchBrackets: 'never' | 'near' | 'always'; @@ -116,7 +116,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont this._editor = editor; this._lastBracketsData = []; this._lastVersionId = 0; - this._decorations = []; + this._decorations = this._editor.createDecorationsCollection(); this._updateBracketsSoon = this._register(new RunOnceScheduler(() => this._updateBrackets(), 50)); this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); @@ -136,7 +136,6 @@ export class BracketMatchingController extends Disposable implements IEditorCont })); this._register(editor.onDidChangeModel((e) => { this._lastBracketsData = []; - this._decorations = []; this._updateBracketsSoon.schedule(); })); this._register(editor.onDidChangeModelLanguageConfiguration((e) => { @@ -146,7 +145,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont this._register(editor.onDidChangeConfiguration((e) => { if (e.hasChanged(EditorOption.matchBrackets)) { this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); - this._decorations = this._editor.deltaDecorations(this._decorations, []); + this._decorations.clear(); this._lastBracketsData = []; this._lastVersionId = 0; this._updateBracketsSoon.schedule(); @@ -285,7 +284,7 @@ export class BracketMatchingController extends Disposable implements IEditorCont } } - this._decorations = this._editor.deltaDecorations(this._decorations, newDecorations); + this._decorations.set(newDecorations); } private _recomputeBrackets(): void { diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 2a312a7c9b8..cf9a2bfcd47 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -16,7 +16,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IModelDecoration, IModelDeltaDecoration } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; @@ -41,7 +41,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { private _decorationsIds: string[] = []; private _colorDatas = new Map(); - private _colorDecoratorIds: ReadonlySet = new Set(); + private readonly _colorDecoratorIds = this._editor.createDecorationsCollection(); private _isEnabled: boolean; @@ -215,12 +215,12 @@ export class ColorDetector extends Disposable implements IEditorContribution { }); } - this._colorDecoratorIds = new Set(this._editor.deltaDecorations([...this._colorDecoratorIds], decorations)); + this._colorDecoratorIds.set(decorations); } private removeAllDecorations(): void { this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, []); - this._colorDecoratorIds = new Set(this._editor.deltaDecorations([...this._colorDecoratorIds], [])); + this._colorDecoratorIds.clear(); this._colorDecorationClassRefs.clear(); } @@ -241,8 +241,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { return this._colorDatas.get(decorations[0].id)!; } - isColorDecorationId(decorationId: string): boolean { - return this._colorDecoratorIds.has(decorationId); + isColorDecoration(decoration: IModelDecoration): boolean { + return this._colorDecoratorIds.has(decoration); } } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts index 406407f5fd4..adb039eecf6 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts @@ -70,7 +70,7 @@ export class ColorHoverParticipant implements IEditorHoverParticipant this.updateRanges(), this._debounceDuration ?? this._debounceInformation.get(model)); }; const rangeSyncScheduler = new Delayer(0); - const triggerRangeSync = (decorations: string[]) => { - this._rangeSyncTriggerPromise = rangeSyncScheduler.trigger(() => this._syncRanges(decorations)); + const triggerRangeSync = (token: number) => { + this._rangeSyncTriggerPromise = rangeSyncScheduler.trigger(() => this._syncRanges(token)); }; this._localToDispose.add(this._editor.onDidChangeCursorPosition(() => { triggerRangeUpdate(); @@ -156,9 +158,9 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont this._localToDispose.add(this._editor.onDidChangeModelContent((e) => { if (!this._ignoreChangeEvent) { if (this._currentDecorations.length > 0) { - const referenceRange = model.getDecorationRange(this._currentDecorations[0]); + const referenceRange = this._currentDecorations.getRange(0); if (referenceRange && e.changes.every(c => referenceRange.intersectRanges(c.range))) { - triggerRangeSync(this._currentDecorations); + triggerRangeSync(this._syncRangesToken); return; } } @@ -174,15 +176,15 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont this.updateRanges(); } - private _syncRanges(decorations: string[]): void { + private _syncRanges(token: number): void { // dalayed invocation, make sure we're still on - if (!this._editor.hasModel() || decorations !== this._currentDecorations || decorations.length === 0) { + if (!this._editor.hasModel() || token !== this._syncRangesToken || this._currentDecorations.length === 0) { // nothing to do return; } const model = this._editor.getModel(); - const referenceRange = model.getDecorationRange(decorations[0]); + const referenceRange = this._currentDecorations.getRange(0); if (!referenceRange || referenceRange.startLineNumber !== referenceRange.endLineNumber) { return this.clearRanges(); @@ -198,8 +200,8 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont } let edits: ISingleEditOperation[] = []; - for (let i = 1, len = decorations.length; i < len; i++) { - const mirrorRange = model.getDecorationRange(decorations[i]); + for (let i = 1, len = this._currentDecorations.length; i < len; i++) { + const mirrorRange = this._currentDecorations.getRange(i); if (!mirrorRange) { continue; } @@ -255,7 +257,7 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont public clearRanges(): void { this._visibleContextKey.set(false); - this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []); + this._currentDecorations.clear(); if (this._currentRequest) { this._currentRequest.cancel(); this._currentRequest = null; @@ -290,8 +292,8 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont if (position.equals(this._currentRequestPosition)) { return; // same position } - if (this._currentDecorations && this._currentDecorations.length > 0) { - const range = model.getDecorationRange(this._currentDecorations[0]); + if (this._currentDecorations.length > 0) { + const range = this._currentDecorations.getRange(0); if (range && range.containsPosition(position)) { return; // just moving inside the existing primary range } @@ -341,7 +343,8 @@ export class LinkedEditingContribution extends Disposable implements IEditorCont const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: LinkedEditingContribution.DECORATION })); this._visibleContextKey.set(true); - this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations); + this._currentDecorations.set(decorations); + this._syncRangesToken++; // cancel any pending syncRanges call } catch (err) { if (!isCancellationError(err)) { onUnexpectedError(err); diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index 326e6567180..44b682dea38 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -179,9 +179,9 @@ export class UnicodeHighlighter extends Disposable implements IEditorContributio } } - public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null { + public getDecorationInfo(decoration: IModelDecoration): UnicodeHighlighterDecorationInfo | null { if (this._highlighter) { - return this._highlighter.getDecorationInfo(decorationId); + return this._highlighter.getDecorationInfo(decoration); } return null; } @@ -214,7 +214,7 @@ function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptio class DocumentUnicodeHighlighter extends Disposable { private readonly _model: ITextModel = this._editor.getModel(); private readonly _updateSoon: RunOnceScheduler; - private _decorationIds = new Set(); + private _decorations = this._editor.createDecorationsCollection(); constructor( private readonly _editor: IActiveCodeEditor, @@ -233,7 +233,7 @@ class DocumentUnicodeHighlighter extends Disposable { } public override dispose() { - this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), [])); + this._decorations.clear(); super.dispose(); } @@ -243,7 +243,7 @@ class DocumentUnicodeHighlighter extends Disposable { } if (!this._model.mightContainNonBasicASCII()) { - this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), [])); + this._decorations.clear(); return; } @@ -271,31 +271,21 @@ class DocumentUnicodeHighlighter extends Disposable { }); } } - this._decorationIds = new Set(this._editor.deltaDecorations( - Array.from(this._decorationIds), - decorations - )); + this._decorations.set(decorations); }); } - public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null { - if (!this._decorationIds.has(decorationId)) { + public getDecorationInfo(decoration: IModelDecoration): UnicodeHighlighterDecorationInfo | null { + if (!this._decorations.has(decoration)) { return null; } const model = this._editor.getModel(); - const range = model.getDecorationRange(decorationId)!; - const decoration = { - range: range, - options: Decorations.instance.getDecorationFromOptions(this._options), - id: decorationId, - ownerId: 0, - }; if ( !isModelDecorationVisible(model, decoration) ) { return null; } - const text = model.getValueInRange(range); + const text = model.getValueInRange(decoration.range); return { reason: computeReason(text, this._options)!, inComment: isModelDecorationInComment(model, decoration), @@ -308,7 +298,7 @@ class ViewportUnicodeHighlighter extends Disposable { private readonly _model: ITextModel = this._editor.getModel(); private readonly _updateSoon: RunOnceScheduler; - private _decorationIds = new Set(); + private readonly _decorations = this._editor.createDecorationsCollection(); constructor( private readonly _editor: IActiveCodeEditor, @@ -336,7 +326,7 @@ class ViewportUnicodeHighlighter extends Disposable { } public override dispose() { - this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), [])); + this._decorations.clear(); super.dispose(); } @@ -346,7 +336,7 @@ class ViewportUnicodeHighlighter extends Disposable { } if (!this._model.mightContainNonBasicASCII()) { - this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), [])); + this._decorations.clear(); return; } @@ -379,22 +369,15 @@ class ViewportUnicodeHighlighter extends Disposable { } this._updateState(totalResult); - this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations)); + this._decorations.set(decorations); } - public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null { - if (!this._decorationIds.has(decorationId)) { + public getDecorationInfo(decoration: IModelDecoration): UnicodeHighlighterDecorationInfo | null { + if (!this._decorations.has(decoration)) { return null; } const model = this._editor.getModel(); - const range = model.getDecorationRange(decorationId)!; - const text = model.getValueInRange(range); - const decoration = { - range: range, - options: Decorations.instance.getDecorationFromOptions(this._options), - id: decorationId, - ownerId: 0, - }; + const text = model.getValueInRange(decoration.range); if (!isModelDecorationVisible(model, decoration)) { return null; } @@ -449,7 +432,7 @@ export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipa let index = 300; for (const d of lineDecorations) { - const highlightInfo = unicodeHighlighter.getDecorationInfo(d.id); + const highlightInfo = unicodeHighlighter.getDecorationInfo(d); if (!highlightInfo) { continue; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d96e306b525..8390e8adca5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2553,6 +2553,10 @@ declare namespace monaco.editor { * Get all ranges for decorations. */ getRanges(): Range[]; + /** + * Determine if a decoration is in this collection. + */ + has(decoration: IModelDecoration): boolean; /** * Replace all previous decorations with `newDecorations`. */ diff --git a/src/vs/workbench/browser/codeeditor.ts b/src/vs/workbench/browser/codeeditor.ts index bc251d1bda1..0b23958dfed 100644 --- a/src/vs/workbench/browser/codeeditor.ts +++ b/src/vs/workbench/browser/codeeditor.ts @@ -47,8 +47,11 @@ export class RangeHighlightDecorations extends Disposable { } removeHighlightRange() { - if (this.editor?.getModel() && this.rangeHighlightDecorationId) { - this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); + if (this.editor && this.rangeHighlightDecorationId) { + const decorationId = this.rangeHighlightDecorationId; + this.editor.changeDecorations((accessor) => { + accessor.removeDecoration(decorationId); + }); this._onHighlightRemoved.fire(); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 4eab6791a7f..943b41f8eb5 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -11,7 +11,7 @@ import severity from 'vs/base/common/severity'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; +import { IModelDecorationOptions, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -158,7 +158,7 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio export class BreakpointEditorContribution implements IBreakpointEditorContribution { - private breakpointHintDecoration: string[] = []; + private breakpointHintDecoration: string | null = null; private breakpointWidget: BreakpointWidget | undefined; private breakpointWidgetVisible: IContextKey; private toDispose: IDisposable[] = []; @@ -443,20 +443,21 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi } private ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber: number): void { - const newDecoration: IModelDeltaDecoration[] = []; - if (showBreakpointHintAtLineNumber !== -1) { - newDecoration.push({ - options: breakpointHelperDecoration, - range: { + this.editor.changeDecorations((accessor) => { + if (this.breakpointHintDecoration) { + accessor.removeDecoration(this.breakpointHintDecoration); + this.breakpointHintDecoration = null; + } + if (showBreakpointHintAtLineNumber !== -1) { + this.breakpointHintDecoration = accessor.addDecoration({ startLineNumber: showBreakpointHintAtLineNumber, startColumn: 1, endLineNumber: showBreakpointHintAtLineNumber, endColumn: 1 - } - }); - } - - this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); + }, breakpointHelperDecoration + ); + } + }); } private async setDecorations(): Promise { From 6ec996a9b784acaed8d5f37bbd80a04b0d26b0c4 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 May 2022 16:21:45 +0200 Subject: [PATCH 089/350] Adopt `runWithFakedTimers` to speed up tests (#149712) (#149720) --- .../api/test/browser/extHostTreeViews.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index 058a03faa02..48e90584747 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -19,6 +19,7 @@ import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from 'vs/workbenc import { NullLogService } from 'vs/platform/log/common/log'; import type { IDisposable } from 'vs/base/common/lifecycle'; import { nullExtensionDescription as extensionsDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; suite('ExtHostTreeView', function () { @@ -250,15 +251,17 @@ suite('ExtHostTreeView', function () { }); async function runWithEventMerging(action: (resolve: () => void) => void) { - await new Promise((resolve) => { - let subscription: IDisposable | undefined = undefined; - subscription = target.onRefresh.event(() => { - subscription!.dispose(); - resolve(); + await runWithFakedTimers({}, async () => { + await new Promise((resolve) => { + let subscription: IDisposable | undefined = undefined; + subscription = target.onRefresh.event(() => { + subscription!.dispose(); + resolve(); + }); + onDidChangeTreeNode.fire(getNode('b')); }); - onDidChangeTreeNode.fire(getNode('b')); + await new Promise(action); }); - await new Promise(action); } test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => { From 3f092b812dbcababef496899f83a52bbdf014e88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 16:29:25 +0200 Subject: [PATCH 090/350] fix tests --- .../configuration/common/configurations.ts | 15 +++++-- .../test/common/policyConfiguration.test.ts | 44 +++++++++---------- .../configurationEditingService.test.ts | 4 +- .../test/browser/configurationService.test.ts | 3 +- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 9cd8e84917b..1fc471bb739 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -108,19 +108,26 @@ export class PolicyConfiguration extends Disposable { for (const key of properties) { const config = configurationProperties[key]; - const policy = config.policy; - if (policy) { + if (!config) { + // Config is removed. So add it to the list if in case it was registered as policy before + keys.push(key); + continue; + } + if (config.policy) { if (config.type !== 'string' && config.type !== 'number') { - this.logService.warn(`Policy ${policy.name} has unsupported type ${config.type}`); + this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`); continue; } keys.push(key); - policyDefinitions[policy.name] = { type: config.type }; + policyDefinitions[config.policy.name] = { type: config.type }; } } if (!isEmptyObject(policyDefinitions)) { await this.policyService.registerPolicyDefinitions(policyDefinitions); + } + + if (keys.length) { this.update(keys, trigger); } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 116322e570d..ba2aa5d527e 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -33,16 +33,16 @@ suite('PolicyConfiguration', () => { 'type': 'object', 'properties': { 'policy.settingA': { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueA', policy: { name: 'PolicySettingA', minimumVersion: '1.0.0', } }, 'policy.settingB': { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueB', policy: { name: 'PolicySettingB', minimumVersion: '1.0.0', @@ -71,12 +71,12 @@ suite('PolicyConfiguration', () => { teardown(() => disposables.clear()); test('initialize: with policies', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); @@ -95,44 +95,44 @@ suite('PolicyConfiguration', () => { }); test('initialize: with policies but not registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); await testObject.initialize(); const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); + assert.strictEqual(acutal.getValue('policy.settingB'), 'policyValueB'); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is added', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false, 'PolicySettingB': false, 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), false); - assert.strictEqual(acutal.getValue('policy.settingB'), false); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); + assert.strictEqual(acutal.getValue('policy.settingB'), 'policyValueB'); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA', 'policy.settingB']); assert.deepStrictEqual(acutal.overrides, []); }); test('change: when policy is updated', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': true }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingA'), true); + assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueAChanged'); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); assert.deepStrictEqual(acutal.keys, ['policy.settingA']); @@ -140,7 +140,7 @@ suite('PolicyConfiguration', () => { }); test('change: when policy is removed', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); @@ -156,13 +156,13 @@ suite('PolicyConfiguration', () => { }); test('change: when policy setting is registered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingC': 'policyValueC' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); policyConfigurationNode.properties!['policy.settingC'] = { - 'type': 'boolean', - 'default': true, + 'type': 'string', + 'default': 'defaultValueC', policy: { name: 'PolicySettingC', minimumVersion: '1.0.0', @@ -172,7 +172,7 @@ suite('PolicyConfiguration', () => { await promise; const acutal = testObject.configurationModel; - assert.strictEqual(acutal.getValue('policy.settingC'), false); + assert.strictEqual(acutal.getValue('policy.settingC'), 'policyValueC'); assert.strictEqual(acutal.getValue('policy.settingA'), undefined); assert.strictEqual(acutal.getValue('policy.settingB'), undefined); assert.strictEqual(acutal.getValue('nonPolicy.setting'), undefined); @@ -181,7 +181,7 @@ suite('PolicyConfiguration', () => { }); test('change: when policy setting is deregistered', async () => { - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': false }))); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 47d3e6b8706..727e4328c1f 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -41,7 +41,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; -import { NullPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -109,7 +109,7 @@ suite('ConfigurationEditingService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); await workspaceService.initialize({ id: hash(workspaceFolder.toString()).toString(16), uri: workspaceFolder diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index b623dcdb5fc..9cc61d7deca 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -45,6 +45,7 @@ import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remot import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; +import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -744,7 +745,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); From 30f5b296abe2041e05edc9f31457324302123eab Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 17 May 2022 16:53:50 +0200 Subject: [PATCH 091/350] make `getWordAtText`-config configurable and use lower budget for speedy tests --- src/vs/editor/common/core/wordHelper.ts | 28 +++++++++++++++++-- .../test/browser/extHostDocumentData.test.ts | 23 ++++++++++----- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/common/core/wordHelper.ts b/src/vs/editor/common/core/wordHelper.ts index 9f98fe1a524..a4051dc26e8 100644 --- a/src/vs/editor/common/core/wordHelper.ts +++ b/src/vs/editor/common/core/wordHelper.ts @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Iterable } from 'vs/base/common/iterator'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; + export const USUAL_WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'; /** @@ -71,13 +75,31 @@ export function ensureValidWordDefinition(wordDefinition?: RegExp | null): RegEx return result; } -const _defaultConfig = { + +export interface IGetWordAtTextConfig { + maxLen: number; + windowSize: number; + timeBudget: number; +} + + +const _defaultConfig = new LinkedList(); +_defaultConfig.unshift({ maxLen: 1000, windowSize: 15, timeBudget: 150 -}; +}); -export function getWordAtText(column: number, wordDefinition: RegExp, text: string, textOffset: number, config = _defaultConfig): IWordAtPosition | null { +export function setDefaultGetWordAtTextConfig(value: IGetWordAtTextConfig) { + const rm = _defaultConfig.unshift(value); + return toDisposable(rm); +} + +export function getWordAtText(column: number, wordDefinition: RegExp, text: string, textOffset: number, config?: IGetWordAtTextConfig): IWordAtPosition | null { + + if (!config) { + config = Iterable.first(_defaultConfig)!; + } if (text.length > config.maxLen) { // don't throw strings that long at the regexp diff --git a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index a3cada12b00..a457ca659ca 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -12,6 +12,7 @@ import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protoc import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { mock } from 'vs/base/test/common/mock'; import * as perfData from './extHostDocumentData.test.perf-data'; +import { setDefaultGetWordAtTextConfig } from 'vs/editor/common/core/wordHelper'; suite('ExtHostDocumentData', () => { @@ -316,14 +317,22 @@ suite('ExtHostDocumentData', () => { perfData._$_$_expensive ], '\n', 1, 'text', false); - let range = data.document.getWordRangeAtPosition(new Position(0, 1_177_170), regex)!; - assert.strictEqual(range, undefined); + // this test only ensures that we eventually give and timeout (when searching "funny" words and long lines) + // for the sake of speedy tests we lower the timeBudget here + const config = setDefaultGetWordAtTextConfig({ maxLen: 1000, windowSize: 15, timeBudget: 30 }); + try { + let range = data.document.getWordRangeAtPosition(new Position(0, 1_177_170), regex)!; + assert.strictEqual(range, undefined); - const pos = new Position(0, 1177170); - range = data.document.getWordRangeAtPosition(pos)!; - assert.ok(range); - assert.ok(range.contains(pos)); - assert.strictEqual(data.document.getText(range), 'TaskDefinition'); + const pos = new Position(0, 1177170); + range = data.document.getWordRangeAtPosition(pos)!; + assert.ok(range); + assert.ok(range.contains(pos)); + assert.strictEqual(data.document.getText(range), 'TaskDefinition'); + + } finally { + config.dispose(); + } }); test('Rename popup sometimes populates with text on the left side omitted #96013', function () { From 94a16a85aae7c2c9ad94cbd9df03d058e565b17b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 17:01:21 +0200 Subject: [PATCH 092/350] - faster unit tests use faketimer - little refactoring --- .../configuration/common/configurations.ts | 10 +++----- .../test/common/policyConfiguration.test.ts | 25 ++++++++++++------- .../configurationEditingService.test.ts | 9 ++++--- .../test/browser/configurationService.test.ts | 9 ++++--- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 1fc471bb739..6099234ef8e 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -95,13 +95,13 @@ export class PolicyConfiguration extends Disposable { } async initialize(): Promise { - await this.registerPolicyDefinitionsAndUpdate(this.defaultConfiguration.configurationModel.keys, false); + this.update(await this.registerPolicyDefinitions(this.defaultConfiguration.configurationModel.keys), false); this._register(this.policyService.onDidChange(policyNames => this.onDidChangePolicies(policyNames))); - this._register(this.defaultConfiguration.onDidChangeConfiguration(({ properties }) => this.registerPolicyDefinitionsAndUpdate(properties, true))); + this._register(this.defaultConfiguration.onDidChangeConfiguration(async ({ properties }) => this.update(await this.registerPolicyDefinitions(properties), true))); return this._configurationModel; } - private async registerPolicyDefinitionsAndUpdate(properties: string[], trigger: boolean): Promise { + private async registerPolicyDefinitions(properties: string[]): Promise { const policyDefinitions: IStringDictionary = {}; const keys: string[] = []; const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); @@ -127,9 +127,7 @@ export class PolicyConfiguration extends Disposable { await this.policyService.registerPolicyDefinitions(policyDefinitions); } - if (keys.length) { - this.update(keys, trigger); - } + return keys; } private onDidChangePolicies(policyNames: readonly PolicyName[]): void { diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index ba2aa5d527e..d5b31e5516d 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -18,6 +18,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { deepClone } from 'vs/base/common/objects'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; suite('PolicyConfiguration', () => { @@ -111,9 +112,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA', 'PolicySettingB': 'policyValueB', 'PolicySettingC': 'policyValueC' }))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueA'); @@ -127,9 +130,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueAChanged' }))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), 'policyValueAChanged'); @@ -143,9 +148,11 @@ suite('PolicyConfiguration', () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); await testObject.initialize(); - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({}))); + await promise; + }); const acutal = testObject.configurationModel; assert.strictEqual(acutal.getValue('policy.settingA'), undefined); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 727e4328c1f..995442cd8b8 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -42,6 +42,7 @@ import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/worksp import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -197,9 +198,11 @@ suite('ConfigurationEditingService', () => { }); test('errors cases - ERROR_POLICY_CONFIGURATION', async () => { - const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); - await promise; + await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(instantiationService.get(IConfigurationService).onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationEditing.service.policySetting": "policyValue" }')); + await promise; + }); try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.policySetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 9cc61d7deca..d36be57c247 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -46,6 +46,7 @@ import { hash } from 'vs/base/common/hash'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -968,9 +969,11 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('policy value override all', async () => { - const promise = Event.toPromise(testObject.onDidChangeConfiguration); - await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); - const result = await promise; + const result = await runWithFakedTimers({ useFakeTimers: true }, async () => { + const promise = Event.toPromise(testObject.onDidChangeConfiguration); + await fileService.writeFile(environmentService.policyFile!, VSBuffer.fromString('{ "configurationService.folder.policySetting": "policyValue" }')); + return promise; + }); assert.deepStrictEqual(result.affectedKeys, ['configurationService.folder.policySetting']); assert.strictEqual(testObject.getValue('configurationService.folder.policySetting'), 'policyValue'); assert.strictEqual(testObject.inspect('configurationService.folder.policySetting').policyValue, 'policyValue'); From 9acfe28a460b76a353a3ee577ae63c0160fef1ac Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 May 2022 17:23:23 +0200 Subject: [PATCH 093/350] Adopt `runWithFakedTimers` to speed up tests (for #149712) (#149724) --- .../common/userDataAutoSyncService.test.ts | 599 +++++++++--------- 1 file changed, 314 insertions(+), 285 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 6657810f7d6..134fb2e963b 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -8,6 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; @@ -31,401 +32,429 @@ suite('UserDataAutoSyncService', () => { teardown(() => disposableStore.clear()); test('test auto sync with sync resource change triggers sync', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); - // Sync once and reset requests - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - target.reset(); + // Sync once and reset requests + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + target.reset(); - const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Trigger auto sync with settings change - await testObject.triggerSync([SyncResource.Settings], false, false); + // Trigger auto sync with settings change + await testObject.triggerSync([SyncResource.Settings], false, false); - // Filter out machine requests - const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); + // Filter out machine requests + const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); - // Make sure only one manifest request is made - assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + // Make sure only one manifest request is made + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + }); }); test('test auto sync with sync resource change triggers sync for every change', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); - // Sync once and reset requests - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - target.reset(); + // Sync once and reset requests + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + target.reset(); - const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Trigger auto sync with settings change multiple times - for (let counter = 0; counter < 2; counter++) { - await testObject.triggerSync([SyncResource.Settings], false, false); - } + // Trigger auto sync with settings change multiple times + for (let counter = 0; counter < 2; counter++) { + await testObject.triggerSync([SyncResource.Settings], false, false); + } - // Filter out machine requests - const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); + // Filter out machine requests + const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); - assert.deepStrictEqual(actual, [ - { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } } - ]); + assert.deepStrictEqual(actual, [ + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } } + ]); + }); }); test('test auto sync with non sync resource change triggers sync', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); - // Sync once and reset requests - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - target.reset(); + // Sync once and reset requests + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + target.reset(); - const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Trigger auto sync with window focus once - await testObject.triggerSync(['windowFocus'], true, false); + // Trigger auto sync with window focus once + await testObject.triggerSync(['windowFocus'], true, false); - // Filter out machine requests - const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); + // Filter out machine requests + const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); - // Make sure only one manifest request is made - assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + // Make sure only one manifest request is made + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + }); }); test('test auto sync with non sync resource change does not trigger continuous syncs', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); - // Sync once and reset requests - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - target.reset(); + // Sync once and reset requests + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + target.reset(); - const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + const testObject: UserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Trigger auto sync with window focus multiple times - for (let counter = 0; counter < 2; counter++) { - await testObject.triggerSync(['windowFocus'], true, false); - } + // Trigger auto sync with window focus multiple times + for (let counter = 0; counter < 2; counter++) { + await testObject.triggerSync(['windowFocus'], true, false); + } - // Filter out machine requests - const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); + // Filter out machine requests + const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`)); - // Make sure only one manifest request is made - assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + // Make sure only one manifest request is made + assert.deepStrictEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]); + }); }); test('test first auto sync requests', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.sync(); - - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Machines - { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} }, - // Settings - { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } }, - // Keybindings - { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } }, - // Snippets - { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, - // Tasks - { type: 'GET', url: `${target.url}/v1/resource/tasks/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/tasks`, headers: { 'If-Match': '0' } }, - // Global state - { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, - // Extensions - { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Machines - { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '0' } } - ]); + await testObject.sync(); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Machines + { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} }, + // Settings + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } }, + // Keybindings + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } }, + // Snippets + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } }, + // Tasks + { type: 'GET', url: `${target.url}/v1/resource/tasks/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/tasks`, headers: { 'If-Match': '0' } }, + // Global state + { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, + { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } }, + // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + // Machines + { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '0' } } + ]); + }); }); test('test further auto sync requests without changes', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Sync once and reset requests - await testObject.sync(); - target.reset(); + // Sync once and reset requests + await testObject.sync(); + target.reset(); - await testObject.sync(); - - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } } - ]); + await testObject.sync(); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } } + ]); + }); }); test('test further auto sync requests with changes', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Sync once and reset requests - await testObject.sync(); - target.reset(); + // Sync once and reset requests + await testObject.sync(); + target.reset(); - // Do changes in the client - const fileService = client.instantiationService.get(IFileService); - const environmentService = client.instantiationService.get(IEnvironmentService); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); - await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); - await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); - await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); - await testObject.sync(); - - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, - // Settings - { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, - // Keybindings - { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, - // Snippets - { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, - // Global state - { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, - ]); + // Do changes in the client + const fileService = client.instantiationService.get(IFileService); + const environmentService = client.instantiationService.get(IEnvironmentService); + await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); + await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); + await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`)); + await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); + await testObject.sync(); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, + // Settings + { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, + // Keybindings + { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, + // Snippets + { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, + // Global state + { type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } }, + ]); + }); }); test('test auto sync send execution id header', async () => { - // Setup the client - const target = new UserDataSyncTestServer(); - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); + await runWithFakedTimers({}, async () => { + // Setup the client + const target = new UserDataSyncTestServer(); + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(client.instantiationService.createInstance(TestUserDataAutoSyncService)); - // Sync once and reset requests - await testObject.sync(); - target.reset(); + // Sync once and reset requests + await testObject.sync(); + target.reset(); - await testObject.sync(); + await testObject.sync(); - for (const request of target.requestsWithAllHeaders) { - const hasExecutionIdHeader = request.headers && request.headers['X-Execution-Id'] && request.headers['X-Execution-Id'].length > 0; - if (request.url.startsWith(`${target.url}/v1/resource/machines`)) { - assert.ok(!hasExecutionIdHeader, `Should not have execution header: ${request.url}`); - } else { - assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`); + for (const request of target.requestsWithAllHeaders) { + const hasExecutionIdHeader = request.headers && request.headers['X-Execution-Id'] && request.headers['X-Execution-Id'].length > 0; + if (request.url.startsWith(`${target.url}/v1/resource/machines`)) { + assert.ok(!hasExecutionIdHeader, `Should not have execution header: ${request.url}`); + } else { + assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`); + } } - } - + }); }); test('test delete on one client throws turned off error on other client while syncing', async () => { - const target = new UserDataSyncTestServer(); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(); - // Set up and sync from the client - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + // Set up and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.sync(); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + await testObject.sync(); - // Reset from the first client - await client.instantiationService.get(IUserDataSyncService).reset(); + // Reset from the first client + await client.instantiationService.get(IUserDataSyncService).reset(); - // Sync from the test client - target.reset(); + // Sync from the test client + target.reset(); - const errorPromise = Event.toPromise(testObject.onError); - await testObject.sync(); + const errorPromise = Event.toPromise(testObject.onError); + await testObject.sync(); - const e = await errorPromise; - assert.ok(e instanceof UserDataAutoSyncError); - assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, - // Machine - { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } }, - ]); + const e = await errorPromise; + assert.ok(e instanceof UserDataAutoSyncError); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, + // Machine + { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } }, + ]); + }); }); test('test disabling the machine turns off sync', async () => { - const target = new UserDataSyncTestServer(); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.sync(); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + await testObject.sync(); - // Disable current machine - const userDataSyncMachinesService = testClient.instantiationService.get(IUserDataSyncMachinesService); - const machines = await userDataSyncMachinesService.getMachines(); - const currentMachine = machines.find(m => m.isCurrent)!; - await userDataSyncMachinesService.setEnablements([[currentMachine.id, false]]); + // Disable current machine + const userDataSyncMachinesService = testClient.instantiationService.get(IUserDataSyncMachinesService); + const machines = await userDataSyncMachinesService.getMachines(); + const currentMachine = machines.find(m => m.isCurrent)!; + await userDataSyncMachinesService.setEnablements([[currentMachine.id, false]]); - target.reset(); + target.reset(); - const errorPromise = Event.toPromise(testObject.onError); - await testObject.sync(); + const errorPromise = Event.toPromise(testObject.onError); + await testObject.sync(); - const e = await errorPromise; - assert.ok(e instanceof UserDataAutoSyncError); - assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, - // Machine - { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '2' } }, - { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '2' } }, - ]); + const e = await errorPromise; + assert.ok(e instanceof UserDataAutoSyncError); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TurnedOff); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, + // Machine + { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '2' } }, + { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '2' } }, + ]); + }); }); test('test removing the machine adds machine back', async () => { - const target = new UserDataSyncTestServer(); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.sync(); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + await testObject.sync(); - // Remove current machine - await testClient.instantiationService.get(IUserDataSyncMachinesService).removeCurrentMachine(); + // Remove current machine + await testClient.instantiationService.get(IUserDataSyncMachinesService).removeCurrentMachine(); - target.reset(); + target.reset(); - await testObject.sync(); - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, - // Machine - { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '2' } }, - ]); + await testObject.sync(); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, + // Machine + { type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '2' } }, + ]); + }); }); test('test creating new session from one client throws session expired error on another client while syncing', async () => { - const target = new UserDataSyncTestServer(); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(); - // Set up and sync from the client - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + // Set up and sync from the client + const client = disposableStore.add(new UserDataSyncClient(target)); + await client.setUp(); + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.sync(); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + await testObject.sync(); - // Reset from the first client - await client.instantiationService.get(IUserDataSyncService).reset(); + // Reset from the first client + await client.instantiationService.get(IUserDataSyncService).reset(); - // Sync again from the first client to create new session - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); + // Sync again from the first client to create new session + await (await client.instantiationService.get(IUserDataSyncService).createSyncTask(null)).run(); - // Sync from the test client - target.reset(); + // Sync from the test client + target.reset(); - const errorPromise = Event.toPromise(testObject.onError); - await testObject.sync(); + const errorPromise = Event.toPromise(testObject.onError); + await testObject.sync(); - const e = await errorPromise; - assert.ok(e instanceof UserDataAutoSyncError); - assert.deepStrictEqual((e).code, UserDataSyncErrorCode.SessionExpired); - assert.deepStrictEqual(target.requests, [ - // Manifest - { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, - // Machine - { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } }, - ]); + const e = await errorPromise; + assert.ok(e instanceof UserDataAutoSyncError); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.SessionExpired); + assert.deepStrictEqual(target.requests, [ + // Manifest + { type: 'GET', url: `${target.url}/v1/manifest`, headers: { 'If-None-Match': '1' } }, + // Machine + { type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } }, + ]); + }); }); test('test rate limit on server', async () => { - const target = new UserDataSyncTestServer(5); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(5); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - const errorPromise = Event.toPromise(testObject.onError); - while (target.requests.length < 5) { - await testObject.sync(); - } + const errorPromise = Event.toPromise(testObject.onError); + while (target.requests.length < 5) { + await testObject.sync(); + } - const e = await errorPromise; - assert.ok(e instanceof UserDataSyncStoreError); - assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TooManyRequests); + const e = await errorPromise; + assert.ok(e instanceof UserDataSyncStoreError); + assert.deepStrictEqual((e).code, UserDataSyncErrorCode.TooManyRequests); + }); }); test('test auto sync is suspended when server donot accepts requests', async () => { - const target = new UserDataSyncTestServer(5, 1); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(5, 1); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - while (target.requests.length < 5) { + while (target.requests.length < 5) { + await testObject.sync(); + } + + target.reset(); await testObject.sync(); - } - target.reset(); - await testObject.sync(); - - assert.deepStrictEqual(target.requests, []); + assert.deepStrictEqual(target.requests, []); + }); }); test('test cache control header with no cache is sent when triggered with disable cache option', async () => { - const target = new UserDataSyncTestServer(5, 1); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(5, 1); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.triggerSync(['some reason'], true, true); - assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); + await testObject.triggerSync(['some reason'], true, true); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], 'no-cache'); + }); }); test('test cache control header is not sent when triggered without disable cache option', async () => { - const target = new UserDataSyncTestServer(5, 1); + await runWithFakedTimers({}, async () => { + const target = new UserDataSyncTestServer(5, 1); - // Set up and sync from the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); + // Set up and sync from the test client + const testClient = disposableStore.add(new UserDataSyncClient(target)); + await testClient.setUp(); + const testObject: TestUserDataAutoSyncService = disposableStore.add(testClient.instantiationService.createInstance(TestUserDataAutoSyncService)); - await testObject.triggerSync(['some reason'], true, false); - assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); + await testObject.triggerSync(['some reason'], true, false); + assert.strictEqual(target.requestsWithAllHeaders[0].headers!['Cache-Control'], undefined); + }); }); }); From f427aed437f70906cd0eb7d56ca362307b817923 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 17 May 2022 08:26:16 -0700 Subject: [PATCH 094/350] add additional telemetry for connection latency and duration (#149398) * server: add telemetry for initial connection time * feat: measure approximate rtt to the remote extension host * fixup! remove unused property * fixup! address pr comments --- .../common/remoteTelemetryChannel.ts | 4 ++ .../remote/common/remote.contribution.ts | 44 +++++++++++++++++++ .../common/abstractRemoteAgentService.ts | 34 +++++++++++++- .../common/remoteAgentEnvironmentChannel.ts | 4 ++ .../remote/common/remoteAgentService.ts | 7 +++ .../test/browser/workbenchTestServices.ts | 1 + 6 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/telemetry/common/remoteTelemetryChannel.ts b/src/vs/platform/telemetry/common/remoteTelemetryChannel.ts index 66bd712a808..97fb3ec4989 100644 --- a/src/vs/platform/telemetry/common/remoteTelemetryChannel.ts +++ b/src/vs/platform/telemetry/common/remoteTelemetryChannel.ts @@ -45,6 +45,10 @@ export class ServerTelemetryChannel extends Disposable implements IServerChannel return Promise.resolve(); } + + case 'ping': { + return; + } } // Command we cannot handle so we throw an error throw new Error(`IPC Command ${command} not found`); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index eb62a41277f..a2d8b9671ae 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -31,6 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; +import { timeout } from 'vs/base/common/async'; export class LabelContribution implements IWorkbenchContribution { constructor( @@ -165,6 +166,9 @@ class RemoteInvalidWorkspaceDetector extends Disposable implements IWorkbenchCon } } +const EXT_HOST_LATENCY_SAMPLES = 5; +const EXT_HOST_LATENCY_DELAY = 2_000; + class InitialRemoteConnectionHealthContribution implements IWorkbenchContribution { constructor( @@ -185,17 +189,22 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio owner: 'alexdima'; comment: 'The initial connection succeeded'; web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; }; type RemoteConnectionSuccessEvent = { web: boolean; + connectionTimeMs: number | undefined; remoteName: string | undefined; }; this._telemetryService.publicLog2('remoteConnectionSuccess', { web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), remoteName: getRemoteName(this._environmentService.remoteAuthority) }); + await this._measureExtHostLatency(); + } catch (err) { type RemoteConnectionFailureClassification = { @@ -203,21 +212,56 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio comment: 'The initial connection failed'; web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; }; type RemoteConnectionFailureEvent = { web: boolean; remoteName: string | undefined; + connectionTimeMs: number | undefined; message: string; }; this._telemetryService.publicLog2('remoteConnectionFailure', { web: isWeb, + connectionTimeMs: await this._remoteAgentService.getConnection()?.getInitialConnectionTimeMs(), remoteName: getRemoteName(this._environmentService.remoteAuthority), message: err ? err.message : '' }); } } + + private async _measureExtHostLatency() { + // Get the minimum latency, since latency spikes could be caused by a busy extension host. + let bestLatency = Infinity; + for (let i = 0; i < EXT_HOST_LATENCY_SAMPLES; i++) { + const rtt = await this._remoteAgentService.getRoundTripTime(); + if (rtt === undefined) { + return; + } + bestLatency = Math.min(bestLatency, rtt / 2); + await timeout(EXT_HOST_LATENCY_DELAY); + } + + type RemoteConnectionFailureClassification = { + owner: 'connor4312'; + comment: 'The latency to the remote extension host'; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; + latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; + }; + type RemoteConnectionFailureEvent = { + web: boolean; + remoteName: string | undefined; + latencyMs: number; + }; + + this._telemetryService.publicLog2('remoteConnectionFailure', { + web: isWeb, + remoteName: getRemoteName(this._environmentService.remoteAuthority), + latencyMs: bestLatency + }); + } } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 3268cc37382..0eba0de6b00 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection'; +import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, ManagementPersistentConnection, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection'; import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; @@ -125,6 +125,17 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I ); } + getRoundTripTime(): Promise { + return this._withTelemetryChannel( + async channel => { + const start = Date.now(); + await RemoteExtensionEnvironmentChannelClient.ping(channel); + return Date.now() - start; + }, + undefined + ); + } + private _withChannel(callback: (channel: IChannel, connection: IRemoteAgentConnection) => Promise, fallback: R): Promise { const connection = this.getConnection(); if (!connection) { @@ -153,6 +164,8 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon readonly remoteAuthority: string; private _connection: Promise> | null; + private _initialConnectionMs: number | undefined; + constructor( remoteAuthority: string, private readonly _commit: string | undefined, @@ -181,6 +194,16 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon this._getOrCreateConnection().then(client => client.registerChannel(channelName, channel)); } + async getInitialConnectionTimeMs() { + try { + await this._getOrCreateConnection(); + } catch { + // ignored -- time is measured even if connection fails + } + + return this._initialConnectionMs!; + } + private _getOrCreateConnection(): Promise> { if (!this._connection) { this._connection = this._createConnection(); @@ -209,7 +232,14 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon logService: this._logService, ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null }; - const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`)); + let connection: ManagementPersistentConnection; + let start = Date.now(); + try { + connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`)); + } finally { + this._initialConnectionMs = Date.now() - start; + } + connection.protocol.onDidDispose(() => { connection.dispose(); }); diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 5d33f408514..35bfae00a4d 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -138,4 +138,8 @@ export class RemoteExtensionEnvironmentChannelClient { static flushTelemetry(channel: IChannel): Promise { return channel.call('flushTelemetry'); } + + static async ping(channel: IChannel): Promise { + await channel.call('ping'); + } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index b7508ffc4a7..6a899e927be 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -36,6 +36,12 @@ export interface IRemoteAgentService { */ getExtensionHostExitInfo(reconnectionToken: string): Promise; + /** + * Gets the round trip time from the remote extension host. Note that this + * may be delayed if the extension host is busy. + */ + getRoundTripTime(): Promise; + whenExtensionsReady(): Promise; /** * Scan remote extensions. @@ -65,4 +71,5 @@ export interface IRemoteAgentConnection { getChannel(channelName: string): T; withChannel(channelName: string, callback: (channel: T) => Promise): Promise; registerChannel>(channelName: string, channel: T): void; + getInitialConnectionTimeMs(): Promise; } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 9a1025debd8..5bd962101c9 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1920,4 +1920,5 @@ export class TestRemoteAgentService implements IRemoteAgentService { async updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise { } async logTelemetry(eventName: string, data?: ITelemetryData): Promise { } async flushTelemetry(): Promise { } + async getRoundTripTime(): Promise { return undefined; } } From 99a76d1569e13b6a1344d888321f5aeecf127ab6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 17:34:46 +0200 Subject: [PATCH 095/350] use fake timer (#149725) --- .../test/browser/indexedDBFileService.test.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index 3387e3b989b..370a9458263 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -11,6 +11,7 @@ import { Schemas } from 'vs/base/common/network'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { flakySuite } from 'vs/base/test/common/testUtils'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -272,22 +273,24 @@ flakySuite('IndexedDBFileSystemProvider', function () { }); test('createFile (mixed parallel/sequential)', async () => { - const single1 = makeBatchTester(1, 'single1'); - const single2 = makeBatchTester(1, 'single2'); + runWithFakedTimers({ useFakeTimers: true }, async () => { + const single1 = makeBatchTester(1, 'single1'); + const single2 = makeBatchTester(1, 'single2'); - const batch1 = makeBatchTester(20, 'batch1'); - const batch2 = makeBatchTester(20, 'batch2'); + const batch1 = makeBatchTester(20, 'batch1'); + const batch2 = makeBatchTester(20, 'batch2'); - single1.create(); - batch1.create(); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - single2.create(); - batch2.create(); - await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + single1.create(); + batch1.create(); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + single2.create(); + batch2.create(); + await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); - await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); + await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); + await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); + }); }); test('rename not existing resource', async () => { From 4b3becfae1014b49f24262c382fa72fbba164107 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 17:44:52 +0200 Subject: [PATCH 096/350] revert the change (#149726) * revert the change * remove import --- .../test/browser/indexedDBFileService.test.ts | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index 370a9458263..3387e3b989b 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -11,7 +11,6 @@ import { Schemas } from 'vs/base/common/network'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { flakySuite } from 'vs/base/test/common/testUtils'; -import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -273,24 +272,22 @@ flakySuite('IndexedDBFileSystemProvider', function () { }); test('createFile (mixed parallel/sequential)', async () => { - runWithFakedTimers({ useFakeTimers: true }, async () => { - const single1 = makeBatchTester(1, 'single1'); - const single2 = makeBatchTester(1, 'single2'); + const single1 = makeBatchTester(1, 'single1'); + const single2 = makeBatchTester(1, 'single2'); - const batch1 = makeBatchTester(20, 'batch1'); - const batch2 = makeBatchTester(20, 'batch2'); + const batch1 = makeBatchTester(20, 'batch1'); + const batch2 = makeBatchTester(20, 'batch2'); - single1.create(); - batch1.create(); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - single2.create(); - batch2.create(); - await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + single1.create(); + batch1.create(); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); + single2.create(); + batch2.create(); + await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); + await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); - await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); - }); + await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); + await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); }); test('rename not existing resource', async () => { From 2c97c8064722c44a10cf548990d3457864b6c89f Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 May 2022 17:52:20 +0200 Subject: [PATCH 097/350] Adopt `setTimeout0()` which doesn't suffer from the 4ms artificial delay that browsers set when the nesting level is > 5. (#149723) --- src/vs/base/test/common/timeTravelScheduler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/test/common/timeTravelScheduler.ts b/src/vs/base/test/common/timeTravelScheduler.ts index 9081b7c5632..470d93ac864 100644 --- a/src/vs/base/test/common/timeTravelScheduler.ts +++ b/src/vs/base/test/common/timeTravelScheduler.ts @@ -5,6 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { setTimeout0 } from 'vs/base/common/platform'; interface PriorityQueue { length: number; @@ -178,7 +179,7 @@ export class AsyncSchedulerProcessor extends Disposable { if (this.useSetImmediate) { originalGlobalValues.setImmediate(() => this.process()); } else { - originalGlobalValues.setTimeout(() => this.process()); + setTimeout0(() => this.process()); } }); } From 9ae563f53bd406c28a2415d2df9f335a5ced8729 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 May 2022 09:22:29 -0700 Subject: [PATCH 098/350] Pick up latest Marked.js release (#149729) Fixes #142003 --- src/vs/base/common/marked/cgmanifest.json | 4 +- src/vs/base/common/marked/marked.js | 252 ++++++++++++++++------ 2 files changed, 183 insertions(+), 73 deletions(-) diff --git a/src/vs/base/common/marked/cgmanifest.json b/src/vs/base/common/marked/cgmanifest.json index 47100d82d7f..60e11b4144e 100644 --- a/src/vs/base/common/marked/cgmanifest.json +++ b/src/vs/base/common/marked/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "marked", "repositoryUrl": "https://github.com/markedjs/marked", - "commitHash": "d1b7d521c41bcf915f81f0218b0e5acd607c1b72" + "commitHash": "2002557d004139ca2208c910d9ca999829b65406" } }, "license": "MIT", - "version": "3.0.2" + "version": "4.0.16" } ], "version": 1 diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index 09c308378d4..b7d8fe40e5e 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -23,6 +23,7 @@ typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.marked = {})); })(this, (function (exports) { 'use strict'; + function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; @@ -141,6 +142,10 @@ return html; } var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; + /** + * @param {string} html + */ + function unescape(html) { // explicitly match decimal, hex, and named HTML entities return html.replace(unescapeTest, function (_, n) { @@ -155,8 +160,13 @@ }); } var caret = /(^|[^\[])\^/g; + /** + * @param {string | RegExp} regex + * @param {string} opt + */ + function edit(regex, opt) { - regex = regex.source || regex; + regex = typeof regex === 'string' ? regex : regex.source; opt = opt || ''; var obj = { replace: function replace(name, val) { @@ -173,6 +183,12 @@ } var nonWordAndColonTest = /[^\w:]/g; var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; + /** + * @param {boolean} sanitize + * @param {string} base + * @param {string} href + */ + function cleanUrl(sanitize, base, href) { if (sanitize) { var prot; @@ -204,6 +220,11 @@ var justDomain = /^[^:]+:\/*[^/]*$/; var protocol = /^([^:]+:)[\s\S]*$/; var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/; + /** + * @param {string} base + * @param {string} href + */ + function resolveUrl(base, href) { if (!baseUrls[' ' + base]) { // we can ignore everything in base after the last slash of its path component, @@ -282,7 +303,7 @@ cells.shift(); } - if (!cells[cells.length - 1].trim()) { + if (cells.length > 0 && !cells[cells.length - 1].trim()) { cells.pop(); } @@ -300,9 +321,15 @@ } return cells; - } // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). - // /c*$/ is vulnerable to REDOS. - // invert: Remove suffix of non-c chars instead. Default falsey. + } + /** + * Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). + * /c*$/ is vulnerable to REDOS. + * + * @param {string} str + * @param {string} c + * @param {boolean} invert Remove suffix of non-c chars instead. Default falsey. + */ function rtrim(str, c, invert) { var l = str.length; @@ -326,7 +353,7 @@ } } - return str.substr(0, l - suffLen); + return str.slice(0, l - suffLen); } function findClosingBracket(str, b) { if (str.indexOf(b[1]) === -1) { @@ -359,6 +386,11 @@ } } // copied from https://stackoverflow.com/a/5450113/806777 + /** + * @param {string} pattern + * @param {number} count + */ + function repeatString(pattern, count) { if (count < 1) { return ''; @@ -395,15 +427,15 @@ }; lexer.state.inLink = false; return token; - } else { - return { - type: 'image', - raw: raw, - href: href, - title: title, - text: escape(text) - }; } + + return { + type: 'image', + raw: raw, + href: href, + title: title, + text: escape(text) + }; } function indentCodeCompensation(raw, text) { @@ -446,11 +478,11 @@ var cap = this.rules.block.newline.exec(src); if (cap && cap[0].length > 0) { - return { - type: 'space', - raw: cap[0] - }; - } + return { + type: 'space', + raw: cap[0] + }; + } }; _proto.code = function code(src) { @@ -526,7 +558,7 @@ var cap = this.rules.block.blockquote.exec(src); if (cap) { - var text = cap[0].replace(/^ *> ?/gm, ''); + var text = cap[0].replace(/^ *>[ \t]?/gm, ''); return { type: 'blockquote', raw: cap[0], @@ -558,7 +590,7 @@ } // Get next list item - var itemRegex = new RegExp("^( {0,3}" + bull + ")((?: [^\\n]*)?(?:\\n|$))"); // Check if current bullet point can start a new List Item + var itemRegex = new RegExp("^( {0,3}" + bull + ")((?:[\t ][^\\n]*)?(?:\\n|$))"); // Check if current bullet point can start a new List Item while (src) { endEarly = false; @@ -599,31 +631,37 @@ } if (!endEarly) { - var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])"); // Check if following lines should be included in List Item + var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])((?: [^\\n]*)?(?:\\n|$))"); + var hrRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)"); // Check if following lines should be included in List Item while (src) { rawLine = src.split('\n', 1)[0]; line = rawLine; // Re-align to follow commonmark nesting rules - if (this.options.pedantic) { - line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); - } // End list item if found start of new bullet + if (this.options.pedantic) { + line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); + } // End list item if found start of new bullet - if (nextBulletRegex.test(line)) { - break; + if (nextBulletRegex.test(line)) { + break; + } // Horizontal rule found + + + if (hrRegex.test(src)) { + break; } - if (line.search(/[^ ]/) >= indent || !line.trim()) { + if (line.search(/[^ ]/) >= indent || !line.trim()) { // Dedent if possible - itemContents += '\n' + line.slice(indent); + itemContents += '\n' + line.slice(indent); } else if (!blankLine) { // Until blank line, item doesn't need indentation itemContents += '\n' + line; - } else { + } else { // Otherwise, improper indentation ends this item - break; - } + break; + } if (!blankLine && !line.trim()) { // Check if current line is blank @@ -757,7 +795,7 @@ }; }), align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - rows: cap[3] ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [] + rows: cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, '').split('\n') : [] }; if (item.header.length === item.align.length) { @@ -793,7 +831,7 @@ for (j = 0; j < l; j++) { item.header[j].tokens = []; - this.lexer.inlineTokens(item.header[j].text, item.header[j].tokens); + this.lexer.inline(item.header[j].text, item.header[j].tokens); } // cell child tokens @@ -804,7 +842,7 @@ for (k = 0; k < row.length; k++) { row[k].tokens = []; - this.lexer.inlineTokens(row[k].text, row[k].tokens); + this.lexer.inline(row[k].text, row[k].tokens); } } @@ -1195,10 +1233,10 @@ newline: /^(?: *(?:\n|$))+/, code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, + hr: /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/, heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3}bull)( [^\n]+?)?(?:\n|$)/, + list: /^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/, html: '^ {0,3}(?:' // optional indentation + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) + '|comment[^\\n]*(\\n+|$)' // (2) @@ -1288,9 +1326,9 @@ emStrong: { lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/, // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right. - // () Skip orphan delim inside strong (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a - rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/, - rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _ + // () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a + rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[^*]+(?=[^*])|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/, + rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _ }, code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, @@ -1372,6 +1410,7 @@ /** * smartypants text replacement + * @param {string} text */ function smartypants(text) { @@ -1386,6 +1425,7 @@ } /** * mangle email addresses + * @param {string} text */ @@ -1476,7 +1516,7 @@ var _proto = Lexer.prototype; _proto.lex = function lex(src) { - src = src.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' '); + src = src.replace(/\r\n|\r/g, '\n'); this.blockTokens(src, this.tokens); var next; @@ -1499,7 +1539,11 @@ } if (this.options.pedantic) { - src = src.replace(/^ +$/gm, ''); + src = src.replace(/\t/g, ' ').replace(/^ +$/gm, ''); + } else { + src = src.replace(/^( *)(\t+)/gm, function (_, leading, tabs) { + return leading + ' '.repeat(tabs.length); + }); } var token, lastToken, cutSrc, lastParagraphClipped; @@ -1959,23 +2003,35 @@ } return '
' + (escaped ? _code : escape(_code, true)) + '
\n'; - }; + } + /** + * @param {string} quote + */ + ; _proto.blockquote = function blockquote(quote) { - return '
\n' + quote + '
\n'; + return "
\n" + quote + "
\n"; }; _proto.html = function html(_html) { return _html; - }; + } + /** + * @param {string} text + * @param {string} level + * @param {string} raw + * @param {any} slugger + */ + ; _proto.heading = function heading(text, level, raw, slugger) { if (this.options.headerIds) { - return '' + text + '\n'; + var id = this.options.headerPrefix + slugger.slug(raw); + return "" + text + "\n"; } // ignore IDs - return '' + text + '\n'; + return "" + text + "\n"; }; _proto.hr = function hr() { @@ -1986,55 +2042,94 @@ var type = ordered ? 'ol' : 'ul', startatt = ordered && start !== 1 ? ' start="' + start + '"' : ''; return '<' + type + startatt + '>\n' + body + '\n'; - }; + } + /** + * @param {string} text + */ + ; _proto.listitem = function listitem(text) { - return '
  • ' + text + '
  • \n'; + return "
  • " + text + "
  • \n"; }; _proto.checkbox = function checkbox(checked) { return ' '; - }; + } + /** + * @param {string} text + */ + ; _proto.paragraph = function paragraph(text) { - return '

    ' + text + '

    \n'; - }; + return "

    " + text + "

    \n"; + } + /** + * @param {string} header + * @param {string} body + */ + ; _proto.table = function table(header, body) { - if (body) body = '' + body + ''; + if (body) body = "" + body + ""; return '\n' + '\n' + header + '\n' + body + '
    \n'; - }; + } + /** + * @param {string} content + */ + ; _proto.tablerow = function tablerow(content) { - return '\n' + content + '\n'; + return "\n" + content + "\n"; }; _proto.tablecell = function tablecell(content, flags) { var type = flags.header ? 'th' : 'td'; - var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; - return tag + content + '\n'; - } // span level renderer + var tag = flags.align ? "<" + type + " align=\"" + flags.align + "\">" : "<" + type + ">"; + return tag + content + ("\n"); + } + /** + * span level renderer + * @param {string} text + */ ; _proto.strong = function strong(text) { - return '' + text + ''; - }; + return "" + text + ""; + } + /** + * @param {string} text + */ + ; _proto.em = function em(text) { - return '' + text + ''; - }; + return "" + text + ""; + } + /** + * @param {string} text + */ + ; _proto.codespan = function codespan(text) { - return '' + text + ''; + return "" + text + ""; }; _proto.br = function br() { return this.options.xhtml ? '
    ' : '
    '; - }; + } + /** + * @param {string} text + */ + ; _proto.del = function del(text) { - return '' + text + ''; - }; + return "" + text + ""; + } + /** + * @param {string} href + * @param {string} title + * @param {string} text + */ + ; _proto.link = function link(href, title, text) { href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); @@ -2051,7 +2146,13 @@ out += '>' + text + ''; return out; - }; + } + /** + * @param {string} href + * @param {string} title + * @param {string} text + */ + ; _proto.image = function image(href, title, text) { href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); @@ -2060,10 +2161,10 @@ return text; } - var out = '' + text + '' : '>'; @@ -2133,6 +2234,10 @@ function Slugger() { this.seen = {}; } + /** + * @param {string} value + */ + var _proto = Slugger.prototype; @@ -2143,6 +2248,8 @@ } /** * Finds the next safe (unique) slug to use + * @param {string} originalSlug + * @param {boolean} isDryRun */ ; @@ -2168,8 +2275,9 @@ } /** * Convert string to unique id - * @param {object} options - * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator. + * @param {object} [options] + * @param {boolean} [options.dryrun] Generates the next unique slug without + * updating the internal accumulator. */ ; @@ -2866,6 +2974,7 @@ }; /** * Parse Inline + * @param {string} src */ @@ -2941,6 +3050,7 @@ exports.walkTokens = walkTokens; Object.defineProperty(exports, '__esModule', { value: true }); + })); // ESM-uncomment-begin From 1c7ab357618b568746790b5a30f204794aa50afa Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 May 2022 09:26:54 -0700 Subject: [PATCH 099/350] Rename to DocumentOnDropEditProvider (#149730) This change renames `DocumentOnDropProvider` to `DocumentOnDropEditProvider`. This new name aligns with the existing `DocumentFormattingEditProvider` --- .../src/languageFeatures/dropIntoEditor.ts | 2 +- .../api/browser/mainThreadLanguageFeatures.ts | 14 +++++++------- src/vs/workbench/api/common/extHost.api.impl.ts | 4 ++-- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 14 +++++++------- src/vscode-dts/vscode.proposed.textEditorDrop.d.ts | 6 +++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index c3fb1f55631..d5d7c4d9d29 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -24,7 +24,7 @@ const imageFileExtensions = new Set([ ]); export function registerDropIntoEditor(selector: vscode.DocumentSelector) { - return vscode.languages.registerDocumentOnDropProvider(selector, new class implements vscode.DocumentOnDropProvider { + return vscode.languages.registerDocumentOnDropEditProvider(selector, new class implements vscode.DocumentOnDropEditProvider { async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); if (!enabled) { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index e01c59d4ec4..07d04a05c60 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -863,19 +863,19 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- document drop Edits - private readonly _documentOnDropProviders = new Map(); + private readonly _documentOnDropEditProviders = new Map(); - $registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void { - const provider = new MainThreadDocumentOnDropProvider(handle, this._proxy); - this._documentOnDropProviders.set(handle, provider); + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[]): void { + const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy); + this._documentOnDropEditProviders.set(handle, provider); this._registrations.set(handle, combinedDisposable( this._languageFeaturesService.documentOnDropEditProvider.register(selector, provider), - toDisposable(() => this._documentOnDropProviders.delete(handle)), + toDisposable(() => this._documentOnDropEditProviders.delete(handle)), )); } async $resolveDocumentOnDropFileData(handle: number, requestId: number, dataIndex: number): Promise { - const provider = this._documentOnDropProviders.get(handle); + const provider = this._documentOnDropEditProviders.get(handle); if (!provider) { throw new Error('Could not find provider'); } @@ -883,7 +883,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } } -class MainThreadDocumentOnDropProvider implements languages.DocumentOnDropEditProvider { +class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEditProvider { private readonly dataTransfers = new DataTransferCache(); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index c2ad7565594..313a9aadfd1 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -570,9 +570,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem { return extHostLanguages.createLanguageStatusItem(extension, id, selector); }, - registerDocumentOnDropProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider): vscode.Disposable { + registerDocumentOnDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropEditProvider): vscode.Disposable { checkProposedApiEnabled(extension, 'textEditorDrop'); - return extHostLanguageFeatures.registerDocumentOnDropProvider(extension, selector, provider); + return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9fa08b4b6c3..6d936351c35 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -392,7 +392,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; - $registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[]): void; $resolveDocumentOnDropFileData(handle: number, requestId: number, dataIndex: number): Promise; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 32bf1eab953..3b6b789641a 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1745,12 +1745,12 @@ class TypeHierarchyAdapter { } } -class DocumentOnDropAdapter { +class DocumentOnDropEditAdapter { constructor( private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape, private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.DocumentOnDropProvider, + private readonly _provider: vscode.DocumentOnDropEditProvider, private readonly _handle: number, ) { } @@ -1778,7 +1778,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter | InlineCompletionAdapterNew - | DocumentOnDropAdapter; + | DocumentOnDropEditAdapter; class AdapterData { constructor( @@ -2402,15 +2402,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- Document on drop - registerDocumentOnDropProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropProvider) { + registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropEditProvider) { const handle = this._nextHandle(); - this._adapter.set(handle, new AdapterData(new DocumentOnDropAdapter(this._proxy, this._documents, provider, handle), extension)); - this._proxy.$registerDocumentOnDropProvider(handle, this._transformDocumentSelector(selector)); + this._adapter.set(handle, new AdapterData(new DocumentOnDropEditAdapter(this._proxy, this._documents, provider, handle), extension)); + this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined> { - return this._withAdapter(handle, DocumentOnDropAdapter, adapter => + return this._withAdapter(handle, DocumentOnDropEditAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } diff --git a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts index 879e5a757b7..5468bc218cd 100644 --- a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { * * The user can drop into a text editor by holding down `shift` while dragging. Requires `workbench.experimental.editor.dropIntoEditor.enabled` to be on. */ - export interface DocumentOnDropProvider { + export interface DocumentOnDropEditProvider { /** * Provide edits which inserts the content being dragged and dropped into the document. * @@ -35,13 +35,13 @@ declare module 'vscode' { export namespace languages { /** - * Registers a new {@link DocumentOnDropProvider}. + * Registers a new {@link DocumentOnDropEditProvider}. * * @param selector A selector that defines the documents this provider applies to. * @param provider A drop provider. * * @return A {@link Disposable} that unregisters this provider when disposed of. */ - export function registerDocumentOnDropProvider(selector: DocumentSelector, provider: DocumentOnDropProvider): Disposable; + export function registerDocumentOnDropEditProvider(selector: DocumentSelector, provider: DocumentOnDropEditProvider): Disposable; } } From aef0e27d249ae48f856a410a7bf7c447a9e1f7f8 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 17 May 2022 10:00:20 -0700 Subject: [PATCH 100/350] fix layer break (#149735) --- .../platform/terminal/common/xterm/shellIntegrationAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index c28607fc175..df36e27041d 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -125,7 +125,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _terminal?: Terminal; readonly capabilities = new TerminalCapabilityStore(); private _hasUpdatedTelemetry: boolean = false; - private _activationTimeout: number | undefined; + private _activationTimeout: any; constructor( private readonly _telemetryService: ITelemetryService | undefined, @@ -230,7 +230,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati } private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { - this._activationTimeout = window.setTimeout(() => { + this._activationTimeout = setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationTimeout'); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); From 16db4dc2bd5221ad61aba1f22d7c4d77a60452d4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 19:10:37 +0200 Subject: [PATCH 101/350] make tests faster - remove unnecessary validations - group writes --- .../test/browser/indexedDBFileService.test.ts | 94 ++++++++----------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index 3387e3b989b..a12b43189f0 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -12,20 +12,16 @@ import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { flakySuite } from 'vs/base/test/common/testUtils'; import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; -import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileSystemProviderError, FileSystemProviderErrorCode, FileType } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; flakySuite('IndexedDBFileSystemProvider', function () { - const logSchema = 'logs'; - let service: FileService; - let logFileProvider: IndexedDBFileSystemProvider; let userdataFileProvider: IndexedDBFileSystemProvider; const testDir = '/'; - const logfileURIFromPaths = (paths: string[]) => joinPath(URI.from({ scheme: logSchema, path: testDir }), ...paths); const userdataURIFromPaths = (paths: readonly string[]) => joinPath(URI.from({ scheme: Schemas.vscodeUserData, path: testDir }), ...paths); const disposables = new DisposableStore(); @@ -69,10 +65,6 @@ flakySuite('IndexedDBFileSystemProvider', function () { const indexedDB = await IndexedDB.create('vscode-web-db-test', 1, ['vscode-userdata-store', 'vscode-logs-store']); - logFileProvider = new IndexedDBFileSystemProvider(logSchema, indexedDB, 'vscode-logs-store', false); - disposables.add(service.registerProvider(logSchema, logFileProvider)); - disposables.add(logFileProvider); - userdataFileProvider = new IndexedDBFileSystemProvider(Schemas.vscodeUserData, indexedDB, 'vscode-userdata-store', true); disposables.add(service.registerProvider(Schemas.vscodeUserData, userdataFileProvider)); disposables.add(userdataFileProvider); @@ -84,8 +76,7 @@ flakySuite('IndexedDBFileSystemProvider', function () { }); teardown(async () => { - await logFileProvider.delete(logfileURIFromPaths([]), { recursive: true, useTrash: false }); - await userdataFileProvider.delete(userdataURIFromPaths([]), { recursive: true, useTrash: false }); + await userdataFileProvider.reset(); disposables.clear(); }); @@ -234,60 +225,43 @@ flakySuite('IndexedDBFileSystemProvider', function () { assert.strictEqual(event!.target!.resource.path, resource.path); } - const makeBatchTester = (size: number, name: string) => { + const fileCreateBatchTester = (size: number, name: string) => { const batch = Array.from({ length: size }).map((_, i) => ({ contents: `Hello${i}`, resource: userdataURIFromPaths(['batched', name, `Hello${i}.txt`]) })); - let stats: Promise | undefined = undefined; + let creationPromises: Promise | undefined = undefined; return { async create() { - return stats = Promise.all(batch.map(entry => service.createFile(entry.resource, VSBuffer.fromString(entry.contents)))); + return creationPromises = Promise.all(batch.map(entry => userdataFileProvider.writeFile(entry.resource, VSBuffer.fromString(entry.contents).buffer, { create: true, overwrite: true, unlock: false }))); }, async assertContentsCorrect() { + if (!creationPromises) { throw Error('read called before create'); } + await creationPromises; await Promise.all(batch.map(async (entry, i) => { - if (!stats) { throw Error('read called before create'); } - const stat = (await stats!)[i]; - assert.strictEqual(stat.name, `Hello${i}.txt`); - assert.strictEqual((await userdataFileProvider.stat(stat.resource)).type, FileType.File); - assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(stat.resource)), entry.contents); - })); - }, - async delete() { - await service.del(userdataURIFromPaths(['batched', name]), { recursive: true, useTrash: false }); - }, - async assertContentsEmpty() { - if (!stats) { throw Error('assertContentsEmpty called before create'); } - await Promise.all((await stats).map(async stat => { - const newStat = await userdataFileProvider.stat(stat.resource).catch(e => e.code); - assert.strictEqual(newStat, FileSystemProviderErrorCode.FileNotFound); + assert.strictEqual((await userdataFileProvider.stat(entry.resource)).type, FileType.File); + assert.strictEqual(new TextDecoder().decode(await userdataFileProvider.readFile(entry.resource)), entry.contents); })); } }; }; - test('createFile (small batch)', async () => { - const tester = makeBatchTester(50, 'smallBatch'); + test('createFile - batch', async () => { + const tester = fileCreateBatchTester(20, 'batch'); await tester.create(); await tester.assertContentsCorrect(); - await tester.delete(); - await tester.assertContentsEmpty(); }); - test('createFile (mixed parallel/sequential)', async () => { - const single1 = makeBatchTester(1, 'single1'); - const single2 = makeBatchTester(1, 'single2'); + test('createFile - batch (mixed parallel/sequential)', async () => { + const batch1 = fileCreateBatchTester(1, 'batch1'); + const batch2 = fileCreateBatchTester(20, 'batch2'); + const batch3 = fileCreateBatchTester(1, 'batch3'); + const batch4 = fileCreateBatchTester(20, 'batch4'); - const batch1 = makeBatchTester(20, 'batch1'); - const batch2 = makeBatchTester(20, 'batch2'); - - single1.create(); batch1.create(); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - single2.create(); batch2.create(); - await Promise.all([single2.assertContentsCorrect(), batch2.assertContentsCorrect()]); - await Promise.all([single1.assertContentsCorrect(), batch1.assertContentsCorrect()]); - - await (Promise.all([single1.delete(), single2.delete(), batch1.delete(), batch2.delete()])); - await (Promise.all([single1.assertContentsEmpty(), single2.assertContentsEmpty(), batch1.assertContentsEmpty(), batch2.assertContentsEmpty()])); + await Promise.all([batch1.assertContentsCorrect(), batch2.assertContentsCorrect()]); + batch3.create(); + batch4.create(); + await Promise.all([batch3.assertContentsCorrect(), batch4.assertContentsCorrect()]); + await Promise.all([batch1.assertContentsCorrect(), batch2.assertContentsCorrect()]); }); test('rename not existing resource', async () => { @@ -392,10 +366,12 @@ flakySuite('IndexedDBFileSystemProvider', function () { test('rename to an existing file with overwrite', async () => { const parent = await service.resolve(userdataURIFromPaths([])); const sourceFile = joinPath(parent.resource, 'sourceFile'); - await service.writeFile(sourceFile, VSBuffer.fromString('This is source file')); - const targetFile = joinPath(parent.resource, 'targetFile'); - await service.writeFile(targetFile, VSBuffer.fromString('This is target file')); + + await Promise.all([ + service.writeFile(sourceFile, VSBuffer.fromString('This is source file')), + service.writeFile(targetFile, VSBuffer.fromString('This is target file')) + ]); await service.move(sourceFile, targetFile, true); @@ -434,11 +410,14 @@ flakySuite('IndexedDBFileSystemProvider', function () { const sourceFolder = joinPath(parent.resource, 'sourceFolder'); const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1'); - await service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')); const sourceFile2 = joinPath(sourceFolder, 'folder2', 'file1'); - await service.writeFile(sourceFile2, VSBuffer.fromString('Source File 2')); const sourceEmptyFolder = joinPath(sourceFolder, 'folder3'); - await service.createFolder(sourceEmptyFolder); + + await Promise.all([ + service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')), + service.writeFile(sourceFile2, VSBuffer.fromString('Source File 2')), + service.createFolder(sourceEmptyFolder) + ]); const targetFolder = joinPath(parent.resource, 'targetFolder'); const targetFile1 = joinPath(targetFolder, 'folder1', 'file1'); @@ -459,14 +438,17 @@ flakySuite('IndexedDBFileSystemProvider', function () { const sourceFolder = joinPath(parent.resource, 'sourceFolder'); const sourceFile1 = joinPath(sourceFolder, 'folder1', 'file1'); - await service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')); const targetFolder = joinPath(parent.resource, 'targetFolder'); const targetFile1 = joinPath(targetFolder, 'folder1', 'file1'); const targetFile2 = joinPath(targetFolder, 'folder1', 'file2'); - await service.writeFile(targetFile2, VSBuffer.fromString('Target File 2')); const targetFile3 = joinPath(targetFolder, 'folder2', 'file1'); - await service.writeFile(targetFile3, VSBuffer.fromString('Target File 3')); + + await Promise.all([ + service.writeFile(sourceFile1, VSBuffer.fromString('Source File 1')), + service.writeFile(targetFile2, VSBuffer.fromString('Target File 2')), + service.writeFile(targetFile3, VSBuffer.fromString('Target File 3')) + ]); await service.move(sourceFolder, targetFolder, true); From aa2996cbb7da1022d378d3425c6bc0027481d074 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 17 May 2022 10:40:45 -0700 Subject: [PATCH 102/350] Localize "Suggested" (#149684) For #146338 --- src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 06a6e7f8ea4..7f9e2de3788 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -360,7 +360,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { const picks: { label: string; debugger?: Debugger; type?: string }[] = []; if (suggestedCandidates.length > 0) { picks.push( - { type: 'separator', label: 'Suggested' }, + { type: 'separator', label: nls.localize('suggestedDebuggers', "Suggested") }, ...suggestedCandidates.map(c => ({ label: c.label, debugger: c }))); } From 7ca85c375d5d76711e1f53c8637fa336b7be62be Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 17 May 2022 19:45:36 +0200 Subject: [PATCH 103/350] Revert "Adopt `setTimeout0()` which doesn't suffer from the 4ms artificial delay that browsers set when the nesting level is > 5. (#149723)" (#149742) This reverts commit 2c97c8064722c44a10cf548990d3457864b6c89f. --- src/vs/base/test/common/timeTravelScheduler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/test/common/timeTravelScheduler.ts b/src/vs/base/test/common/timeTravelScheduler.ts index 470d93ac864..9081b7c5632 100644 --- a/src/vs/base/test/common/timeTravelScheduler.ts +++ b/src/vs/base/test/common/timeTravelScheduler.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { setTimeout0 } from 'vs/base/common/platform'; interface PriorityQueue { length: number; @@ -179,7 +178,7 @@ export class AsyncSchedulerProcessor extends Disposable { if (this.useSetImmediate) { originalGlobalValues.setImmediate(() => this.process()); } else { - setTimeout0(() => this.process()); + originalGlobalValues.setTimeout(() => this.process()); } }); } From 4f7f2c6135bab4bbe67717aaf8f1c9ca5fb2bc4c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 May 2022 19:53:04 +0200 Subject: [PATCH 104/350] convert to integration tests (#149744) --- ...ileService.test.ts => indexedDBFileService.integrationTest.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/platform/files/test/browser/{indexedDBFileService.test.ts => indexedDBFileService.integrationTest.ts} (100%) diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts similarity index 100% rename from src/vs/platform/files/test/browser/indexedDBFileService.test.ts rename to src/vs/platform/files/test/browser/indexedDBFileService.integrationTest.ts From d26569164e873e5f3010a83283103a6ae6a66f45 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 17 May 2022 10:57:08 -0700 Subject: [PATCH 105/350] Adopt queue review bot behaviour (#149745) --- .github/workflows/pr-chat.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-chat.yml b/.github/workflows/pr-chat.yml index 95cee4ed196..0c8d6bab793 100644 --- a/.github/workflows/pr-chat.yml +++ b/.github/workflows/pr-chat.yml @@ -1,7 +1,7 @@ name: PR Chat on: pull_request_target: - types: [opened, ready_for_review] + types: [opened, ready_for_review, closed] jobs: main: @@ -20,4 +20,5 @@ jobs: with: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} slack_token: ${{ secrets.SLACK_TOKEN }} + slack_bot_name: "VSCodeBot" notification_channel: codereview From 06cf633d63a6bc0dbadaf9a446ade3a87da4732f Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 17 May 2022 11:03:19 -0700 Subject: [PATCH 106/350] Revert running on PR close (#149747) --- .github/workflows/pr-chat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-chat.yml b/.github/workflows/pr-chat.yml index 0c8d6bab793..969c3cc17c0 100644 --- a/.github/workflows/pr-chat.yml +++ b/.github/workflows/pr-chat.yml @@ -1,7 +1,7 @@ name: PR Chat on: pull_request_target: - types: [opened, ready_for_review, closed] + types: [opened, ready_for_review] jobs: main: From 116c10e0eeed56dc9b0801d079e2a2374b6ccf98 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 May 2022 11:35:04 -0700 Subject: [PATCH 107/350] Add drop into editor option (#147049) * Add drop into editor option This change adds a new `enableDropIntoEditor` editor option that enables/disables dropping an extermal resources into an editor Previously this option was exposed `IEditorConstructionOptions`, however this did not correctly disable drop into editor when dragging and dropping unknown types (such as dragging emoji from the MacOS emoji panel) With this change, disabling `workbench.editor.dropIntoEditor.enabled` should fully disable the new drop into behavior * Move drop into editor from workbench to editor This moves the `dropIntoEditorContribution` from the workbench layer to the platform layer As part of this change, I also add to move `extractEditorsDropData` up to `platform` so that it could be used from the `editor` layer This change also enables drop into for the SCM message box * Fixing monaco errors * Revert id change --- src/tsconfig.monaco.json | 5 +- .../dnd.ts => base/common/dataTransfer.ts} | 0 .../browser/config/editorConfiguration.ts | 6 - .../editor/browser/widget/codeEditorWidget.ts | 61 +-- src/vs/editor/common/config/editorOptions.ts | 11 + src/vs/editor/common/languages.ts | 6 +- .../common/standalone/standaloneEnums.ts | 203 +++++----- .../browser/dropIntoEditorContribution.ts} | 123 ++++-- src/vs/editor/editor.all.ts | 1 + src/vs/monaco.d.ts | 216 +++++------ src/vs/platform/dnd/browser/dnd.ts | 339 +++++++++++++++++ .../api/browser/mainThreadLanguageFeatures.ts | 2 +- .../api/browser/mainThreadTreeViews.ts | 2 +- .../workbench/api/common/extHostTreeViews.ts | 2 +- .../api/common/shared/dataTransfer.ts | 2 +- .../api/common/shared/dataTransferCache.ts | 2 +- src/vs/workbench/browser/dnd.ts | 358 ++---------------- .../browser/parts/editor/editorDropTarget.ts | 3 +- .../workbench/browser/parts/views/treeView.ts | 5 +- src/vs/workbench/common/views.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../contrib/files/browser/fileImportExport.ts | 2 +- .../files/browser/views/explorerViewer.ts | 3 +- .../files/browser/views/openEditorsView.ts | 3 +- .../contrib/scm/browser/scmViewPane.ts | 7 +- .../terminal/browser/terminal.contribution.ts | 2 +- .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/terminalTabsList.ts | 2 +- .../dialogs/browser/fileDialogService.ts | 2 +- .../views/browser/treeViewsService.ts | 2 +- src/vs/workbench/workbench.common.main.ts | 3 - 31 files changed, 736 insertions(+), 643 deletions(-) rename src/vs/{editor/common/dnd.ts => base/common/dataTransfer.ts} (100%) rename src/vs/{workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts => editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts} (62%) create mode 100644 src/vs/platform/dnd/browser/dnd.ts diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index f9f0c874eb6..7d18928f6b4 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -2,7 +2,10 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "noEmit": true, - "types": ["trusted-types"], + "types": [ + "trusted-types", + "wicg-file-system-access" + ], "paths": {}, "module": "amd", "moduleResolution": "classic", diff --git a/src/vs/editor/common/dnd.ts b/src/vs/base/common/dataTransfer.ts similarity index 100% rename from src/vs/editor/common/dnd.ts rename to src/vs/base/common/dataTransfer.ts diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 58474ac9363..e3f41e0d416 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -30,12 +30,6 @@ export interface IEditorConstructionOptions extends IEditorOptions { * Defaults to an internal DOM node. */ overflowWidgetsDomNode?: HTMLElement; - /** - * Enables dropping into the editor. - * - * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. - */ - enableDropIntoEditor?: boolean; } export class EditorConfiguration extends Disposable implements IEditorConfiguration { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 801501376ce..25890eb2332 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -368,35 +368,42 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._actions[internalAction.id] = internalAction; }); - if (_options.enableDropIntoEditor) { - this._register(new dom.DragAndDropObserver(this._domElement, { - onDragEnter: () => undefined, - onDragOver: e => { - const target = this.getTargetAtClientPoint(e.clientX, e.clientY); - if (target?.position) { - this.showDropIndicatorAt(target.position); - } - }, - onDrop: async e => { - this.removeDropIndicator(); + this._register(new dom.DragAndDropObserver(this._domElement, { + onDragEnter: () => undefined, + onDragOver: e => { + if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + return; + } - if (!e.dataTransfer) { - return; - } + const target = this.getTargetAtClientPoint(e.clientX, e.clientY); + if (target?.position) { + this.showDropIndicatorAt(target.position); + } + }, + onDrop: async e => { + if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + return; + } + + this.removeDropIndicator(); + + if (!e.dataTransfer) { + return; + } + + const target = this.getTargetAtClientPoint(e.clientX, e.clientY); + if (target?.position) { + this._onDropIntoEditor.fire({ position: target.position, event: e }); + } + }, + onDragLeave: () => { + this.removeDropIndicator(); + }, + onDragEnd: () => { + this.removeDropIndicator(); + }, + })); - const target = this.getTargetAtClientPoint(e.clientX, e.clientY); - if (target?.position) { - this._onDropIntoEditor.fire({ position: target.position, event: e }); - } - }, - onDragLeave: () => { - this.removeDropIndicator(); - }, - onDragEnd: () => { - this.removeDropIndicator(); - }, - })); - } this._codeEditorService.addCodeEditor(this); } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index feb0bd01946..022895e3e55 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -659,6 +659,13 @@ export interface IEditorOptions { * Configures bracket pair colorization (disabled by default). */ bracketPairColorization?: IBracketPairColorizationOptions; + + /** + * Enables dropping into the editor from an external source. + * + * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. + */ + enableDropIntoEditor?: boolean; } /** @@ -4431,6 +4438,7 @@ export const enum EditorOption { disableMonospaceOptimizations, domReadOnly, dragAndDrop, + enableDropIntoEditor, emptySelectionClipboard, extraEditorClassName, fastScrollSensitivity, @@ -4739,6 +4747,9 @@ export const EditorOptions = { { description: nls.localize('dragAndDrop', "Controls whether the editor should allow moving selections via drag and drop.") } )), emptySelectionClipboard: register(new EditorEmptySelectionClipboard()), + enableDropIntoEditor: register(new EditorBooleanOption( + EditorOption.enableDropIntoEditor, 'enableDropIntoEditor', true + )), extraEditorClassName: register(new EditorStringOption( EditorOption.extraEditorClassName, 'extraEditorClassName', '', )), diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 74306ee05d9..b5c7361ce88 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; +import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -16,10 +19,7 @@ import * as model from 'vs/editor/common/model'; import { TokenizationRegistry as TokenizationRegistryImpl } from 'vs/editor/common/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; -import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import { IDataTransfer } from 'vs/editor/common/dnd'; /** * Open ended enum at runtime diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 265cb543054..8d709286d5c 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -199,107 +199,108 @@ export enum EditorOption { disableMonospaceOptimizations = 29, domReadOnly = 30, dragAndDrop = 31, - emptySelectionClipboard = 32, - extraEditorClassName = 33, - fastScrollSensitivity = 34, - find = 35, - fixedOverflowWidgets = 36, - folding = 37, - foldingStrategy = 38, - foldingHighlight = 39, - foldingImportsByDefault = 40, - foldingMaximumRegions = 41, - unfoldOnClickAfterEndOfLine = 42, - fontFamily = 43, - fontInfo = 44, - fontLigatures = 45, - fontSize = 46, - fontWeight = 47, - formatOnPaste = 48, - formatOnType = 49, - glyphMargin = 50, - gotoLocation = 51, - hideCursorInOverviewRuler = 52, - hover = 53, - inDiffEditor = 54, - inlineSuggest = 55, - letterSpacing = 56, - lightbulb = 57, - lineDecorationsWidth = 58, - lineHeight = 59, - lineNumbers = 60, - lineNumbersMinChars = 61, - linkedEditing = 62, - links = 63, - matchBrackets = 64, - minimap = 65, - mouseStyle = 66, - mouseWheelScrollSensitivity = 67, - mouseWheelZoom = 68, - multiCursorMergeOverlapping = 69, - multiCursorModifier = 70, - multiCursorPaste = 71, - occurrencesHighlight = 72, - overviewRulerBorder = 73, - overviewRulerLanes = 74, - padding = 75, - parameterHints = 76, - peekWidgetDefaultFocus = 77, - definitionLinkOpensInPeek = 78, - quickSuggestions = 79, - quickSuggestionsDelay = 80, - readOnly = 81, - renameOnType = 82, - renderControlCharacters = 83, - renderFinalNewline = 84, - renderLineHighlight = 85, - renderLineHighlightOnlyWhenFocus = 86, - renderValidationDecorations = 87, - renderWhitespace = 88, - revealHorizontalRightPadding = 89, - roundedSelection = 90, - rulers = 91, - scrollbar = 92, - scrollBeyondLastColumn = 93, - scrollBeyondLastLine = 94, - scrollPredominantAxis = 95, - selectionClipboard = 96, - selectionHighlight = 97, - selectOnLineNumbers = 98, - showFoldingControls = 99, - showUnused = 100, - snippetSuggestions = 101, - smartSelect = 102, - smoothScrolling = 103, - stickyTabStops = 104, - stopRenderingLineAfter = 105, - suggest = 106, - suggestFontSize = 107, - suggestLineHeight = 108, - suggestOnTriggerCharacters = 109, - suggestSelection = 110, - tabCompletion = 111, - tabIndex = 112, - unicodeHighlighting = 113, - unusualLineTerminators = 114, - useShadowDOM = 115, - useTabStops = 116, - wordSeparators = 117, - wordWrap = 118, - wordWrapBreakAfterCharacters = 119, - wordWrapBreakBeforeCharacters = 120, - wordWrapColumn = 121, - wordWrapOverride1 = 122, - wordWrapOverride2 = 123, - wrappingIndent = 124, - wrappingStrategy = 125, - showDeprecated = 126, - inlayHints = 127, - editorClassName = 128, - pixelRatio = 129, - tabFocusMode = 130, - layoutInfo = 131, - wrappingInfo = 132 + enableDropIntoEditor = 32, + emptySelectionClipboard = 33, + extraEditorClassName = 34, + fastScrollSensitivity = 35, + find = 36, + fixedOverflowWidgets = 37, + folding = 38, + foldingStrategy = 39, + foldingHighlight = 40, + foldingImportsByDefault = 41, + foldingMaximumRegions = 42, + unfoldOnClickAfterEndOfLine = 43, + fontFamily = 44, + fontInfo = 45, + fontLigatures = 46, + fontSize = 47, + fontWeight = 48, + formatOnPaste = 49, + formatOnType = 50, + glyphMargin = 51, + gotoLocation = 52, + hideCursorInOverviewRuler = 53, + hover = 54, + inDiffEditor = 55, + inlineSuggest = 56, + letterSpacing = 57, + lightbulb = 58, + lineDecorationsWidth = 59, + lineHeight = 60, + lineNumbers = 61, + lineNumbersMinChars = 62, + linkedEditing = 63, + links = 64, + matchBrackets = 65, + minimap = 66, + mouseStyle = 67, + mouseWheelScrollSensitivity = 68, + mouseWheelZoom = 69, + multiCursorMergeOverlapping = 70, + multiCursorModifier = 71, + multiCursorPaste = 72, + occurrencesHighlight = 73, + overviewRulerBorder = 74, + overviewRulerLanes = 75, + padding = 76, + parameterHints = 77, + peekWidgetDefaultFocus = 78, + definitionLinkOpensInPeek = 79, + quickSuggestions = 80, + quickSuggestionsDelay = 81, + readOnly = 82, + renameOnType = 83, + renderControlCharacters = 84, + renderFinalNewline = 85, + renderLineHighlight = 86, + renderLineHighlightOnlyWhenFocus = 87, + renderValidationDecorations = 88, + renderWhitespace = 89, + revealHorizontalRightPadding = 90, + roundedSelection = 91, + rulers = 92, + scrollbar = 93, + scrollBeyondLastColumn = 94, + scrollBeyondLastLine = 95, + scrollPredominantAxis = 96, + selectionClipboard = 97, + selectionHighlight = 98, + selectOnLineNumbers = 99, + showFoldingControls = 100, + showUnused = 101, + snippetSuggestions = 102, + smartSelect = 103, + smoothScrolling = 104, + stickyTabStops = 105, + stopRenderingLineAfter = 106, + suggest = 107, + suggestFontSize = 108, + suggestLineHeight = 109, + suggestOnTriggerCharacters = 110, + suggestSelection = 111, + tabCompletion = 112, + tabIndex = 113, + unicodeHighlighting = 114, + unusualLineTerminators = 115, + useShadowDOM = 116, + useTabStops = 117, + wordSeparators = 118, + wordWrap = 119, + wordWrapBreakAfterCharacters = 120, + wordWrapBreakBeforeCharacters = 121, + wordWrapColumn = 122, + wordWrapOverride1 = 123, + wordWrapOverride2 = 124, + wrappingIndent = 125, + wrappingStrategy = 126, + showDeprecated = 127, + inlayHints = 128, + editorClassName = 129, + pixelRatio = 130, + tabFocusMode = 131, + layoutInfo = 132, + wrappingInfo = 133 } /** diff --git a/src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts similarity index 62% rename from src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts rename to src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index def227ff8b3..d113f9e513c 100644 --- a/src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -5,6 +5,7 @@ import { distinct } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { relativePath } from 'vs/base/common/resources'; @@ -13,13 +14,15 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { DocumentOnDropEditProvider, SnippetTextEdit } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { extractEditorsDropData, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { extractEditorsDropData } from 'vs/workbench/browser/dnd'; export class DropIntoEditorController extends Disposable implements IEditorContribution { @@ -28,13 +31,31 @@ export class DropIntoEditorController extends Disposable implements IEditorContr constructor( editor: ICodeEditor, + @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); - editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)); + this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); + + + this._languageFeaturesService.documentOnDropEditProvider.register('*', new DefaultOnDropProvider(workspaceContextService)); + + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.experimental.editor.dropIntoEditor.enabled')) { + this.updateEditorOptions(editor); + } + })); + + this.updateEditorOptions(editor); + } + + private updateEditorOptions(editor: ICodeEditor) { + editor.updateOptions({ + enableDropIntoEditor: this._configurationService.getValue('workbench.experimental.editor.dropIntoEditor.enabled') + }); } private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) { @@ -45,7 +66,39 @@ export class DropIntoEditorController extends Disposable implements IEditorContr const model = editor.getModel(); const modelVersionNow = model.getVersionId(); + const ourDataTransfer = await this.extractDataTransferData(dragEvent); + if (ourDataTransfer.size === 0) { + return; + } + + if (editor.getModel().getVersionId() !== modelVersionNow) { + return; + } + + const cts = new CancellationTokenSource(); + editor.onDidDispose(() => cts.cancel()); + model.onDidChangeContent(() => cts.cancel()); + + const providers = this._languageFeaturesService.documentOnDropEditProvider.ordered(model); + for (const provider of providers) { + const edit = await provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, cts.token); + if (cts.token.isCancellationRequested || editor.getModel().getVersionId() !== modelVersionNow) { + return; + } + + if (edit) { + performSnippetEdit(editor, edit); + return; + } + } + } + + public async extractDataTransferData(dragEvent: DragEvent): Promise { const textEditorDataTransfer: IDataTransfer = new Map(); + if (!dragEvent.dataTransfer) { + return textEditorDataTransfer; + } + for (const item of dragEvent.dataTransfer.items) { const type = item.type; if (item.kind === 'string') { @@ -61,7 +114,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr textEditorDataTransfer.set(type, { asString: () => Promise.resolve(''), asFile: () => { - const uri = file.path ? URI.parse(file.path) : undefined; + const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; return { name: file.name, uri: uri, @@ -76,66 +129,52 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } } - if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) { + if (!textEditorDataTransfer.has(Mimes.uriList)) { const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent)) .filter(input => input.resource) .map(input => input.resource!.toString()); if (editorData.length) { + const added: IDataTransfer = new Map(); + const str = distinct(editorData).join('\n'); - textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), { - asString: () => Promise.resolve(str), + added.set(Mimes.uriList.toLowerCase(), { asFile: () => undefined, - value: undefined + asString: async () => str, + value: str, }); + return added; } } - if (textEditorDataTransfer.size === 0) { - return; - } - - if (editor.getModel().getVersionId() !== modelVersionNow) { - return; - } - - const cts = new CancellationTokenSource(); - editor.onDidDispose(() => cts.cancel()); - model.onDidChangeContent(() => cts.cancel()); - - const ordered = this._languageFeaturesService.documentOnDropEditProvider.ordered(model); - for (const provider of ordered) { - const edit = await provider.provideDocumentOnDropEdits(model, position, textEditorDataTransfer, cts.token); - if (cts.token.isCancellationRequested || editor.getModel().getVersionId() !== modelVersionNow) { - return; - } - - if (edit) { - performSnippetEdit(editor, edit); - return; - } - } - - return this.doDefaultDrop(editor, position, textEditorDataTransfer, cts.token); + return textEditorDataTransfer; } +} - private async doDefaultDrop(editor: ICodeEditor, position: IPosition, textEditorDataTransfer: IDataTransfer, token: CancellationToken): Promise { +class DefaultOnDropProvider implements DocumentOnDropEditProvider { + + constructor( + @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, + ) { } + + async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IDataTransfer, _token: CancellationToken): Promise { const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - const urlListEntry = textEditorDataTransfer.get('text/uri-list'); + const urlListEntry = dataTransfer.get('text/uri-list'); if (urlListEntry) { const urlList = await urlListEntry.asString(); - return this.doUriListDrop(editor, range, urlList, token); + return this.doUriListDrop(range, urlList); } - const textEntry = textEditorDataTransfer.get('text') ?? textEditorDataTransfer.get(Mimes.text); + const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text); if (textEntry) { const text = await textEntry.asString(); - performSnippetEdit(editor, { range, snippet: text }); + return { range, snippet: text }; } + return undefined; } - private async doUriListDrop(editor: ICodeEditor, range: Range, urlList: string, token: CancellationToken): Promise { + private doUriListDrop(range: Range, urlList: string): SnippetTextEdit | undefined { const uris: URI[] = []; for (const resource of urlList.split('\n')) { try { @@ -162,7 +201,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr }) .join(' '); - performSnippetEdit(editor, { range, snippet }); + return { range, snippet }; } } diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index fdd3f3f8d7f..aba1e89aa1d 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -19,6 +19,7 @@ import 'vs/editor/contrib/comment/browser/comment'; import 'vs/editor/contrib/contextmenu/browser/contextmenu'; import 'vs/editor/contrib/cursorUndo/browser/cursorUndo'; import 'vs/editor/contrib/dnd/browser/dnd'; +import 'vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution'; import 'vs/editor/contrib/find/browser/findController'; import 'vs/editor/contrib/folding/browser/folding'; import 'vs/editor/contrib/fontZoom/browser/fontZoom'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8390e8adca5..cba1ad5adc5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3413,6 +3413,12 @@ declare namespace monaco.editor { * Configures bracket pair colorization (disabled by default). */ bracketPairColorization?: IBracketPairColorizationOptions; + /** + * Enables dropping into the editor from an external source. + * + * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. + */ + enableDropIntoEditor?: boolean; } export interface IDiffEditorBaseOptions { @@ -4314,107 +4320,108 @@ declare namespace monaco.editor { disableMonospaceOptimizations = 29, domReadOnly = 30, dragAndDrop = 31, - emptySelectionClipboard = 32, - extraEditorClassName = 33, - fastScrollSensitivity = 34, - find = 35, - fixedOverflowWidgets = 36, - folding = 37, - foldingStrategy = 38, - foldingHighlight = 39, - foldingImportsByDefault = 40, - foldingMaximumRegions = 41, - unfoldOnClickAfterEndOfLine = 42, - fontFamily = 43, - fontInfo = 44, - fontLigatures = 45, - fontSize = 46, - fontWeight = 47, - formatOnPaste = 48, - formatOnType = 49, - glyphMargin = 50, - gotoLocation = 51, - hideCursorInOverviewRuler = 52, - hover = 53, - inDiffEditor = 54, - inlineSuggest = 55, - letterSpacing = 56, - lightbulb = 57, - lineDecorationsWidth = 58, - lineHeight = 59, - lineNumbers = 60, - lineNumbersMinChars = 61, - linkedEditing = 62, - links = 63, - matchBrackets = 64, - minimap = 65, - mouseStyle = 66, - mouseWheelScrollSensitivity = 67, - mouseWheelZoom = 68, - multiCursorMergeOverlapping = 69, - multiCursorModifier = 70, - multiCursorPaste = 71, - occurrencesHighlight = 72, - overviewRulerBorder = 73, - overviewRulerLanes = 74, - padding = 75, - parameterHints = 76, - peekWidgetDefaultFocus = 77, - definitionLinkOpensInPeek = 78, - quickSuggestions = 79, - quickSuggestionsDelay = 80, - readOnly = 81, - renameOnType = 82, - renderControlCharacters = 83, - renderFinalNewline = 84, - renderLineHighlight = 85, - renderLineHighlightOnlyWhenFocus = 86, - renderValidationDecorations = 87, - renderWhitespace = 88, - revealHorizontalRightPadding = 89, - roundedSelection = 90, - rulers = 91, - scrollbar = 92, - scrollBeyondLastColumn = 93, - scrollBeyondLastLine = 94, - scrollPredominantAxis = 95, - selectionClipboard = 96, - selectionHighlight = 97, - selectOnLineNumbers = 98, - showFoldingControls = 99, - showUnused = 100, - snippetSuggestions = 101, - smartSelect = 102, - smoothScrolling = 103, - stickyTabStops = 104, - stopRenderingLineAfter = 105, - suggest = 106, - suggestFontSize = 107, - suggestLineHeight = 108, - suggestOnTriggerCharacters = 109, - suggestSelection = 110, - tabCompletion = 111, - tabIndex = 112, - unicodeHighlighting = 113, - unusualLineTerminators = 114, - useShadowDOM = 115, - useTabStops = 116, - wordSeparators = 117, - wordWrap = 118, - wordWrapBreakAfterCharacters = 119, - wordWrapBreakBeforeCharacters = 120, - wordWrapColumn = 121, - wordWrapOverride1 = 122, - wordWrapOverride2 = 123, - wrappingIndent = 124, - wrappingStrategy = 125, - showDeprecated = 126, - inlayHints = 127, - editorClassName = 128, - pixelRatio = 129, - tabFocusMode = 130, - layoutInfo = 131, - wrappingInfo = 132 + enableDropIntoEditor = 32, + emptySelectionClipboard = 33, + extraEditorClassName = 34, + fastScrollSensitivity = 35, + find = 36, + fixedOverflowWidgets = 37, + folding = 38, + foldingStrategy = 39, + foldingHighlight = 40, + foldingImportsByDefault = 41, + foldingMaximumRegions = 42, + unfoldOnClickAfterEndOfLine = 43, + fontFamily = 44, + fontInfo = 45, + fontLigatures = 46, + fontSize = 47, + fontWeight = 48, + formatOnPaste = 49, + formatOnType = 50, + glyphMargin = 51, + gotoLocation = 52, + hideCursorInOverviewRuler = 53, + hover = 54, + inDiffEditor = 55, + inlineSuggest = 56, + letterSpacing = 57, + lightbulb = 58, + lineDecorationsWidth = 59, + lineHeight = 60, + lineNumbers = 61, + lineNumbersMinChars = 62, + linkedEditing = 63, + links = 64, + matchBrackets = 65, + minimap = 66, + mouseStyle = 67, + mouseWheelScrollSensitivity = 68, + mouseWheelZoom = 69, + multiCursorMergeOverlapping = 70, + multiCursorModifier = 71, + multiCursorPaste = 72, + occurrencesHighlight = 73, + overviewRulerBorder = 74, + overviewRulerLanes = 75, + padding = 76, + parameterHints = 77, + peekWidgetDefaultFocus = 78, + definitionLinkOpensInPeek = 79, + quickSuggestions = 80, + quickSuggestionsDelay = 81, + readOnly = 82, + renameOnType = 83, + renderControlCharacters = 84, + renderFinalNewline = 85, + renderLineHighlight = 86, + renderLineHighlightOnlyWhenFocus = 87, + renderValidationDecorations = 88, + renderWhitespace = 89, + revealHorizontalRightPadding = 90, + roundedSelection = 91, + rulers = 92, + scrollbar = 93, + scrollBeyondLastColumn = 94, + scrollBeyondLastLine = 95, + scrollPredominantAxis = 96, + selectionClipboard = 97, + selectionHighlight = 98, + selectOnLineNumbers = 99, + showFoldingControls = 100, + showUnused = 101, + snippetSuggestions = 102, + smartSelect = 103, + smoothScrolling = 104, + stickyTabStops = 105, + stopRenderingLineAfter = 106, + suggest = 107, + suggestFontSize = 108, + suggestLineHeight = 109, + suggestOnTriggerCharacters = 110, + suggestSelection = 111, + tabCompletion = 112, + tabIndex = 113, + unicodeHighlighting = 114, + unusualLineTerminators = 115, + useShadowDOM = 116, + useTabStops = 117, + wordSeparators = 118, + wordWrap = 119, + wordWrapBreakAfterCharacters = 120, + wordWrapBreakBeforeCharacters = 121, + wordWrapColumn = 122, + wordWrapOverride1 = 123, + wordWrapOverride2 = 124, + wrappingIndent = 125, + wrappingStrategy = 126, + showDeprecated = 127, + inlayHints = 128, + editorClassName = 129, + pixelRatio = 130, + tabFocusMode = 131, + layoutInfo = 132, + wrappingInfo = 133 } export const EditorOptions: { @@ -4452,6 +4459,7 @@ declare namespace monaco.editor { domReadOnly: IEditorOption; dragAndDrop: IEditorOption; emptySelectionClipboard: IEditorOption; + enableDropIntoEditor: IEditorOption; extraEditorClassName: IEditorOption; fastScrollSensitivity: IEditorOption; find: IEditorOption>>; @@ -4573,12 +4581,6 @@ declare namespace monaco.editor { * Defaults to an internal DOM node. */ overflowWidgetsDomNode?: HTMLElement; - /** - * Enables dropping into the editor. - * - * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. - */ - enableDropIntoEditor?: boolean; } /** diff --git a/src/vs/platform/dnd/browser/dnd.ts b/src/vs/platform/dnd/browser/dnd.ts new file mode 100644 index 00000000000..14c6c8d11d3 --- /dev/null +++ b/src/vs/platform/dnd/browser/dnd.ts @@ -0,0 +1,339 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DataTransfers } from 'vs/base/browser/dnd'; +import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; +import { coalesce } from 'vs/base/common/arrays'; +import { DeferredPromise } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse } from 'vs/base/common/marshalling'; +import { Schemas } from 'vs/base/common/network'; +import { isWeb } from 'vs/base/common/platform'; +import Severity from 'vs/base/common/severity'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IBaseTextResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; +import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { ByteSize, IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { extractSelection } from 'vs/platform/opener/common/opener'; +import { Registry } from 'vs/platform/registry/common/platform'; + +export interface FileAdditionalNativeProperties { + /** + * The real path to the file on the users filesystem. Only available on electron. + */ + readonly path?: string; +} + + +//#region Editor / Resources DND + +export const CodeDataTransfers = { + EDITORS: 'CodeEditors', + FILES: 'CodeFiles' +}; + +export interface IDraggedResourceEditorInput extends IBaseTextResourceEditorInput { + resource: URI | undefined; + + /** + * A hint that the source of the dragged editor input + * might not be the application but some external tool. + */ + isExternal?: boolean; + + /** + * Whether we probe for the dropped editor to be a workspace + * (i.e. code-workspace file or even a folder), allowing to + * open it as workspace instead of opening as editor. + */ + allowWorkspaceOpen?: boolean; +} + +export async function extractEditorsDropData(accessor: ServicesAccessor, e: DragEvent): Promise> { + const editors: IDraggedResourceEditorInput[] = []; + if (e.dataTransfer && e.dataTransfer.types.length > 0) { + + // Data Transfer: Code Editors + const rawEditorsData = e.dataTransfer.getData(CodeDataTransfers.EDITORS); + if (rawEditorsData) { + try { + editors.push(...parse(rawEditorsData)); + } catch (error) { + // Invalid transfer + } + } + + // Data Transfer: Resources + else { + try { + const rawResourcesData = e.dataTransfer.getData(DataTransfers.RESOURCES); + editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); + } catch (error) { + // Invalid transfer + } + } + + // Check for native file transfer + if (e.dataTransfer?.files) { + for (let i = 0; i < e.dataTransfer.files.length; i++) { + const file = e.dataTransfer.files[i]; + if (file && (file as FileAdditionalNativeProperties).path /* Electron only */) { + try { + editors.push({ resource: URI.file((file as FileAdditionalNativeProperties).path!), isExternal: true, allowWorkspaceOpen: true }); + } catch (error) { + // Invalid URI + } + } + } + } + + // Check for CodeFiles transfer + const rawCodeFiles = e.dataTransfer.getData(CodeDataTransfers.FILES); + if (rawCodeFiles) { + try { + const codeFiles: string[] = JSON.parse(rawCodeFiles); + for (const codeFile of codeFiles) { + editors.push({ resource: URI.file(codeFile), isExternal: true, allowWorkspaceOpen: true }); + } + } catch (error) { + // Invalid transfer + } + } + + // Web: Check for file transfer + if (isWeb && containsDragType(e, DataTransfers.FILES)) { + const files = e.dataTransfer.items; + if (files) { + const instantiationService = accessor.get(IInstantiationService); + const filesData = await instantiationService.invokeFunction(accessor => extractFilesDropData(accessor, e)); + for (const fileData of filesData) { + editors.push({ resource: fileData.resource, contents: fileData.contents?.toString(), isExternal: true, allowWorkspaceOpen: fileData.isDirectory }); + } + } + } + + // Workbench contributions + const contributions = Registry.as(Extensions.DragAndDropContribution).getAll(); + for (const contribution of contributions) { + const data = e.dataTransfer.getData(contribution.dataFormatKey); + if (data) { + try { + editors.push(...contribution.getEditorInputs(data)); + } catch (error) { + // Invalid transfer + } + } + } + } + + return editors; +} + +export function createDraggedEditorInputFromRawResourcesData(rawResourcesData: string | undefined): IDraggedResourceEditorInput[] { + const editors: IDraggedResourceEditorInput[] = []; + + if (rawResourcesData) { + const resourcesRaw: string[] = JSON.parse(rawResourcesData); + for (const resourceRaw of resourcesRaw) { + if (resourceRaw.indexOf(':') > 0) { // mitigate https://github.com/microsoft/vscode/issues/124946 + const { selection, uri } = extractSelection(URI.parse(resourceRaw)); + editors.push({ resource: uri, options: { selection } }); + } + } + } + + return editors; +} + + +interface IFileTransferData { + resource: URI; + isDirectory?: boolean; + contents?: VSBuffer; +} + +async function extractFilesDropData(accessor: ServicesAccessor, event: DragEvent): Promise { + + // Try to extract via `FileSystemHandle` + if (WebFileSystemAccess.supported(window)) { + const items = event.dataTransfer?.items; + if (items) { + return extractFileTransferData(accessor, items); + } + } + + // Try to extract via `FileList` + const files = event.dataTransfer?.files; + if (!files) { + return []; + } + + return extractFileListData(accessor, files); +} + +async function extractFileTransferData(accessor: ServicesAccessor, items: DataTransferItemList): Promise { + const fileSystemProvider = accessor.get(IFileService).getProvider(Schemas.file); + if (!(fileSystemProvider instanceof HTMLFileSystemProvider)) { + return []; // only supported when running in web + } + + const results: DeferredPromise[] = []; + + for (let i = 0; i < items.length; i++) { + const file = items[i]; + if (file) { + const result = new DeferredPromise(); + results.push(result); + + (async () => { + try { + const handle = await file.getAsFileSystemHandle(); + if (!handle) { + result.complete(undefined); + return; + } + + if (WebFileSystemAccess.isFileSystemFileHandle(handle)) { + result.complete({ + resource: await fileSystemProvider.registerFileHandle(handle), + isDirectory: false + }); + } else if (WebFileSystemAccess.isFileSystemDirectoryHandle(handle)) { + result.complete({ + resource: await fileSystemProvider.registerDirectoryHandle(handle), + isDirectory: true + }); + } else { + result.complete(undefined); + } + } catch (error) { + result.complete(undefined); + } + })(); + } + } + + return coalesce(await Promise.all(results.map(result => result.p))); +} + +export async function extractFileListData(accessor: ServicesAccessor, files: FileList): Promise { + const dialogService = accessor.get(IDialogService); + + const results: DeferredPromise[] = []; + + for (let i = 0; i < files.length; i++) { + const file = files.item(i); + if (file) { + + // Skip for very large files because this operation is unbuffered + if (file.size > 100 * ByteSize.MB) { + dialogService.show(Severity.Warning, localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again.")); + continue; + } + + const result = new DeferredPromise(); + results.push(result); + + const reader = new FileReader(); + + reader.onerror = () => result.complete(undefined); + reader.onabort = () => result.complete(undefined); + + reader.onload = async event => { + const name = file.name; + const loadResult = withNullAsUndefined(event.target?.result); + if (typeof name !== 'string' || typeof loadResult === 'undefined') { + result.complete(undefined); + return; + } + + result.complete({ + resource: URI.from({ scheme: Schemas.untitled, path: name }), + contents: typeof loadResult === 'string' ? VSBuffer.fromString(loadResult) : VSBuffer.wrap(new Uint8Array(loadResult)) + }); + }; + + // Start reading + reader.readAsArrayBuffer(file); + } + } + + return coalesce(await Promise.all(results.map(result => result.p))); +} + +//#endregion + +export function containsDragType(event: DragEvent, ...dragTypesToFind: string[]): boolean { + if (!event.dataTransfer) { + return false; + } + + const dragTypes = event.dataTransfer.types; + const lowercaseDragTypes: string[] = []; + for (let i = 0; i < dragTypes.length; i++) { + lowercaseDragTypes.push(dragTypes[i].toLowerCase()); // somehow the types are lowercase + } + + for (const dragType of dragTypesToFind) { + if (lowercaseDragTypes.indexOf(dragType.toLowerCase()) >= 0) { + return true; + } + } + + return false; +} + +//#region DND contributions + +export interface IResourceStat { + resource: URI; + isDirectory?: boolean; +} + +export interface IDragAndDropContributionRegistry { + /** + * Registers a drag and drop contribution. + */ + register(contribution: IDragAndDropContribution): void; + + /** + * Returns all registered drag and drop contributions. + */ + getAll(): IterableIterator; +} + +export interface IDragAndDropContribution { + readonly dataFormatKey: string; + getEditorInputs(data: string): IDraggedResourceEditorInput[]; + setData(resources: IResourceStat[], event: DragMouseEvent | DragEvent): void; +} + +class DragAndDropContributionRegistry implements IDragAndDropContributionRegistry { + private readonly _contributions = new Map(); + + register(contribution: IDragAndDropContribution): void { + if (this._contributions.has(contribution.dataFormatKey)) { + throw new Error(`A drag and drop contributiont with key '${contribution.dataFormatKey}' was already registered.`); + } + this._contributions.set(contribution.dataFormatKey, contribution); + } + + getAll(): IterableIterator { + return this._contributions.values(); + } +} + +export const Extensions = { + DragAndDropContribution: 'workbench.contributions.dragAndDrop' +}; + +Registry.add(Extensions.DragAndDropContribution, new DragAndDropContributionRegistry()); + +//#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 07d04a05c60..16fada27b5b 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -30,7 +30,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 54e79e9918c..6e96ff8161f 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ILogService } from 'vs/platform/log/common/log'; import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; import { VSBuffer } from 'vs/base/common/buffer'; import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache'; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 960b8d39a9e..b984ae6569e 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -25,7 +25,7 @@ import { Command } from 'vs/editor/common/languages'; import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; type TreeItemHandle = string; diff --git a/src/vs/workbench/api/common/shared/dataTransfer.ts b/src/vs/workbench/api/common/shared/dataTransfer.ts index dd4749b607b..a16dfe65f96 100644 --- a/src/vs/workbench/api/common/shared/dataTransfer.ts +++ b/src/vs/workbench/api/common/shared/dataTransfer.ts @@ -5,7 +5,7 @@ import { once } from 'vs/base/common/functional'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd'; +import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; export interface IDataTransferFileDTO { readonly name: string; diff --git a/src/vs/workbench/api/common/shared/dataTransferCache.ts b/src/vs/workbench/api/common/shared/dataTransferCache.ts index c3e845c61d4..02184d68d18 100644 --- a/src/vs/workbench/api/common/shared/dataTransferCache.ts +++ b/src/vs/workbench/api/common/shared/dataTransferCache.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from 'vs/base/common/buffer'; -import { IDataTransfer, IDataTransferItem } from 'vs/editor/common/dnd'; +import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; export class DataTransferCache { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index b1f9001e03c..63cfe74d6de 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -3,45 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { VSBuffer } from 'vs/base/common/buffer'; -import Severity from 'vs/base/common/severity'; -import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { basename, isEqual } from 'vs/base/common/resources'; -import { ByteSize, IFileService } from 'vs/platform/files/common/files'; -import { IWindowOpenable } from 'vs/platform/window/common/window'; -import { URI } from 'vs/base/common/uri'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileAccess, Schemas } from 'vs/base/common/network'; -import { IBaseTextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; -import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Mimes } from 'vs/base/common/mime'; -import { isWeb, isWindows } from 'vs/base/common/platform'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorIdentifier, GroupIdentifier, isEditorIdentifier, EditorResourceAccessor } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addDisposableListener, DragAndDropObserver, EventType } from 'vs/base/browser/dom'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { Emitter } from 'vs/base/common/event'; -import { coalesce } from 'vs/base/common/arrays'; -import { parse, stringify } from 'vs/base/common/marshalling'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { hasWorkspaceFileExtension, isTemporaryWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { IDataTransfer } from 'vs/editor/common/dnd'; -import { extractSelection } from 'vs/platform/opener/common/opener'; +import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListDragAndDrop } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; -import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; -import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; -import { DeferredPromise } from 'vs/base/common/async'; +import { coalesce } from 'vs/base/common/arrays'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { stringify } from 'vs/base/common/marshalling'; +import { Mimes } from 'vs/base/common/mime'; +import { FileAccess, Schemas } from 'vs/base/common/network'; +import { isWindows } from 'vs/base/common/platform'; +import { basename, isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { CodeDataTransfers, createDraggedEditorInputFromRawResourcesData, Extensions, extractEditorsDropData, IDragAndDropContributionRegistry, IDraggedResourceEditorInput, IResourceStat } from 'vs/platform/dnd/browser/dnd'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { extractSelection } from 'vs/platform/opener/common/opener'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IWindowOpenable } from 'vs/platform/window/common/window'; +import { hasWorkspaceFileExtension, isTemporaryWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { EditorResourceAccessor, GroupIdentifier, IEditorIdentifier, isEditorIdentifier } from 'vs/workbench/common/editor'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; //#region Editor / Resources DND @@ -60,124 +52,6 @@ export class DraggedTreeItemsIdentifier { constructor(readonly identifier: string) { } } -export const CodeDataTransfers = { - EDITORS: 'CodeEditors', - FILES: 'CodeFiles' -}; - -export interface IDraggedResourceEditorInput extends IBaseTextResourceEditorInput { - resource: URI | undefined; - - /** - * A hint that the source of the dragged editor input - * might not be the application but some external tool. - */ - isExternal?: boolean; - - /** - * Whether we probe for the dropped editor to be a workspace - * (i.e. code-workspace file or even a folder), allowing to - * open it as workspace instead of opening as editor. - */ - allowWorkspaceOpen?: boolean; -} - -export async function extractEditorsDropData(accessor: ServicesAccessor, e: DragEvent): Promise> { - const editors: IDraggedResourceEditorInput[] = []; - if (e.dataTransfer && e.dataTransfer.types.length > 0) { - - // Data Transfer: Code Editors - const rawEditorsData = e.dataTransfer.getData(CodeDataTransfers.EDITORS); - if (rawEditorsData) { - try { - editors.push(...parse(rawEditorsData)); - } catch (error) { - // Invalid transfer - } - } - - // Data Transfer: Resources - else { - try { - const rawResourcesData = e.dataTransfer.getData(DataTransfers.RESOURCES); - editors.push(...createDraggedEditorInputFromRawResourcesData(rawResourcesData)); - } catch (error) { - // Invalid transfer - } - } - - // Check for native file transfer - if (e.dataTransfer?.files) { - for (let i = 0; i < e.dataTransfer.files.length; i++) { - const file = e.dataTransfer.files[i]; - if (file?.path /* Electron only */) { - try { - editors.push({ resource: URI.file(file.path), isExternal: true, allowWorkspaceOpen: true }); - } catch (error) { - // Invalid URI - } - } - } - } - - // Check for CodeFiles transfer - const rawCodeFiles = e.dataTransfer.getData(CodeDataTransfers.FILES); - if (rawCodeFiles) { - try { - const codeFiles: string[] = JSON.parse(rawCodeFiles); - for (const codeFile of codeFiles) { - editors.push({ resource: URI.file(codeFile), isExternal: true, allowWorkspaceOpen: true }); - } - } catch (error) { - // Invalid transfer - } - } - - // Web: Check for file transfer - if (isWeb && containsDragType(e, DataTransfers.FILES)) { - const files = e.dataTransfer.items; - if (files) { - const instantiationService = accessor.get(IInstantiationService); - const filesData = await instantiationService.invokeFunction(accessor => extractFilesDropData(accessor, e)); - for (const fileData of filesData) { - editors.push({ resource: fileData.resource, contents: fileData.contents?.toString(), isExternal: true, allowWorkspaceOpen: fileData.isDirectory }); - } - } - } - - // Workbench contributions - const contributions = Registry.as(Extensions.DragAndDropContribution).getAll(); - for (const contribution of contributions) { - const data = e.dataTransfer.getData(contribution.dataFormatKey); - if (data) { - try { - editors.push(...contribution.getEditorInputs(data)); - } catch (error) { - // Invalid transfer - } - } - } - } - - return editors; -} - -function createDraggedEditorInputFromRawResourcesData(rawResourcesData: string | undefined): IDraggedResourceEditorInput[] { - const editors: IDraggedResourceEditorInput[] = []; - - if (rawResourcesData) { - const resourcesRaw: string[] = JSON.parse(rawResourcesData); - for (const resourceRaw of resourcesRaw) { - if (resourceRaw.indexOf(':') > 0) { // mitigate https://github.com/microsoft/vscode/issues/124946 - const { selection, uri } = extractSelection(URI.parse(resourceRaw)); - editors.push({ resource: uri, options: { selection } }); - } - } - } - - return editors; -} - export async function extractTreeDropData(dataTransfer: IDataTransfer): Promise> { const editors: IDraggedResourceEditorInput[] = []; const resourcesKey = Mimes.uriList.toLowerCase(); @@ -201,121 +75,6 @@ export function convertResourceUrlsToUriList(resourceUrls: string): string { return asJson.map(uri => uri.toString()).join('\n'); } -interface IFileTransferData { - resource: URI; - isDirectory?: boolean; - contents?: VSBuffer; -} - -async function extractFilesDropData(accessor: ServicesAccessor, event: DragEvent): Promise { - - // Try to extract via `FileSystemHandle` - if (WebFileSystemAccess.supported(window)) { - const items = event.dataTransfer?.items; - if (items) { - return extractFileTransferData(accessor, items); - } - } - - // Try to extract via `FileList` - const files = event.dataTransfer?.files; - if (!files) { - return []; - } - - return extractFileListData(accessor, files); -} - -async function extractFileTransferData(accessor: ServicesAccessor, items: DataTransferItemList): Promise { - const fileSystemProvider = accessor.get(IFileService).getProvider(Schemas.file); - if (!(fileSystemProvider instanceof HTMLFileSystemProvider)) { - return []; // only supported when running in web - } - - const results: DeferredPromise[] = []; - - for (let i = 0; i < items.length; i++) { - const file = items[i]; - if (file) { - const result = new DeferredPromise(); - results.push(result); - - (async () => { - try { - const handle = await file.getAsFileSystemHandle(); - if (!handle) { - result.complete(undefined); - return; - } - - if (WebFileSystemAccess.isFileSystemFileHandle(handle)) { - result.complete({ - resource: await fileSystemProvider.registerFileHandle(handle), - isDirectory: false - }); - } else if (WebFileSystemAccess.isFileSystemDirectoryHandle(handle)) { - result.complete({ - resource: await fileSystemProvider.registerDirectoryHandle(handle), - isDirectory: true - }); - } else { - result.complete(undefined); - } - } catch (error) { - result.complete(undefined); - } - })(); - } - } - - return coalesce(await Promise.all(results.map(result => result.p))); -} - -export async function extractFileListData(accessor: ServicesAccessor, files: FileList): Promise { - const dialogService = accessor.get(IDialogService); - - const results: DeferredPromise[] = []; - - for (let i = 0; i < files.length; i++) { - const file = files.item(i); - if (file) { - - // Skip for very large files because this operation is unbuffered - if (file.size > 100 * ByteSize.MB) { - dialogService.show(Severity.Warning, localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again.")); - continue; - } - - const result = new DeferredPromise(); - results.push(result); - - const reader = new FileReader(); - - reader.onerror = () => result.complete(undefined); - reader.onabort = () => result.complete(undefined); - - reader.onload = async event => { - const name = file.name; - const loadResult = withNullAsUndefined(event.target?.result); - if (typeof name !== 'string' || typeof loadResult === 'undefined') { - result.complete(undefined); - return; - } - - result.complete({ - resource: URI.from({ scheme: Schemas.untitled, path: name }), - contents: typeof loadResult === 'string' ? VSBuffer.fromString(loadResult) : VSBuffer.wrap(new Uint8Array(loadResult)) - }); - }; - - // Start reading - reader.readAsArrayBuffer(file); - } - } - - return coalesce(await Promise.all(results.map(result => result.p))); -} - export interface IResourcesDropHandlerOptions { /** @@ -443,11 +202,6 @@ export class ResourcesDropHandler { } } -interface IResourceStat { - resource: URI; - isDirectory?: boolean; -} - export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent): void; export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent): void; export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent): void; @@ -591,48 +345,6 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito //#endregion -//#region DND contributions - -export interface IDragAndDropContributionRegistry { - /** - * Registers a drag and drop contribution. - */ - register(contribution: IDragAndDropContribution): void; - - /** - * Returns all registered drag and drop contributions. - */ - getAll(): IterableIterator; -} - -export interface IDragAndDropContribution { - readonly dataFormatKey: string; - getEditorInputs(data: string): IDraggedResourceEditorInput[]; - setData(resources: IResourceStat[], event: DragMouseEvent | DragEvent): void; -} - -class DragAndDropContributionRegistry implements IDragAndDropContributionRegistry { - private readonly _contributions = new Map(); - - register(contribution: IDragAndDropContribution): void { - if (this._contributions.has(contribution.dataFormatKey)) { - throw new Error(`A drag and drop contributiont with key '${contribution.dataFormatKey}' was already registered.`); - } - this._contributions.set(contribution.dataFormatKey, contribution); - } - - getAll(): IterableIterator { - return this._contributions.values(); - } -} - -export const Extensions = { - DragAndDropContribution: 'workbench.contributions.dragAndDrop' -}; - -Registry.add(Extensions.DragAndDropContribution, new DragAndDropContributionRegistry()); - -//#endregion //#region DND Utilities @@ -681,26 +393,6 @@ export class LocalSelectionTransfer { } } -export function containsDragType(event: DragEvent, ...dragTypesToFind: string[]): boolean { - if (!event.dataTransfer) { - return false; - } - - const dragTypes = event.dataTransfer.types; - const lowercaseDragTypes: string[] = []; - for (let i = 0; i < dragTypes.length; i++) { - lowercaseDragTypes.push(dragTypes[i].toLowerCase()); // somehow the types are lowercase - } - - for (const dragType of dragTypesToFind) { - if (lowercaseDragTypes.indexOf(dragType.toLowerCase()) >= 0) { - return true; - } - } - - return false; -} - //#endregion //#region Composites DND diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index f81d3790b5d..ad6a0289f12 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -18,7 +18,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { isTemporaryWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { CodeDataTransfers, containsDragType, DraggedEditorGroupIdentifier, DraggedEditorIdentifier, DraggedTreeItemsIdentifier, Extensions as DragAndDropExtensions, extractTreeDropData, IDragAndDropContributionRegistry, LocalSelectionTransfer, ResourcesDropHandler } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, containsDragType, Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry } from 'vs/platform/dnd/browser/dnd'; +import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, DraggedTreeItemsIdentifier, extractTreeDropData, LocalSelectionTransfer, ResourcesDropHandler } from 'vs/workbench/browser/dnd'; import { fillActiveEditorViewState, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_DROP_INTO_PROMPT_BACKGROUND, EDITOR_DROP_INTO_PROMPT_BORDER, EDITOR_DROP_INTO_PROMPT_FOREGROUND } from 'vs/workbench/common/theme'; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 9b06e86ae1f..9d2c5d0dab0 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -31,7 +31,7 @@ import { isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/views'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; import { Command } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -54,7 +54,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { FileThemeIcon, FolderThemeIcon, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { CodeDataTransfers, convertResourceUrlsToUriList, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; +import { convertResourceUrlsToUriList, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -66,6 +66,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; +import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; export class TreeViewPane extends ViewPane { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 7bd541c1248..5c9c7d93a9d 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -27,7 +27,7 @@ import { mixin } from 'vs/base/common/objects'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 99b7c3eb1f1..0d895be2eae 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -58,7 +58,7 @@ import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/act import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { coalesce } from 'vs/base/common/arrays'; -import { extractEditorsDropData } from 'vs/workbench/browser/dnd'; +import { extractEditorsDropData } from 'vs/platform/dnd/browser/dnd'; import { extname } from 'vs/base/common/resources'; const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 71eda39201c..2215ecd93ad 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -20,7 +20,7 @@ import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { extractEditorsDropData } from 'vs/workbench/browser/dnd'; +import { extractEditorsDropData } from 'vs/platform/dnd/browser/dnd'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { isWeb } from 'vs/base/common/platform'; import { triggerDownload } from 'vs/base/browser/dom'; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 1181f9867c4..a24f390cf51 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -30,7 +30,8 @@ import { equals, deepClone } from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { compareFileExtensionsDefault, compareFileNamesDefault, compareFileNamesUpper, compareFileExtensionsUpper, compareFileNamesLower, compareFileExtensionsLower, compareFileNamesUnicode, compareFileExtensionsUnicode } from 'vs/base/common/comparers'; -import { fillEditorsDragData, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; +import { fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 06ba5acdbde..fdfcc0a46a0 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -32,7 +32,8 @@ import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/m import { IMenuService, MenuId, IMenu, Action2, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions'; import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext, SAVE_ALL_LABEL, SAVE_ALL_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; -import { ResourcesDropHandler, fillEditorsDragData, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; +import { ResourcesDropHandler, fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e7d863ed61c..a3059234eb1 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -85,6 +85,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/mar import { Button, ButtonWithDescription } from 'vs/base/browser/ui/button/button'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { RepositoryContextKeys } from 'vs/workbench/contrib/scm/browser/scmViewService'; +import { DropIntoEditorController } from 'vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution'; type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -1935,7 +1936,8 @@ class SCMInputWidget extends Disposable { quickSuggestions: false, scrollbar: { alwaysConsumeMouseWheel: false }, overflowWidgetsDomNode, - renderWhitespace: 'none' + renderWhitespace: 'none', + enableDropIntoEditor: true, }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { @@ -1948,7 +1950,8 @@ class SCMInputWidget extends Disposable { ContextMenuController.ID, ColorDetector.ID, ModesHoverController.ID, - LinkDetector.ID + LinkDetector.ID, + DropIntoEditorController.ID ]) }; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index f8aae95de73..104e9f455e1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -16,7 +16,7 @@ import { KeybindingWeight, KeybindingsRegistry, IKeybindings } from 'vs/platform import { Registry } from 'vs/platform/registry/common/platform'; import { getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; -import { Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry, IDraggedResourceEditorInput } from 'vs/workbench/browser/dnd'; +import { Extensions as DragAndDropExtensions, IDragAndDropContributionRegistry, IDraggedResourceEditorInput } from 'vs/platform/dnd/browser/dnd'; import { registerTerminalActions, terminalSendSequenceCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { TERMINAL_VIEW_ID, TerminalCommandId, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index f845f8ddc33..61c2efbb885 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -48,7 +48,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; -import { CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDetectedLinks, TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { TerminalLinkQuickpick } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 868644fc918..0a39093d05e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -40,7 +40,7 @@ import { once } from 'vs/base/common/functional'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; +import { CodeDataTransfers, containsDragType } from 'vs/platform/dnd/browser/dnd'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IProcessDetails } from 'vs/platform/terminal/common/terminalProcess'; diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 1fc0ccc3570..87f6e2ca052 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -16,7 +16,7 @@ import { basename } from 'vs/base/common/resources'; import { triggerDownload, triggerUpload } from 'vs/base/browser/dom'; import Severity from 'vs/base/common/severity'; import { VSBuffer } from 'vs/base/common/buffer'; -import { extractFileListData } from 'vs/workbench/browser/dnd'; +import { extractFileListData } from 'vs/platform/dnd/browser/dnd'; import { Iterable } from 'vs/base/common/iterator'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; diff --git a/src/vs/workbench/services/views/browser/treeViewsService.ts b/src/vs/workbench/services/views/browser/treeViewsService.ts index 988300aadee..93d426deed5 100644 --- a/src/vs/workbench/services/views/browser/treeViewsService.ts +++ b/src/vs/workbench/services/views/browser/treeViewsService.ts @@ -5,7 +5,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDataTransfer } from 'vs/editor/common/dnd'; +import { IDataTransfer } from 'vs/base/common/dataTransfer'; import { ITreeItem } from 'vs/workbench/common/views'; import { ITreeViewsService as ITreeViewsServiceCommon, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index bf87db19d53..992aec9c53f 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -343,7 +343,4 @@ import 'vs/workbench/contrib/list/browser/list.contribution'; // Audio Cues import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; -// Drop into editor -import 'vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution'; - //#endregion From 1181a658000b021a70de82d407e9d8b6684b4156 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 17 May 2022 12:10:25 -0700 Subject: [PATCH 108/350] add back running on close (#149751) --- .github/workflows/pr-chat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-chat.yml b/.github/workflows/pr-chat.yml index 969c3cc17c0..0c8d6bab793 100644 --- a/.github/workflows/pr-chat.yml +++ b/.github/workflows/pr-chat.yml @@ -1,7 +1,7 @@ name: PR Chat on: pull_request_target: - types: [opened, ready_for_review] + types: [opened, ready_for_review, closed] jobs: main: From 18cf3d01ed22f7b35895a9b3c531048c835d4489 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 May 2022 12:13:44 -0700 Subject: [PATCH 109/350] Polish notebook workspace edit (#149734) - Add `insertCells` helper - Add a `newNotebookMetadata` property for setting the metadata for the entire notebook - Add a `NotebookEdit.updateNotebookMetadata` helper - Remove the previous `WorkspaceEdit.updateNotebookMetadata` function since you can now use `NotebookEdit.updateNotebookMetadata` --- src/vs/workbench/api/common/extHostTypes.ts | 24 ++++++++--- ...vscode.proposed.notebookWorkspaceEdit.d.ts | 43 +++++++++++++------ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5976d7d182e..dc08ede3d98 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -616,22 +616,34 @@ export class NotebookEdit implements vscode.NotebookEdit { return new NotebookEdit(range, newCells); } + static insertCells(index: number, newCells: vscode.NotebookCellData[]): vscode.NotebookEdit { + return new NotebookEdit(new NotebookRange(index, index), newCells); + } + static deleteCells(range: NotebookRange): NotebookEdit { return new NotebookEdit(range, []); } static updateCellMetadata(index: number, newMetadata: { [key: string]: any }): NotebookEdit { - return new NotebookEdit(new NotebookRange(index, index), [], newMetadata); + const edit = new NotebookEdit(new NotebookRange(index, index), []); + edit.newCellMetadata = newMetadata; + return edit; } - readonly range: NotebookRange; - readonly newCells: NotebookCellData[]; - readonly newCellMetadata?: { [key: string]: any }; + static updateNotebookMetadata(newMetadata: { [key: string]: any }): NotebookEdit { + const edit = new NotebookEdit(new NotebookRange(0, 0), []); + edit.newNotebookMetadata = newMetadata; + return edit; + } - constructor(range: NotebookRange, newCells: NotebookCellData[], newCellMetadata?: { [key: string]: any }) { + range: NotebookRange; + newCells: NotebookCellData[]; + newCellMetadata?: { [key: string]: any }; + newNotebookMetadata?: { [key: string]: any }; + + constructor(range: NotebookRange, newCells: NotebookCellData[]) { this.range = range; this.newCells = newCells; - this.newCellMetadata = newCellMetadata; } } diff --git a/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts b/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts index 01330ec9cdd..14965fbe054 100644 --- a/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts @@ -21,44 +21,59 @@ declare module 'vscode' { static replaceCells(range: NotebookRange, newCells: NotebookCellData[]): NotebookEdit; /** - * Utility to create a edit that deletes cells in a notebook. + * Utility to create an edit that replaces cells in a notebook. + * + * @param index The index to insert cells at. + * @param newCells The new notebook cells. + */ + static insertCells(index: number, newCells: NotebookCellData[]): NotebookEdit; + + /** + * Utility to create an edit that deletes cells in a notebook. * * @param range The range of cells to delete. */ static deleteCells(range: NotebookRange): NotebookEdit; /** - * Utility to update a cells metadata. + * Utility to create an edit that update a cell's metadata. * * @param index The index of the cell to update. - * @param newMetadata The new metadata for the cell. + * @param newCellMetadata The new metadata for the cell. */ - static updateCellMetadata(index: number, newMetadata: { [key: string]: any }): NotebookEdit; + static updateCellMetadata(index: number, newCellMetadata: { [key: string]: any }): NotebookEdit; /** - * Range of the cells being edited + * Utility to create an edit that updates the notebook's metadata. + * + * @param newNotebookMetadata The new metadata for the notebook. */ - readonly range: NotebookRange; + static updateNotebookMetadata(newNotebookMetadata: { [key: string]: any }): NotebookEdit; + + /** + * Range of the cells being edited. May be empty. + */ + range: NotebookRange; /** * New cells being inserted. May be empty. */ - readonly newCells: NotebookCellData[]; + newCells: NotebookCellData[]; /** * Optional new metadata for the cells. */ - readonly newCellMetadata?: { [key: string]: any }; + newCellMetadata?: { [key: string]: any }; - constructor(range: NotebookRange, newCells: NotebookCellData[], newCellMetadata?: { [key: string]: any }); + /** + * Optional new metadata for the notebook. + */ + newNotebookMetadata?: { [key: string]: any }; + + constructor(range: NotebookRange, newCells: NotebookCellData[]); } export interface WorkspaceEdit { - /** - * Replaces the metadata for a notebook document. - */ - replaceNotebookMetadata(uri: Uri, value: { [key: string]: any }): void; - /** * Set (and replace) edits for a resource. * From ca4db53d718f07c8b9732b9a59322b6ecc574fe4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 12:14:54 -0700 Subject: [PATCH 110/350] Skip shell integration smoke tests on mac as well Part of #149757 --- .../smoke/src/areas/terminal/terminal-shellIntegration.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index b94b40045f7..a5f5842b4c6 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -26,7 +26,8 @@ export function setup() { describe('Shell integration', function () { // TODO: Fix on Linux, some distros use sh as the default shell in which case shell integration will fail - (process.platform === 'win32' || process.platform === 'linux' ? describe.skip : describe)('Decorations', function () { + // TODO: Fix on macOS, not sure reason for failing https://github.com/microsoft/vscode/issues/149757 + describe.skip('Decorations', function () { describe('Should show default icons', function () { it('Placeholder', async () => { await terminal.createTerminal(); From 769a8f15c725e203bcf8ee79055588caabd95c2d Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 17 May 2022 15:29:22 -0400 Subject: [PATCH 111/350] Annotate error fragment (#149760) --- .../telemetry/common/errorTelemetry.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/platform/telemetry/common/errorTelemetry.ts b/src/vs/platform/telemetry/common/errorTelemetry.ts index 309b372c83b..8de88fe9142 100644 --- a/src/vs/platform/telemetry/common/errorTelemetry.ts +++ b/src/vs/platform/telemetry/common/errorTelemetry.ts @@ -10,14 +10,16 @@ import { safeStringify } from 'vs/base/common/objects'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type ErrorEventFragment = { - callstack: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; - msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; - file?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; - line?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - column?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - uncaught_error_name?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; - uncaught_error_msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; - count?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; + owner: 'lramos15, sbatten' + comment: 'Whenever an error in VS Code is thrown.' + callstack: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The callstack of the error.' }; + msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The message of the error. Normally the first line int the callstack.' }; + file?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The file the error originated from.'}; + line?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'The line the error originate on.' }; + column?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'The column of the line which the error orginated on.'}; + uncaught_error_name?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'If the error is uncaught what is the error type' }; + uncaught_error_msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'If the error is uncaught this is just msg but for uncaught errors.' }; + count?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'How many times this error has been thrown' }; }; export interface ErrorEvent { callstack: string; From c3d6d7ec7832fa7607422f24e62638d94d50aff2 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 17 May 2022 12:32:01 -0700 Subject: [PATCH 112/350] fixes #149438 (#149761) --- src/vs/workbench/browser/parts/panel/panelPart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 5ffa3a07bbc..453dd8c5f11 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -378,7 +378,7 @@ export abstract class BasePanelPart extends CompositePart impleme contextKey.set(true); this.compositeBar.addComposite({ id: viewContainer.id, name: viewContainer.title, order: viewContainer.order, requestedIndex: viewContainer.requestedIndex }); - if (this.layoutService.isVisible(this.partId)) { + if (this.layoutService.isRestored() && this.layoutService.isVisible(this.partId)) { const activeComposite = this.getActiveComposite(); if (activeComposite === undefined || activeComposite.getId() === viewContainer.id) { this.compositeBar.activateComposite(viewContainer.id); From 95603b0f2ed4a6bf55d33eb9ac35d8350db12f44 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 17 May 2022 16:00:42 -0400 Subject: [PATCH 113/350] Fix hygiene (#149763) --- .../telemetry/common/errorTelemetry.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/telemetry/common/errorTelemetry.ts b/src/vs/platform/telemetry/common/errorTelemetry.ts index 8de88fe9142..acc2dbd0833 100644 --- a/src/vs/platform/telemetry/common/errorTelemetry.ts +++ b/src/vs/platform/telemetry/common/errorTelemetry.ts @@ -10,16 +10,16 @@ import { safeStringify } from 'vs/base/common/objects'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type ErrorEventFragment = { - owner: 'lramos15, sbatten' - comment: 'Whenever an error in VS Code is thrown.' - callstack: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The callstack of the error.' }; - msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The message of the error. Normally the first line int the callstack.' }; - file?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'The file the error originated from.'}; - line?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'The line the error originate on.' }; - column?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'The column of the line which the error orginated on.'}; - uncaught_error_name?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'If the error is uncaught what is the error type' }; - uncaught_error_msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth', comment: 'If the error is uncaught this is just msg but for uncaught errors.' }; - count?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true, comment: 'How many times this error has been thrown' }; + owner: 'lramos15, sbatten'; + comment: 'Whenever an error in VS Code is thrown.'; + callstack: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The callstack of the error.' }; + msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The message of the error. Normally the first line int the callstack.' }; + file?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The file the error originated from.' }; + line?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The line the error originate on.' }; + column?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The column of the line which the error orginated on.' }; + uncaught_error_name?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'If the error is uncaught what is the error type' }; + uncaught_error_msg?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'If the error is uncaught this is just msg but for uncaught errors.' }; + count?: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'How many times this error has been thrown' }; }; export interface ErrorEvent { callstack: string; From ba59882f7175b573ef553ee106437d07a338b908 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 18 May 2022 00:08:27 +0200 Subject: [PATCH 114/350] Reduce usages of `editor.deltaDecorations` (#149768) --- .../editor/browser/widget/codeEditorWidget.ts | 16 +++++-- .../editor/browser/widget/diffEditorWidget.ts | 4 +- src/vs/editor/common/editorCommon.ts | 2 +- .../anchorSelect/browser/anchorSelect.ts | 30 +++++++----- src/vs/editor/contrib/dnd/browser/dnd.ts | 15 +++--- .../gotoSymbol/browser/goToCommands.ts | 4 +- .../inPlaceReplace/browser/inPlaceReplace.ts | 9 ++-- .../contrib/zoneWidget/browser/zoneWidget.ts | 42 +++++++---------- src/vs/monaco.d.ts | 2 +- .../browser/callHierarchyPeek.ts | 4 +- .../browser/outline/documentSymbolsOutline.ts | 4 +- .../comments/browser/commentGlyphWidget.ts | 14 +++--- .../browser/commentsEditorContribution.ts | 6 ++- .../browser/callStackEditorContribution.ts | 6 +-- .../debug/browser/debugEditorContribution.ts | 8 ++-- .../contrib/debug/browser/debugHover.ts | 8 ++-- .../browser/viewModel/baseCellViewModel.ts | 47 +++++++++++-------- .../browser/keybindingsEditorContribution.ts | 6 +-- .../browser/preferencesRenderers.ts | 6 +-- .../preferences/browser/preferencesWidgets.ts | 7 ++- .../browser/typeHierarchyPeek.ts | 4 +- 21 files changed, 127 insertions(+), 117 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 25890eb2332..991afab9ef8 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1262,8 +1262,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.viewModel.executeCommands(commands, source); } - public createDecorationsCollection(): EditorDecorationsCollection { - return new EditorDecorationsCollection(this); + public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): EditorDecorationsCollection { + return new EditorDecorationsCollection(this, decorations); } public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { @@ -2152,8 +2152,13 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl } constructor( - private readonly _editor: editorBrowser.ICodeEditor - ) { } + private readonly _editor: editorBrowser.ICodeEditor, + decorations: IModelDeltaDecoration[] | undefined + ) { + if (Array.isArray(decorations) && decorations.length > 0) { + this.set(decorations); + } + } public onDidChange(listener: (e: IModelDecorationsChangedEvent) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable { return this._editor.onDidChangeModelDecorations((e) => { @@ -2168,6 +2173,9 @@ class EditorDecorationsCollection implements editorCommon.IEditorDecorationsColl if (!this._editor.hasModel()) { return null; } + if (index >= this._decorationIds.length) { + return null; + } return this._editor.getModel().getDecorationRange(this._decorationIds[index]); } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index d2129bc8e38..e9e666a6ce0 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -920,8 +920,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._modifiedEditor.trigger(source, handlerId, payload); } - public createDecorationsCollection(): editorCommon.IEditorDecorationsCollection { - return this._modifiedEditor.createDecorationsCollection(); + public createDecorationsCollection(decorations?: IModelDeltaDecoration[]): editorCommon.IEditorDecorationsCollection { + return this._modifiedEditor.createDecorationsCollection(decorations); } public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 4b8f8d58fbc..9260b986d88 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -465,7 +465,7 @@ export interface IEditor { * will get the ownerId of the editor (meaning they will not show up in other editors). * These decorations will be automatically cleared when the editor's model changes. */ - createDecorationsCollection(): IEditorDecorationsCollection; + createDecorationsCollection(decorations?: IModelDeltaDecoration[]): IEditorDecorationsCollection; /** * Change the decorations. All decorations added through this changeAccessor diff --git a/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts b/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts index ceaefc19bb1..6972a300abb 100644 --- a/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts +++ b/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts @@ -43,17 +43,20 @@ class SelectionAnchorController implements IEditorContribution { setSelectionAnchor(): void { if (this.editor.hasModel()) { const position = this.editor.getPosition(); - const previousDecorations = this.decorationId ? [this.decorationId] : []; - const newDecorationId = this.editor.deltaDecorations(previousDecorations, [{ - range: Selection.fromPositions(position, position), - options: { - description: 'selection-anchor', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - hoverMessage: new MarkdownString().appendText(localize('selectionAnchor', "Selection Anchor")), - className: 'selection-anchor' + this.editor.changeDecorations((accessor) => { + if (this.decorationId) { + accessor.removeDecoration(this.decorationId); } - }]); - this.decorationId = newDecorationId[0]; + this.decorationId = accessor.addDecoration( + Selection.fromPositions(position, position), + { + description: 'selection-anchor', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + hoverMessage: new MarkdownString().appendText(localize('selectionAnchor', "Selection Anchor")), + className: 'selection-anchor' + } + ); + }); this.selectionAnchorSetContextKey.set(!!this.decorationId); alert(localize('anchorSet', "Anchor set at {0}:{1}", position.lineNumber, position.column)); } @@ -81,8 +84,11 @@ class SelectionAnchorController implements IEditorContribution { cancelSelectionAnchor(): void { if (this.decorationId) { - this.editor.deltaDecorations([this.decorationId], []); - this.decorationId = undefined; + const decorationId = this.decorationId; + this.editor.changeDecorations((accessor) => { + accessor.removeDecoration(decorationId); + this.decorationId = undefined; + }); this.selectionAnchorSetContextKey.set(false); } } diff --git a/src/vs/editor/contrib/dnd/browser/dnd.ts b/src/vs/editor/contrib/dnd/browser/dnd.ts index ce2faf6d2eb..cfb074ae7ad 100644 --- a/src/vs/editor/contrib/dnd/browser/dnd.ts +++ b/src/vs/editor/contrib/dnd/browser/dnd.ts @@ -17,8 +17,7 @@ import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; 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 { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IEditorContribution, IEditorDecorationsCollection, ScrollType } from 'vs/editor/common/editorCommon'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { DragAndDropCommand } from 'vs/editor/contrib/dnd/browser/dragAndDropCommand'; @@ -36,7 +35,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu private readonly _editor: ICodeEditor; private _dragSelection: Selection | null; - private _dndDecorationIds: string[]; + private readonly _dndDecorationIds: IEditorDecorationsCollection; private _mouseDown: boolean; private _modifierPressed: boolean; static readonly TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl; @@ -48,6 +47,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu constructor(editor: ICodeEditor) { super(); this._editor = editor; + this._dndDecorationIds = this._editor.createDecorationsCollection(); this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); @@ -57,7 +57,6 @@ export class DragAndDropController extends Disposable implements IEditorContribu this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); this._register(this._editor.onDidBlurEditorText(() => this.onEditorBlur())); - this._dndDecorationIds = []; this._mouseDown = false; this._modifierPressed = false; this._dragSelection = null; @@ -209,17 +208,15 @@ export class DragAndDropController extends Disposable implements IEditorContribu }); public showAt(position: Position): void { - let newDecorations: IModelDeltaDecoration[] = [{ + this._dndDecorationIds.set([{ range: new Range(position.lineNumber, position.column, position.lineNumber, position.column), options: DragAndDropController._DECORATION_OPTIONS - }]; - - this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations); + }]); this._editor.revealPosition(position, ScrollType.Immediate); } private _removeDecoration(): void { - this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, []); + this._dndDecorationIds.clear(); } private _hitContent(target: IMouseTarget): boolean { diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts index 75af03dba4c..cb1b242d7a3 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts @@ -213,10 +213,10 @@ export abstract class SymbolNavigationAction extends EditorAction { if (highlight) { const modelNow = targetEditor.getModel(); - const ids = targetEditor.deltaDecorations([], [{ range, options: { description: 'symbol-navigate-action-highlight', className: 'symbolHighlight' } }]); + const decorations = targetEditor.createDecorationsCollection([{ range, options: { description: 'symbol-navigate-action-highlight', className: 'symbolHighlight' } }]); setTimeout(() => { if (targetEditor.getModel() === modelNow) { - targetEditor.deltaDecorations(ids, []); + decorations.clear(); } }, 350); } diff --git a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts index bdcc799b1ca..2ec112e6a3f 100644 --- a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts @@ -11,7 +11,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; @@ -37,7 +37,7 @@ class InPlaceReplaceController implements IEditorContribution { private readonly editor: ICodeEditor; private readonly editorWorkerService: IEditorWorkerService; - private decorationIds: string[] = []; + private readonly decorations: IEditorDecorationsCollection; private currentRequest?: CancelablePromise; private decorationRemover?: CancelablePromise; @@ -47,6 +47,7 @@ class InPlaceReplaceController implements IEditorContribution { ) { this.editor = editor; this.editorWorkerService = editorWorkerService; + this.decorations = this.editor.createDecorationsCollection(); } public dispose(): void { @@ -114,7 +115,7 @@ class InPlaceReplaceController implements IEditorContribution { this.editor.pushUndoStop(); // add decoration - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, [{ + this.decorations.set([{ range: highlightRange, options: InPlaceReplaceController.DECORATION }]); @@ -124,7 +125,7 @@ class InPlaceReplaceController implements IEditorContribution { this.decorationRemover.cancel(); } this.decorationRemover = timeout(350); - this.decorationRemover.then(() => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, [])).catch(onUnexpectedError); + this.decorationRemover.then(() => this.decorations.clear()).catch(onUnexpectedError); }).catch(onUnexpectedError); } diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index c65d7f7e539..e3fbe97873e 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -14,7 +14,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZo import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IEditorDecorationsCollection, ScrollType } from 'vs/editor/common/editorCommon'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -108,15 +108,13 @@ class Arrow { private static readonly _IdGenerator = new IdGenerator('.arrow-decoration-'); private readonly _ruleName = Arrow._IdGenerator.nextId(); - private _decorations: string[] = []; + private readonly _decorations = this._editor.createDecorationsCollection(); private _color: string | null = null; private _height: number = -1; constructor( private readonly _editor: ICodeEditor - ) { - // - } + ) { } dispose(): void { this.hide(); @@ -152,14 +150,18 @@ class Arrow { where = { lineNumber: where.lineNumber, column: 2 }; } - this._decorations = this._editor.deltaDecorations( - this._decorations, - [{ range: Range.fromPositions(where), options: { description: 'zone-widget-arrow', className: this._ruleName, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }] - ); + this._decorations.set([{ + range: Range.fromPositions(where), + options: { + description: 'zone-widget-arrow', + className: this._ruleName, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }]); } hide(): void { - this._editor.deltaDecorations(this._decorations, []); + this._decorations.clear(); } } @@ -168,7 +170,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { private _arrow: Arrow | null = null; private _overlayWidget: OverlayWidgetDelegate | null = null; private _resizeSash: Sash | null = null; - private _positionMarkerId: string[] = []; + private readonly _positionMarkerId: IEditorDecorationsCollection; protected _viewZone: ViewZoneDelegate | null = null; protected readonly _disposables = new DisposableStore(); @@ -181,6 +183,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { constructor(editor: ICodeEditor, options: IOptions = {}) { this.editor = editor; + this._positionMarkerId = this.editor.createDecorationsCollection(); this.options = objects.deepClone(options); objects.mixin(this.options, defaultOptions, false); this.domNode = document.createElement('div'); @@ -212,8 +215,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { }); } - this.editor.deltaDecorations(this._positionMarkerId, []); - this._positionMarkerId = []; + this._positionMarkerId.clear(); this._disposables.dispose(); } @@ -291,17 +293,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } get position(): Position | undefined { - const [id] = this._positionMarkerId; - if (!id) { - return undefined; - } - - const model = this.editor.getModel(); - if (!model) { - return undefined; - } - - const range = model.getDecorationRange(id); + const range = this._positionMarkerId.getRange(0); if (!range) { return undefined; } @@ -315,7 +307,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this._isShowing = true; this._showImpl(range, heightInLines); this._isShowing = false; - this._positionMarkerId = this.editor.deltaDecorations(this._positionMarkerId, [{ range, options: ModelDecorationOptions.EMPTY }]); + this._positionMarkerId.set([{ range, options: ModelDecorationOptions.EMPTY }]); } hide(): void { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cba1ad5adc5..ee1b68b4155 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2529,7 +2529,7 @@ declare namespace monaco.editor { * will get the ownerId of the editor (meaning they will not show up in other editors). * These decorations will be automatically cleared when the editor's model changes. */ - createDecorationsCollection(): IEditorDecorationsCollection; + createDecorationsCollection(decorations?: IModelDeltaDecoration[]): IEditorDecorationsCollection; } /** diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index f3ea2110649..f1e945e1bef 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -334,8 +334,8 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { } if (fullRange) { this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); - const ids = this._editor.deltaDecorations([], decorations); - this._previewDisposable.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); + const decorationsCollection = this._editor.createDecorationsCollection(decorations); + this._previewDisposable.add(toDisposable(() => decorationsCollection.clear())); } this._previewDisposable.add(value); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 8a23eaaeb0a..e0fffb28ca8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -239,7 +239,7 @@ class DocumentSymbolsOutline implements IOutline { const { symbol } = entry; this._editor.revealRangeInCenterIfOutsideViewport(symbol.range, ScrollType.Smooth); - const ids = this._editor.deltaDecorations([], [{ + const decorationsCollection = this._editor.createDecorationsCollection([{ range: symbol.range, options: { description: 'document-symbols-outline-range-highlight', @@ -247,7 +247,7 @@ class DocumentSymbolsOutline implements IOutline { isWholeLine: true } }]); - return toDisposable(() => this._editor.deltaDecorations(ids, [])); + return toDisposable(() => decorationsCollection.clear()); } captureViewState(): IDisposable { diff --git a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts index 7be06df3a41..563ce8a4c9e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts @@ -10,6 +10,7 @@ import { IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/mod import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; const overviewRulerDefault = new Color(new RGBA(197, 197, 197, 1)); @@ -19,12 +20,13 @@ export class CommentGlyphWidget { public static description = 'comment-glyph-widget'; private _lineNumber!: number; private _editor: ICodeEditor; - private commentsDecorations: string[] = []; + private readonly _commentsDecorations: IEditorDecorationsCollection; private _commentsOptions: ModelDecorationOptions; constructor(editor: ICodeEditor, lineNumber: number) { this._commentsOptions = this.createDecorationOptions(); this._editor = editor; + this._commentsDecorations = this._editor.createDecorationsCollection(); this.setLineNumber(lineNumber); } @@ -52,13 +54,11 @@ export class CommentGlyphWidget { options: this._commentsOptions }]; - this.commentsDecorations = this._editor.deltaDecorations(this.commentsDecorations, commentsDecorations); + this._commentsDecorations.set(commentsDecorations); } getPosition(): IContentWidgetPosition { - const range = this._editor.hasModel() && this.commentsDecorations && this.commentsDecorations.length - ? this._editor.getModel().getDecorationRange(this.commentsDecorations[0]) - : null; + const range = (this._commentsDecorations.length > 0 ? this._commentsDecorations.getRange(0) : null); return { position: { @@ -70,8 +70,6 @@ export class CommentGlyphWidget { } dispose() { - if (this.commentsDecorations) { - this._editor.deltaDecorations(this.commentsDecorations, []); - } + this._commentsDecorations.clear(); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index fa9db7ee660..2d6e03e1c60 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -241,8 +241,10 @@ class CommentingRangeDecorator { }); } - this.decorationIds = editor.deltaDecorations(this.decorationIds, commentingRangeDecorations); - commentingRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + editor.changeDecorations((accessor) => { + this.decorationIds = accessor.deltaDecorations(this.decorationIds, commentingRangeDecorations); + commentingRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + }); const rangesDifference = this.commentingRangeDecorations.length - commentingRangeDecorations.length; this.commentingRangeDecorations = commentingRangeDecorations; diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index b858ddb0659..b09f8c1157c 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -109,7 +109,7 @@ export function createDecorationsForStackFrame(stackFrame: IStackFrame, isFocuse export class CallStackEditorContribution implements IEditorContribution { private toDispose: IDisposable[] = []; - private decorationIds: string[] = []; + private decorations = this.editor.createDecorationsCollection(); constructor( private readonly editor: ICodeEditor, @@ -117,7 +117,7 @@ export class CallStackEditorContribution implements IEditorContribution { @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, ) { - const setDecorations = () => this.decorationIds = this.editor.deltaDecorations(this.decorationIds, this.createCallStackDecorations()); + const setDecorations = () => this.decorations.set(this.createCallStackDecorations()); this.toDispose.push(Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getModel().onDidChangeCallStack)(() => { setDecorations(); })); @@ -170,7 +170,7 @@ export class CallStackEditorContribution implements IEditorContribution { } dispose(): void { - this.editor.deltaDecorations(this.decorationIds, []); + this.decorations.clear(); this.toDispose = dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 848242efad6..98bcc2d1d6f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -221,7 +221,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private configurationWidget: FloatingClickWidget | undefined; private altListener: IDisposable | undefined; private altPressed = false; - private oldDecorations: string[] = []; + private oldDecorations = this.editor.createDecorationsCollection(); private readonly debounceInfo: IFeatureDebounceInformation; constructor( @@ -605,7 +605,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private get removeInlineValuesScheduler(): RunOnceScheduler { return new RunOnceScheduler( () => { - this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, []); + this.oldDecorations.clear(); }, 100 ); @@ -759,7 +759,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { decoration => `${decoration.range.startLineNumber}:${decoration?.options.after?.content}`); } - this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, allDecorations); + this.oldDecorations.set(allDecorations); } dispose(): void { @@ -771,6 +771,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { } this.toDispose = dispose(this.toDispose); - this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, []); + this.oldDecorations.clear(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 66626216c50..21d65a90cb3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -77,7 +77,7 @@ export class DebugHoverWidget implements IContentWidget { private tree!: AsyncDataTree; private showAtPosition: Position | null; private positionPreference: ContentWidgetPositionPreference[]; - private highlightDecorations: string[]; + private readonly highlightDecorations = this.editor.createDecorationsCollection(); private complexValueContainer!: HTMLElement; private complexValueTitle!: HTMLElement; private valueContainer!: HTMLElement; @@ -96,7 +96,6 @@ export class DebugHoverWidget implements IContentWidget { this._isVisible = false; this.showAtPosition = null; - this.highlightDecorations = []; this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; } @@ -264,7 +263,7 @@ export class DebugHoverWidget implements IContentWidget { } if (rng) { - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ + this.highlightDecorations.set([{ range: rng, options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS }]); @@ -351,8 +350,7 @@ export class DebugHoverWidget implements IContentWidget { this.editor.focus(); } this._isVisible = false; - this.editor.deltaDecorations(this.highlightDecorations, []); - this.highlightDecorations = []; + this.highlightDecorations.clear(); this.editor.layoutContentWidget(this); this.positionPreference = [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 11b51e17e2f..d602602277f 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -241,16 +241,18 @@ export abstract class BaseCellViewModel extends Disposable { writeTransientState(editor.getModel(), this._editorTransientState, this._codeEditorService); } - this._resolvedDecorations.forEach((value, key) => { - if (key.startsWith('_lazy_')) { - // lazy ones - const ret = this._textEditor!.deltaDecorations([], [value.options]); - this._resolvedDecorations.get(key)!.id = ret[0]; - } - else { - const ret = this._textEditor!.deltaDecorations([], [value.options]); - this._resolvedDecorations.get(key)!.id = ret[0]; - } + this._textEditor.changeDecorations((accessor) => { + this._resolvedDecorations.forEach((value, key) => { + if (key.startsWith('_lazy_')) { + // lazy ones + const ret = accessor.addDecoration(value.options.range, value.options.options); + this._resolvedDecorations.get(key)!.id = ret; + } + else { + const ret = accessor.addDecoration(value.options.range, value.options.options); + this._resolvedDecorations.get(key)!.id = ret; + } + }); }); this._editorListeners.push(this._textEditor.onDidChangeCursorSelection(() => { this._onDidChangeState.fire({ selectionChanged: true }); })); @@ -263,12 +265,14 @@ export abstract class BaseCellViewModel extends Disposable { this.saveViewState(); this.saveTransientState(); // decorations need to be cleared first as editors can be resued. - this._resolvedDecorations.forEach(value => { - const resolvedid = value.id; + this._textEditor?.changeDecorations((accessor) => { + this._resolvedDecorations.forEach(value => { + const resolvedid = value.id; - if (resolvedid) { - this._textEditor?.deltaDecorations([resolvedid], []); - } + if (resolvedid) { + accessor.removeDecoration(resolvedid); + } + }); }); this._textEditor = undefined; @@ -332,16 +336,21 @@ export abstract class BaseCellViewModel extends Disposable { return decorationId; } - const result = this._textEditor.deltaDecorations([], [decoration]); - this._resolvedDecorations.set(result[0], { id: result[0], options: decoration }); - return result[0]; + let id: string; + this._textEditor.changeDecorations((accessor) => { + id = accessor.addDecoration(decoration.range, decoration.options); + this._resolvedDecorations.set(id, { id, options: decoration }); + }); + return id!; } removeModelDecoration(decorationId: string) { const realDecorationId = this._resolvedDecorations.get(decorationId); if (this._textEditor && realDecorationId && realDecorationId.id !== undefined) { - this._textEditor.deltaDecorations([realDecorationId.id!], []); + this._textEditor.changeDecorations((accessor) => { + accessor.removeDecoration(realDecorationId.id!); + }); } // lastly, remove all the cache diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index 814f9e1282a..e69f1504400 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -163,7 +163,7 @@ export class KeybindingWidgetRenderer extends Disposable { export class KeybindingEditorDecorationsRenderer extends Disposable { private _updateDecorations: RunOnceScheduler; - private _dec: string[] = []; + private readonly _dec = this._editor.createDecorationsCollection(); constructor( private _editor: ICodeEditor, @@ -178,7 +178,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { this._register(this._keybindingService.onDidUpdateKeybindings((e) => this._updateDecorations.schedule())); this._register({ dispose: () => { - this._dec = this._editor.deltaDecorations(this._dec, []); + this._dec.clear(); this._updateDecorations.cancel(); } }); @@ -201,7 +201,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { } } - this._dec = this._editor.deltaDecorations(this._dec, newDecorations); + this._dec.set(newDecorations); } private _getDecorationForEntry(model: ITextModel, entry: Node): IModelDeltaDecoration | null { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 606a9255884..3bb3a346bd3 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -668,7 +668,7 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc class WorkspaceConfigurationRenderer extends Disposable { private static readonly supportedKeys = ['folders', 'tasks', 'launch', 'extensions', 'settings', 'remoteAuthority', 'transient']; - private decorationIds: string[] = []; + private readonly decorations = this.editor.createDecorationsCollection(); private renderingDelayer: Delayer = new Delayer(200); constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel, @@ -697,7 +697,7 @@ class WorkspaceConfigurationRenderer extends Disposable { } } } - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range))); + this.decorations.set(ranges.map(range => this.createDecoration(range))); } if (markerData.length) { this.markerService.changeOne('WorkspaceConfigurationRenderer', this.workspaceSettingsEditorModel.uri, markerData); @@ -721,7 +721,7 @@ class WorkspaceConfigurationRenderer extends Disposable { override dispose(): void { this.markerService.remove('WorkspaceConfigurationRenderer', [this.workspaceSettingsEditorModel.uri]); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); + this.decorations.clear(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 4c942330f52..f74ec72414c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -518,14 +518,13 @@ export class EditPreferenceWidget extends Disposable { private _line: number = -1; private _preferences: T[] = []; - private _editPreferenceDecoration: string[]; + private readonly _editPreferenceDecoration = this.editor.createDecorationsCollection(); private readonly _onClick = this._register(new Emitter()); readonly onClick: Event = this._onClick.event; constructor(private editor: ICodeEditor) { super(); - this._editPreferenceDecoration = []; this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => { if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.detail.isAfterLines || !this.isVisible()) { return; @@ -560,11 +559,11 @@ export class EditPreferenceWidget extends Disposable { endColumn: 1 } }); - this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, newDecoration); + this._editPreferenceDecoration.set(newDecoration); } hide(): void { - this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, []); + this._editPreferenceDecoration.clear(); } isVisible(): boolean { diff --git a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts index dca7e901bce..d0956de5457 100644 --- a/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts +++ b/src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchyPeek.ts @@ -329,8 +329,8 @@ export class TypeHierarchyTreePeekWidget extends peekView.PeekViewWidget { } if (fullRange) { this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate); - const ids = this._editor.deltaDecorations([], decorations); - this._previewDisposable.add(toDisposable(() => this._editor.deltaDecorations(ids, []))); + const decorationsCollection = this._editor.createDecorationsCollection(decorations); + this._previewDisposable.add(toDisposable(() => decorationsCollection.clear())); } this._previewDisposable.add(value); From 612af7602ac6c33882f3e3639cd4c86b788db287 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 17 May 2022 15:15:10 -0700 Subject: [PATCH 115/350] Sort used debug extensions to the top when clicking "Run and Debug". Towards #146338 (#149773) --- .../debug/browser/debugAdapterManager.ts | 29 ++++++++++++++----- .../browser/debugConfigurationManager.ts | 2 +- .../contrib/debug/browser/debugService.ts | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 7f9e2de3788..7a9ec3bbb3d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -34,6 +34,10 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); +export interface IAdapterManagerDelegate { + onDidNewSession: Event; +} + export class AdapterManager extends Disposable implements IAdapterManager { private debuggers: Debugger[]; @@ -49,7 +53,10 @@ export class AdapterManager extends Disposable implements IAdapterManager { /** Extensions that were already active before any debugger activation events */ private earlyActivatedExtensions: Set | undefined; + private usedDebugTypes = new Set(); + constructor( + delegate: IAdapterManagerDelegate, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -59,7 +66,7 @@ export class AdapterManager extends Disposable implements IAdapterManager { @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILanguageService private readonly languageService: ILanguageService, @IDialogService private readonly dialogService: IDialogService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(); this.adapterDescriptorFactories = []; @@ -79,6 +86,10 @@ export class AdapterManager extends Disposable implements IAdapterManager { })); this.lifecycleService.when(LifecyclePhase.Eventually) .then(() => this.debugExtensionsAvailable.set(this.debuggers.length > 0)); // If no extensions with a debugger contribution are loaded + + this._register(delegate.onDidNewSession(s => { + this.usedDebugTypes.add(s.configuration.type); + })); } private registerListeners(): void { @@ -287,18 +298,18 @@ export class AdapterManager extends Disposable implements IAdapterManager { return this.debuggers.find(dbg => strings.equalsIgnoreCase(dbg.type, type)); } + getEnabledDebugger(type: string): Debugger | undefined { + const adapter = this.getDebugger(type); + return adapter && adapter.enabled ? adapter : undefined; + } + isDebuggerInterestedInLanguage(language: string): boolean { return !!this.debuggers .filter(d => d.enabled) .find(a => language && a.languages && a.languages.indexOf(language) >= 0); } - async guessDebugger(gettingConfigurations: boolean, type?: string): Promise { - if (type) { - const adapter = this.getDebugger(type); - return adapter && adapter.enabled ? adapter : undefined; - } - + async guessDebugger(gettingConfigurations: boolean): Promise { const activeTextEditorControl = this.editorService.activeTextEditorControl; let candidates: Debugger[] = []; let languageLabel: string | null = null; @@ -351,6 +362,10 @@ export class AdapterManager extends Disposable implements IAdapterManager { candidates.forEach(d => { const descriptor = d.getMainExtensionDescriptor(); if (descriptor.id && !!this.earlyActivatedExtensions?.has(descriptor.id)) { + // Was activated early + suggestedCandidates.push(d); + } else if (this.usedDebugTypes.has(d.type)) { + // Was used already suggestedCandidates.push(d); } else { otherCandidates.push(d); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index e8ed18ca485..0245dbd5a2d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -523,7 +523,7 @@ abstract class AbstractLaunch { async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { let content = ''; - const adapter = await this.adapterManager.guessDebugger(true, type); + const adapter = type ? this.adapterManager.getEnabledDebugger(type) : await this.adapterManager.guessDebugger(true); if (adapter) { const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None); content = await adapter.getInitialConfigurationContent(initialConfigs); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 224b72fe4fa..50c7f78b6b2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -114,7 +114,7 @@ export class DebugService implements IDebugService { this._onWillNewSession = new Emitter(); this._onDidEndSession = new Emitter(); - this.adapterManager = this.instantiationService.createInstance(AdapterManager); + this.adapterManager = this.instantiationService.createInstance(AdapterManager, { onDidNewSession: this.onDidNewSession }); this.disposables.add(this.adapterManager); this.configurationManager = this.instantiationService.createInstance(ConfigurationManager, this.adapterManager); this.disposables.add(this.configurationManager); From cf189907bbfa66a99bc1dd5b58f346b12b867449 Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Wed, 18 May 2022 01:32:39 +0200 Subject: [PATCH 116/350] Workaround for the webview positioning bug (#137506) * Basic fix for the webview positioning bug * Reduce to 25fps for less load * Fixing typo in comment * Removing interval to make the change look animated * Running formatter for hygene * Fixing typo Co-authored-by: Anthony Stewart Co-authored-by: Matt Bierner --- .../contrib/webview/browser/overlayWebview.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index d9239b64135..057e5e10d4d 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -29,6 +29,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _html: string = ''; private _initialScrollProgress: number = 0; private _state: string | undefined = undefined; + private _repositionTimeout: any | undefined = undefined; private _extension: WebviewExtensionDescription | undefined; private _contentOptions: WebviewContentOptions; @@ -76,6 +77,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } this._firstLoadPendingMessages.clear(); + clearTimeout(this._repositionTimeout); + this._onDidDispose.fire(); super.dispose(); @@ -144,6 +147,17 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } public layoutWebviewOverElement(element: HTMLElement, dimension?: Dimension, clippingContainer?: HTMLElement) { + this.doLayoutWebviewOverElement(element, dimension, clippingContainer); + + // Temporary fix for https://github.com/microsoft/vscode/issues/110450 + // There is an animation that lasts about 200ms, update the webview positioning once this animation is complete. + clearTimeout(this._repositionTimeout); + this._repositionTimeout = setTimeout(() => { + this.doLayoutWebviewOverElement(element, dimension, clippingContainer); + }, 200); + } + + public doLayoutWebviewOverElement(element: HTMLElement, dimension?: Dimension, clippingContainer?: HTMLElement) { if (!this._container || !this._container.parentElement) { return; } From c9c5990e389c9480695cdb9c29683f6501c61122 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 May 2022 18:51:25 -0700 Subject: [PATCH 117/350] Adopt notebookWorkspaceEdit proposal internally (#149778) This switches us to use the new `notebookWorkspaceEdit` proposal instead of `notebookEditorEdit` --- extensions/ipynb/package.json | 3 +-- extensions/ipynb/src/cellIdService.ts | 4 ++-- extensions/ipynb/src/ipynbMain.ts | 4 ++-- extensions/ipynb/tsconfig.json | 2 +- extensions/vscode-notebook-tests/package.json | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index c8feabc17af..32802630b84 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -9,8 +9,7 @@ "vscode": "^1.57.0" }, "enabledApiProposals": [ - "notebookEditor", - "notebookEditorEdit" + "notebookEditor" ], "activationEvents": [ "*" diff --git a/extensions/ipynb/src/cellIdService.ts b/extensions/ipynb/src/cellIdService.ts index 082d25add97..ddda0a9fd5f 100644 --- a/extensions/ipynb/src/cellIdService.ts +++ b/extensions/ipynb/src/cellIdService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, workspace, WorkspaceEdit } from 'vscode'; +import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, NotebookEdit, workspace, WorkspaceEdit } from 'vscode'; import { v4 as uuid } from 'uuid'; import { getCellMetadata } from './serializers'; import { CellMetadata } from './common'; @@ -34,7 +34,7 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) { // Don't edit the metadata directly, always get a clone (prevents accidental singletons and directly editing the objects). const updatedMetadata: CellMetadata = { ...JSON.parse(JSON.stringify(cellMetadata || {})) }; updatedMetadata.id = id; - edit.replaceNotebookCellMetadata(cell.notebook.uri, cell.index, { ...(cell.metadata), custom: updatedMetadata }); + edit.set(cell.notebook.uri, [NotebookEdit.updateCellMetadata(cell.index, { ...(cell.metadata), custom: updatedMetadata })]); workspace.applyEdit(edit); }); }); diff --git a/extensions/ipynb/src/ipynbMain.ts b/extensions/ipynb/src/ipynbMain.ts index c2c7044bd77..33bb1456af8 100644 --- a/extensions/ipynb/src/ipynbMain.ts +++ b/extensions/ipynb/src/ipynbMain.ts @@ -94,7 +94,7 @@ export function activate(context: vscode.ExtensionContext) { } const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookMetadata(resource, { + edit.set(resource, [vscode.NotebookEdit.updateNotebookMetadata({ ...document.metadata, custom: { ...(document.metadata.custom ?? {}), @@ -103,7 +103,7 @@ export function activate(context: vscode.ExtensionContext) { ...metadata }, } - }); + })]); return vscode.workspace.applyEdit(edit); }, }; diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index 178e86493b4..918db6ea6c2 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -10,6 +10,6 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts" + "../../src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts" ] } diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 8792cd4e6fb..8bb9eb8d3b3 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -17,7 +17,6 @@ "notebookDeprecated", "notebookEditor", "notebookEditorDecorationType", - "notebookEditorEdit", "notebookLiveShare", "notebookMessaging", "notebookMime" From 6fef251f8f0e9f74b7adb4ed310e35eabb97afdf Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 17 May 2022 19:03:46 -0700 Subject: [PATCH 118/350] Show the debug adapter label when hovering the type in launch.json (#149786) --- src/vs/workbench/contrib/debug/common/debugger.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 8a41ba6c603..531f7f789cc 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -225,6 +225,7 @@ export class Debugger implements IDebugger { const properties = attributes.properties; properties['type'] = { enum: [this.type], + enumDescriptions: [this.label], description: nls.localize('debugType', "Type of configuration."), pattern: '^(?!node2)', deprecationMessage: this.enabled ? undefined : debuggerDisabledMessage(this.type), From 688c80245936b49b7ceca494d9edfe97cae06f8a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 17 May 2022 20:50:42 -0700 Subject: [PATCH 119/350] Fix lazy button layout in debug hover. (#149789) Fix #149638 --- .../browser/media/debug.contribution.css | 23 +++++++++++-------- .../debug/browser/media/debugViewlet.css | 4 ---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 577a80c532e..c061b736c55 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -70,6 +70,12 @@ /* Expressions */ + +.monaco-workbench .debug-pane .monaco-list-row .expression, +.monaco-workbench .debug-hover-widget .monaco-list-row .expression { + display: flex; +} + .monaco-workbench .debug-pane .monaco-list-row .expression, .monaco-workbench .debug-hover-widget .monaco-list-row .expression { font-size: 13px; @@ -90,6 +96,13 @@ .monaco-workbench .monaco-list-row .expression .lazy-button { margin-left: 3px; + display: none; + border-radius: 5px; + align-self: center; +} + +.monaco-workbench .monaco-list-row .expression.lazy .lazy-button { + display: inline; } /* Links */ @@ -120,16 +133,6 @@ font-style: italic; } -.monaco-workbench .monaco-list-row .expression .lazy-button { - display: none; - border-radius: 5px; - padding: 3px; -} - -.monaco-workbench .monaco-list-row .expression.lazy .lazy-button { - display: inline; -} - .monaco-workbench .debug-inline-value { background-color: var(--vscode-editor-inlineValuesBackground); color: var(--vscode-editor-inlineValuesForeground); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 493c9270e85..57a946206a8 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -216,10 +216,6 @@ font-size: 11px; } -.debug-pane .monaco-list-row .expression { - display: flex; -} - .debug-pane .monaco-list-row .expression .actionbar-spacer { flex-grow: 1; } From 95b8607540917934d215aa6e2dc8e74cf4db8ad3 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 17 May 2022 21:46:44 -0700 Subject: [PATCH 120/350] load source commands --- src/vs/platform/actions/common/actions.ts | 1 + .../editorStatusBar/editorStatusBar.ts | 181 ++++++++++-------- .../browser/notebookKernelServiceImpl.ts | 50 +++++ .../viewParts/notebookKernelActionViewItem.ts | 21 +- .../notebook/common/notebookKernelService.ts | 6 + .../actions/common/menusExtensionPoint.ts | 5 + 6 files changed, 185 insertions(+), 79 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e97c241b95c..93c7c897b13 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -140,6 +140,7 @@ export class MenuId { static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle'); static readonly NotebookOutputToolbar = new MenuId('NotebookOutputToolbar'); static readonly NotebookEditorLayoutConfigure = new MenuId('NotebookEditorLayoutConfigure'); + static readonly NotebookKernelSource = new MenuId('NotebookKernelSource'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); static readonly TimelineItemContext = new MenuId('TimelineItemContext'); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 22233100d5e..a62de5e8e65 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; import { groupBy } from 'vs/base/common/arrays'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -157,86 +158,114 @@ registerAction2(class extends Action2 { } } - if (!newKernel) { - type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; - const configButton: IQuickInputButton = { - iconClass: ThemeIcon.asClassName(configureKernelIcon), - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default for '{0}' notebooks", editor.textModel.viewType) - }; - function toQuickPick(kernel: INotebookKernel) { - const res = { - kernel, - picked: kernel.id === selected?.id, - label: kernel.label, - description: kernel.description, - detail: kernel.detail, - buttons: [configButton] - }; - if (kernel.id === selected?.id) { - if (!res.description) { - res.description = nls.localize('current1', "Currently Selected"); - } else { - res.description = nls.localize('current2', "{0} - Currently Selected", res.description); - } - } - return res; - } - const quickPickItems: QuickPickInput[] = []; - if (all.length) { - // Always display suggested kernels on the top. - if (suggestions.length) { - quickPickItems.push({ - type: 'separator', - label: nls.localize('suggestedKernels', "Suggested") - }); - quickPickItems.push(...suggestions.map(toQuickPick)); - } - - // Next display all of the kernels grouped by categories or extensions. - // If we don't have a kind, always display those at the bottom. - const picks = all.filter(item => !suggestions.includes(item)).map(toQuickPick); - const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); - kernelsPerCategory.forEach(items => { - quickPickItems.push({ - type: 'separator', - label: items[0].kernel.kind || nls.localize('otherKernelKinds', "Other") - }); - quickPickItems.push(...items); - }); - } - - if (!all.length) { - // there is no kernel, show the install from marketplace - quickPickItems.push({ - id: 'install', - label: nls.localize('installKernels', "Install kernels from the marketplace"), - }); - } - - const pick = await quickInputService.pick(quickPickItems, { - placeHolder: selected - ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) - : nls.localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })), - onDidTriggerItemButton: (context) => { - if ('kernel' in context.item) { - notebookKernelService.selectKernelForNotebookType(context.item.kernel, notebook.viewType); - } - } - }); - - if (pick) { - if (pick.id === 'install') { - await this._showKernelExtension(paneCompositeService, notebook.viewType); - } else if ('kernel' in pick) { - newKernel = pick.kernel; - } - } - } - if (newKernel) { notebookKernelService.selectKernelForNotebook(newKernel, notebook); return true; } + + type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; + type SourcePick = IQuickPickItem & { action: IAction }; + + const configButton: IQuickInputButton = { + iconClass: ThemeIcon.asClassName(configureKernelIcon), + tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default for '{0}' notebooks", editor.textModel.viewType) + }; + function toQuickPick(kernel: INotebookKernel) { + const res = { + kernel, + picked: kernel.id === selected?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail, + buttons: [configButton] + }; + if (kernel.id === selected?.id) { + if (!res.description) { + res.description = nls.localize('current1', "Currently Selected"); + } else { + res.description = nls.localize('current2', "{0} - Currently Selected", res.description); + } + } + return res; + } + const quickPickItems: QuickPickInput[] = []; + if (all.length) { + // Always display suggested kernels on the top. + if (suggestions.length) { + quickPickItems.push({ + type: 'separator', + label: nls.localize('suggestedKernels', "Suggested") + }); + quickPickItems.push(...suggestions.map(toQuickPick)); + } + + // Next display all of the kernels grouped by categories or extensions. + // If we don't have a kind, always display those at the bottom. + const picks = all.filter(item => !suggestions.includes(item)).map(toQuickPick); + const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); + kernelsPerCategory.forEach(items => { + quickPickItems.push({ + type: 'separator', + label: items[0].kernel.kind || nls.localize('otherKernelKinds', "Other") + }); + quickPickItems.push(...items); + }); + } + + if (!all.length) { + // there is no kernel, show the install from marketplace + quickPickItems.push({ + id: 'install', + label: nls.localize('installKernels', "Install kernels from the marketplace"), + }); + } + + const sourceActions = notebookKernelService.getSourceActions(); + if (sourceActions.length) { + quickPickItems.push({ + type: 'separator', + // label: nls.localize('sourceActions', "") + }); + + sourceActions.forEach(action => { + const res = { + action, + picked: false, + label: action.label, + }; + + quickPickItems.push(res); + }); + } + + const pick = await quickInputService.pick(quickPickItems, { + placeHolder: selected + ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) + : nls.localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })), + onDidTriggerItemButton: (context) => { + if ('kernel' in context.item) { + notebookKernelService.selectKernelForNotebookType(context.item.kernel, notebook.viewType); + } + } + }); + + if (pick) { + if ('kernel' in pick) { + newKernel = pick.kernel; + notebookKernelService.selectKernelForNotebook(newKernel, notebook); + return true; + } + + // actions + + if (pick.id === 'install') { + await this._showKernelExtension(paneCompositeService, notebook.viewType); + } else if ('action' in pick) { + // selected explicilty, it should trigger the execution? + notebookKernelService.runSourceAction(pick.action); + } + } + return false; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 3a14f27d0ff..6e0151f222c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -12,6 +12,9 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { URI } from 'vs/base/common/uri'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { runWhenIdle } from 'vs/base/common/async'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IAction } from 'vs/base/common/actions'; class KernelInfo { @@ -56,18 +59,25 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private readonly _onDidAddKernel = this._register(new Emitter()); private readonly _onDidRemoveKernel = this._register(new Emitter()); private readonly _onDidChangeNotebookAffinity = this._register(new Emitter()); + private readonly _onDidChangeSourceActions = this._register(new Emitter()); + private readonly _sourceMenu: IMenu; + private _sourceActions: IAction[]; readonly onDidChangeSelectedNotebooks: Event = this._onDidChangeNotebookKernelBinding.event; readonly onDidAddKernel: Event = this._onDidAddKernel.event; readonly onDidRemoveKernel: Event = this._onDidRemoveKernel.event; readonly onDidChangeNotebookAffinity: Event = this._onDidChangeNotebookAffinity.event; + readonly onDidChangeSourceActions: Event = this._onDidChangeSourceActions.event; private static _storageNotebookBinding = 'notebook.controller2NotebookBindings'; private static _storageTypeBinding = 'notebook.controller2TypeBindings'; + constructor( @INotebookService private readonly _notebookService: INotebookService, @IStorageService private readonly _storageService: IStorageService, + @IMenuService readonly _menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); @@ -80,6 +90,10 @@ export class NotebookKernelService extends Disposable implements INotebookKernel this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel: kernelId, newKernel: undefined }); } })); + this._sourceMenu = this._register(this._menuService.createMenu(MenuId.NotebookKernelSource, contextKeyService)); + this._sourceActions = []; + + this._initSourceActions(); // restore from storage try { @@ -96,6 +110,24 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } } + private _initSourceActions() { + const loadActions = (menu: IMenu) => { + const groups = menu.getActions({ shouldForwardArgs: true }); + const actions: IAction[] = []; + groups.forEach(group => { + actions.push(...group[1]); + }); + this._sourceActions = actions; + this._onDidChangeSourceActions.fire(); + }; + + this._register(this._sourceMenu.onDidChange(() => { + loadActions(this._sourceMenu); + })); + + loadActions(this._sourceMenu); + } + override dispose() { this._kernels.clear(); super.dispose(); @@ -255,4 +287,22 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } this._onDidChangeNotebookAffinity.fire(); } + + private _runningAction: IAction | undefined = undefined; + + getRunningSourceAction() { + return this._runningAction; + } + + getSourceActions(): IAction[] { + return this._sourceActions; + } + + async runSourceAction(action: IAction): Promise { + this._runningAction = action; + this._onDidChangeSourceActions.fire(); + await action.run(); + this._runningAction = undefined; + this._onDidChangeSourceActions.fire(); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 69917ca972b..4f23f001c3a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -9,7 +9,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -33,6 +33,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { this._register(_editor.onDidChangeModel(this._update, this)); this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this)); this._register(_notebookKernelService.onDidChangeSelectedNotebooks(this._update, this)); + this._register(_notebookKernelService.onDidChangeSourceActions(this._update, this)); this._kernelDisposable = this._register(new DisposableStore()); } @@ -61,8 +62,22 @@ export class NotebooKernelActionViewItem extends ActionViewItem { return; } - const info = this._notebookKernelService.getMatchingKernel(notebook); - this._updateActionFromKernelInfo(info); + const runningAction = this._notebookKernelService.getRunningSourceAction(); + if (runningAction) { + this._updateActionFromSourceAction(runningAction); + return; + } else { + this.action.class = ThemeIcon.asClassName(selectKernelIcon); + const info = this._notebookKernelService.getMatchingKernel(notebook); + this._updateActionFromKernelInfo(info); + } + } + + private _updateActionFromSourceAction(sourceAction: IAction) { + this.action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')); + this.updateClass(); + this._action.label = sourceAction.label; + this._action.enabled = true; } private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 13fd479ef1a..e47d930372f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -82,6 +83,7 @@ export interface INotebookKernelService { readonly onDidRemoveKernel: Event; readonly onDidChangeSelectedNotebooks: Event; readonly onDidChangeNotebookAffinity: Event; + readonly onDidChangeSourceActions: Event; registerKernel(kernel: INotebookKernel): IDisposable; @@ -115,4 +117,8 @@ export interface INotebookKernelService { */ updateKernelNotebookAffinity(kernel: INotebookKernel, notebook: URI, preference: number | undefined): void; + getSourceActions(): IAction[]; + getRunningSourceAction(): IAction | undefined; + + runSourceAction(action: IAction): Promise; } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 7e06c120f95..409cfb9924c 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -171,6 +171,11 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.NotebookToolbar, description: localize('notebook.toolbar', "The contributed notebook toolbar menu") }, + { + key: 'notebook/kernelSource', + id: MenuId.NotebookKernelSource, + description: localize('notebook.kernelSource', "The contributed notebook kernel sources menu") + }, { key: 'notebook/cell/title', id: MenuId.NotebookCellTitle, From d1f882f635fdd87f2af500dfdfed91f744af680b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 18 May 2022 10:37:44 +0200 Subject: [PATCH 121/350] Engineering - Exploration to run tests in parallel (#149698) --- .../darwin/product-build-darwin-test.yml | 194 +++++++++------ .../linux/product-build-linux-client.yml | 220 ++++++++++------- build/azure-pipelines/product-build-pr.yml | 94 +++++++- .../win32/product-build-win32.yml | 224 +++++++++++------- scripts/test-integration.bat | 26 +- scripts/test-integration.sh | 26 +- scripts/test-remote-integration.bat | 8 +- scripts/test-remote-integration.sh | 20 +- scripts/test-web-integration.bat | 12 +- scripts/test-web-integration.sh | 14 +- 10 files changed, 530 insertions(+), 308 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 773b5a40845..dd495426b6d 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -1,6 +1,15 @@ parameters: - name: VSCODE_QUALITY type: string + - name: VSCODE_RUN_UNIT_TESTS + type: boolean + default: true + - name: VSCODE_RUN_INTEGRATION_TESTS + type: boolean + default: true + - name: VSCODE_RUN_SMOKE_TESTS + type: boolean + default: true steps: - task: NodeTool@0 @@ -165,91 +174,126 @@ steps: VSCODE_ARCH=$(VSCODE_ARCH) DEBUG=electron-osx-sign* node build/darwin/sign.js displayName: Set Hardened Entitlements - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 - - script: | - set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + yarn gulp \ + compile-extension:css-language-features-server \ + compile-extension:emmet \ + compile-extension:git \ + compile-extension:github-authentication \ + compile-extension:html-language-features-server \ + compile-extension:ipynb \ + compile-extension:json-language-features-server \ + compile-extension:markdown-language-features \ + compile-extension-media \ + compile-extension:microsoft-authentication \ + compile-extension:typescript-language-features \ + compile-extension:vscode-api-tests \ + compile-extension:vscode-colorize-tests \ + compile-extension:vscode-custom-editor-tests \ + compile-extension:vscode-notebook-tests \ + compile-extension:vscode-test-resolver + displayName: Build integration tests + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser webkit - displayName: Run integration tests (Browser, Webkit) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser webkit + displayName: Run integration tests (Browser, Webkit) + timeoutInMinutes: 20 - - script: | - set -e - ps -ef - displayName: Diagnostics before smoke test run - continueOnError: true - condition: succeededOrFailed() + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + ps -ef + displayName: Diagnostics before smoke test run + continueOnError: true + condition: succeededOrFailed() - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) - - script: | - set -e - ps -ef - displayName: Diagnostics after smoke test run - continueOnError: true - condition: succeededOrFailed() + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) + + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + ps -ef + displayName: Diagnostics after smoke test run + continueOnError: true + condition: succeededOrFailed() - task: PublishPipelineArtifact@0 inputs: diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index 4b82e42689c..4e6f8f13d0e 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -1,6 +1,15 @@ parameters: - name: VSCODE_QUALITY type: string + - name: VSCODE_RUN_UNIT_TESTS + type: boolean + default: true + - name: VSCODE_RUN_INTEGRATION_TESTS + type: boolean + default: true + - name: VSCODE_RUN_SMOKE_TESTS + type: boolean + default: true steps: - task: NodeTool@0 @@ -221,104 +230,139 @@ steps: stat $ELECTRON_ROOT/chrome-sandbox displayName: Change setuid helper binary permission - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + yarn gulp \ + compile-extension:css-language-features-server \ + compile-extension:emmet \ + compile-extension:git \ + compile-extension:github-authentication \ + compile-extension:html-language-features-server \ + compile-extension:ipynb \ + compile-extension:json-language-features-server \ + compile-extension:markdown-language-features \ + compile-extension-media \ + compile-extension:microsoft-authentication \ + compile-extension:typescript-language-features \ + compile-extension:vscode-api-tests \ + compile-extension:vscode-colorize-tests \ + compile-extension:vscode-custom-editor-tests \ + compile-extension:vscode-notebook-tests \ + compile-extension:vscode-test-resolver + displayName: Build integration tests + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser chromium - displayName: Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - ps -ef - cat /proc/sys/fs/inotify/max_user_watches - lsof | wc -l - displayName: Diagnostics before smoke test run (processes, max_user_watches, number of opened file handles) - continueOnError: true - condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + ps -ef + cat /proc/sys/fs/inotify/max_user_watches + lsof | wc -l + displayName: Diagnostics before smoke test run (processes, max_user_watches, number of opened file handles) + continueOnError: true + condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --tracing --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + yarn smoketest-no-compile --tracing --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | - set -e - ps -ef - cat /proc/sys/fs/inotify/max_user_watches - lsof | wc -l - displayName: Diagnostics after smoke test run (processes, max_user_watches, number of opened file handles) - continueOnError: true - condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - script: | + set -e + ps -ef + cat /proc/sys/fs/inotify/max_user_watches + lsof | wc -l + displayName: Diagnostics after smoke test run (processes, max_user_watches, number of opened file handles) + continueOnError: true + condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: PublishPipelineArtifact@0 inputs: diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 21fbe295cc9..5b09f8ee77e 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -60,7 +60,8 @@ stages: - Compile pool: vscode-1es-vscode-windows-2019 jobs: - - job: Windows + - job: WindowsUnitTests + displayName: Unit Tests timeoutInMinutes: 120 variables: VSCODE_ARCH: x64 @@ -68,6 +69,33 @@ stages: - template: win32/product-build-win32.yml parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsIntegrationTests + displayName: Integration Tests + timeoutInMinutes: 120 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsSmokeTests + displayName: Smoke Tests + timeoutInMinutes: 120 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - stage: Linux dependsOn: @@ -75,7 +103,8 @@ stages: - LinuxServerDependencies pool: vscode-1es-vscode-linux-18.04 jobs: - - job: Linuxx64 + - job: Linuxx64UnitTest + displayName: Unit Tests container: vscode-bionic-x64 variables: VSCODE_ARCH: x64 @@ -85,6 +114,37 @@ stages: - template: linux/product-build-linux-client.yml parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64IntegrationTest + displayName: Integration Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64SmokeTest + displayName: Smoke Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - stage: macOS dependsOn: @@ -94,7 +154,8 @@ stages: variables: BUILDSECMON_OPT_IN: true jobs: - - job: macOSTest + - job: macOSUnitTest + displayName: Unit Tests timeoutInMinutes: 90 variables: VSCODE_ARCH: x64 @@ -102,3 +163,30 @@ stages: - template: darwin/product-build-darwin-test.yml parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSIntegrationTest + displayName: Integration Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin-test.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSSmokeTest + displayName: Smoke Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin-test.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index d089f70d228..286283a31dc 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,6 +1,15 @@ parameters: - name: VSCODE_QUALITY type: string + - name: VSCODE_RUN_UNIT_TESTS + type: boolean + default: true + - name: VSCODE_RUN_INTEGRATION_TESTS + type: boolean + default: true + - name: VSCODE_RUN_SMOKE_TESTS + type: boolean + default: true steps: - task: NodeTool@0 @@ -184,105 +193,142 @@ steps: displayName: Download Playwright condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --build --tfs "Unit Tests" } - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --build --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-node --build } - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-node --build } + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser, Chromium & Firefox) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser, Chromium & Firefox) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn gulp ` + compile-extension:css-language-features-server ` + compile-extension:emmet ` + compile-extension:git ` + compile-extension:github-authentication ` + compile-extension:html-language-features-server ` + compile-extension:ipynb ` + compile-extension:json-language-features-server ` + compile-extension:markdown-language-features ` + compile-extension-media ` + compile-extension:microsoft-authentication ` + compile-extension:typescript-language-features ` + compile-extension:vscode-api-tests ` + compile-extension:vscode-colorize-tests ` + compile-extension:vscode-custom-editor-tests ` + compile-extension:vscode-notebook-tests ` + compile-extension:vscode-test-resolver ` + } + displayName: Build integration tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } - displayName: Run integration tests (Browser, Firefox) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec {.\build\azure-pipelines\win32\listprocesses.bat } - displayName: Diagnostics before smoke test run - continueOnError: true - condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --tracing --headless } - displayName: Run smoke tests (Browser, Chromium) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + exec {.\build\azure-pipelines\win32\listprocesses.bat } + displayName: Diagnostics before smoke test run + continueOnError: true + condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } - displayName: Run smoke tests (Electron) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --web --tracing --headless } + displayName: Run smoke tests (Browser, Chromium) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } - displayName: Run smoke tests (Remote) - timeoutInMinutes: 20 - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } + displayName: Run smoke tests (Electron) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec {.\build\azure-pipelines\win32\listprocesses.bat } - displayName: Diagnostics after smoke test run - continueOnError: true - condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" + exec { yarn gulp compile-extension:vscode-test-resolver } + exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } + displayName: Run smoke tests (Remote) + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + + - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + exec {.\build\azure-pipelines\win32\listprocesses.bat } + displayName: Diagnostics after smoke test run + continueOnError: true + condition: and(succeededOrFailed(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: PublishPipelineArtifact@0 inputs: diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 0260a781d8d..8ab9e7d76dd 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -21,19 +21,19 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Run from a built: need to compile all test extensions :: because we run extension tests from their source folders :: and the build bundles extensions into .build webpacked - call yarn gulp compile-extension:vscode-api-tests^ - compile-extension:vscode-colorize-tests^ - compile-extension:markdown-language-features^ - compile-extension:typescript-language-features^ - compile-extension:vscode-custom-editor-tests^ - compile-extension:vscode-notebook-tests^ - compile-extension:emmet^ - compile-extension:css-language-features-server^ - compile-extension:html-language-features-server^ - compile-extension:json-language-features-server^ - compile-extension:git^ - compile-extension:ipynb^ - compile-extension-media + :: call yarn gulp compile-extension:vscode-api-tests^ + :: compile-extension:vscode-colorize-tests^ + :: compile-extension:markdown-language-features^ + :: compile-extension:typescript-language-features^ + :: compile-extension:vscode-custom-editor-tests^ + :: compile-extension:vscode-notebook-tests^ + :: compile-extension:emmet^ + :: compile-extension:css-language-features-server^ + :: compile-extension:html-language-features-server^ + :: compile-extension:json-language-features-server^ + :: compile-extension:git^ + :: compile-extension:ipynb^ + :: compile-extension-media :: Configuration for more verbose output set VSCODE_CLI=1 diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 8ddfa7e9eae..e381f61b40a 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -30,19 +30,19 @@ else # Run from a built: need to compile all test extensions # because we run extension tests from their source folders # and the build bundles extensions into .build webpacked - yarn gulp compile-extension:vscode-api-tests \ - compile-extension:vscode-colorize-tests \ - compile-extension:vscode-custom-editor-tests \ - compile-extension:vscode-notebook-tests \ - compile-extension:markdown-language-features \ - compile-extension:typescript-language-features \ - compile-extension:emmet \ - compile-extension:css-language-features-server \ - compile-extension:html-language-features-server \ - compile-extension:json-language-features-server \ - compile-extension:git \ - compile-extension:ipynb \ - compile-extension-media + # yarn gulp compile-extension:vscode-api-tests \ + # compile-extension:vscode-colorize-tests \ + # compile-extension:vscode-custom-editor-tests \ + # compile-extension:vscode-notebook-tests \ + # compile-extension:markdown-language-features \ + # compile-extension:typescript-language-features \ + # compile-extension:emmet \ + # compile-extension:css-language-features-server \ + # compile-extension:html-language-features-server \ + # compile-extension:json-language-features-server \ + # compile-extension:git \ + # compile-extension:ipynb \ + # compile-extension-media # Configuration for more verbose output export VSCODE_CLI=1 diff --git a/scripts/test-remote-integration.bat b/scripts/test-remote-integration.bat index 1c04b2e392c..a7d0719205f 100644 --- a/scripts/test-remote-integration.bat +++ b/scripts/test-remote-integration.bat @@ -53,10 +53,10 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: Run from a built: need to compile all test extensions :: because we run extension tests from their source folders :: and the build bundles extensions into .build webpacked - call yarn gulp compile-extension:vscode-api-tests^ - compile-extension:microsoft-authentication^ - compile-extension:github-authentication^ - compile-extension:vscode-test-resolver + :: call yarn gulp compile-extension:vscode-api-tests^ + :: compile-extension:microsoft-authentication^ + :: compile-extension:github-authentication^ + :: compile-extension:vscode-test-resolver :: Configuration for more verbose output set VSCODE_CLI=1 diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index e2212317d3b..7decdf3798f 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -47,16 +47,16 @@ else # Run from a built: need to compile all test extensions # because we run extension tests from their source folders # and the build bundles extensions into .build webpacked - yarn gulp compile-extension:vscode-api-tests \ - compile-extension:vscode-test-resolver \ - compile-extension:markdown-language-features \ - compile-extension:typescript-language-features \ - compile-extension:emmet \ - compile-extension:git \ - compile-extension:ipynb \ - compile-extension:microsoft-authentication \ - compile-extension:github-authentication \ - compile-extension-media + # yarn gulp compile-extension:vscode-api-tests \ + # compile-extension:vscode-test-resolver \ + # compile-extension:markdown-language-features \ + # compile-extension:typescript-language-features \ + # compile-extension:emmet \ + # compile-extension:git \ + # compile-extension:ipynb \ + # compile-extension:microsoft-authentication \ + # compile-extension:github-authentication \ + # compile-extension-media # Configuration for more verbose output export VSCODE_CLI=1 diff --git a/scripts/test-web-integration.bat b/scripts/test-web-integration.bat index 99bb16b7d5e..c5b89b85b36 100644 --- a/scripts/test-web-integration.bat +++ b/scripts/test-web-integration.bat @@ -25,12 +25,12 @@ if "%VSCODE_REMOTE_SERVER_PATH%"=="" ( :: Run from a built: need to compile all test extensions :: because we run extension tests from their source folders :: and the build bundles extensions into .build webpacked - call yarn gulp compile-extension:vscode-api-tests^ - compile-extension:markdown-language-features^ - compile-extension:typescript-language-features^ - compile-extension:emmet^ - compile-extension:git^ - compile-extension-media + :: call yarn gulp compile-extension:vscode-api-tests^ + :: compile-extension:markdown-language-features^ + :: compile-extension:typescript-language-features^ + :: compile-extension:emmet^ + :: compile-extension:git^ + :: compile-extension-media ) if not exist ".\test\integration\browser\out\index.js" ( diff --git a/scripts/test-web-integration.sh b/scripts/test-web-integration.sh index 8f05929fdc4..4246cdc6ac1 100755 --- a/scripts/test-web-integration.sh +++ b/scripts/test-web-integration.sh @@ -19,13 +19,13 @@ else # Run from a built: need to compile all test extensions # because we run extension tests from their source folders # and the build bundles extensions into .build webpacked - yarn gulp compile-extension:vscode-api-tests \ - compile-extension:markdown-language-features \ - compile-extension:typescript-language-features \ - compile-extension:emmet \ - compile-extension:git \ - compile-extension:ipynb \ - compile-extension-media + # yarn gulp compile-extension:vscode-api-tests \ + # compile-extension:markdown-language-features \ + # compile-extension:typescript-language-features \ + # compile-extension:emmet \ + # compile-extension:git \ + # compile-extension:ipynb \ + # compile-extension-media fi if [ ! -e 'test/integration/browser/out/index.js' ];then From c88474c6bc775ed68eb2448279ca0a3a79d016ea Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 11:03:34 +0200 Subject: [PATCH 122/350] depend on vscode-policy-watcher --- package.json | 1 + yarn.lock | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/package.json b/package.json index affa8ac0cdc..ed114a6ceac 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ }, "optionalDependencies": { "@vscode/windows-registry": "1.0.6", + "vscode-policy-watcher": "^1.0.0", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "0.3.3" diff --git a/yarn.lock b/yarn.lock index 13805339355..ac4091e65aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8136,6 +8136,11 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-addon-api@*: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== + node-addon-api@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" @@ -11791,6 +11796,14 @@ vscode-oniguruma@1.6.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== +vscode-policy-watcher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.0.0.tgz#08138a5ab15a1d6c021df51716a4daa97d74b471" + integrity sha512-j9nWBInu9wPW+3Uw/flYXP0kTewTqTfM6ADC24stqxURCAGOWxYz3ArBo21o+3zOrxpfCjc5mCbUNfLk+6IHuA== + dependencies: + bindings "^1.5.0" + node-addon-api "*" + vscode-proxy-agent@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.12.0.tgz#0775f464b9519b0c903da4dcf50851e1453f4e48" From e2029b6cbed68372b14856222ea7aaf5fb61fe5f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 11:15:18 +0200 Subject: [PATCH 123/350] cleanup vscode-policy-watcher --- build/.moduleignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/.moduleignore b/build/.moduleignore index 22c1b4fe48f..a2e1b715e1b 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -107,6 +107,14 @@ vscode-encrypt/binding.gyp vscode-encrypt/README.md !vscode-encrypt/build/Release/vscode-encrypt-native.node +vscode-policy-watcher/build/** +vscode-policy-watcher/.husky/** +vscode-policy-watcher/src/** +vscode-policy-watcher/binding.gyp +vscode-policy-watcher/README.md +vscode-policy-watcher/index.d.ts +!vscode-policy-watcher/build/Release/vscode-policy-watcher.node + vscode-windows-ca-certs/**/* !vscode-windows-ca-certs/package.json !vscode-windows-ca-certs/**/*.node From 0219800af14c2bb3f7a7225ebe1ed8e96dde00c2 Mon Sep 17 00:00:00 2001 From: Anthony Stewart <150152+a-stewart@users.noreply.github.com> Date: Wed, 18 May 2022 11:50:25 +0200 Subject: [PATCH 124/350] Use the pointer cursor for links in the banner bar (#149738) Co-authored-by: Anthony Stewart --- src/vs/workbench/browser/parts/banner/media/bannerpart.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/browser/parts/banner/media/bannerpart.css b/src/vs/workbench/browser/parts/banner/media/bannerpart.css index fb2d5b4d1e8..aa793f995a6 100644 --- a/src/vs/workbench/browser/parts/banner/media/bannerpart.css +++ b/src/vs/workbench/browser/parts/banner/media/bannerpart.css @@ -52,10 +52,12 @@ padding: 3px; margin-left: 12px; text-decoration: underline; + cursor: pointer; } .monaco-workbench .part.banner .message-container a { text-decoration: underline; + cursor: pointer; } .monaco-workbench .part.banner .action-container { From ba5691170f4e9c9b3cced095ea498d3c243d1add Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 18 May 2022 09:47:49 +0200 Subject: [PATCH 125/350] update vscode-grammar-updater --- extensions/package.json | 2 +- extensions/yarn.lock | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index 3704c9b327c..b6d00d2faaf 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -12,6 +12,6 @@ "devDependencies": { "@parcel/watcher": "2.0.5", "esbuild": "^0.11.12", - "vscode-grammar-updater": "^1.0.4" + "vscode-grammar-updater": "^1.1.0" } } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 97766b7943b..1c746b8d2d0 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -10,17 +10,17 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" -coffee-script@^1.10.0: +coffeescript@1.12.7: version "1.12.7" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" - integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== -cson-parser@^1.3.3: - version "1.3.5" - resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-1.3.5.tgz#7ec675e039145533bf2a6a856073f1599d9c2d24" - integrity sha1-fsZ14DkUVTO/KmqFYHPxWZ2cLSQ= +cson-parser@^4.0.9: + version "4.0.9" + resolved "https://registry.yarnpkg.com/cson-parser/-/cson-parser-4.0.9.tgz#eef0cf77edd057f97861ef800300c8239224eedb" + integrity sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag== dependencies: - coffee-script "^1.10.0" + coffeescript "1.12.7" esbuild@^0.11.12: version "0.11.23" @@ -47,10 +47,10 @@ typescript@4.7.1-rc: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.1-rc.tgz#23a0517d36c56de887b4457f29e2d265647bbd7c" integrity sha512-EQd2NVelDe6ZVc2sO1CSpuSs+RHzY8c2n/kTNQAHw4um/eAXY+ZY4IKoUpNK0wO6C5hN+XcUXR7yqT8VbwwNIQ== -vscode-grammar-updater@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vscode-grammar-updater/-/vscode-grammar-updater-1.0.4.tgz#f0b8bd106a499a15f3e6b199055908ed8e860984" - integrity sha512-WjmpFo+jlnxOfHNeSrO3nJx8S2u3f926UL0AHJhDMQghCwEfkMvf37aafF83xvtLW2G9ywhifLbq4caxDQm+wQ== +vscode-grammar-updater@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vscode-grammar-updater/-/vscode-grammar-updater-1.1.0.tgz#030eacd8b8ba8f3f2fe43c9032601f839ba811c4" + integrity sha512-rWcJXyEFK27Mh9bxfBTLaul0KiGQk0GMXj2qTDH9cy3UZVx5MrF035B03os1w4oIXwl/QDhdLnsBK0j2SNiL1A== dependencies: - cson-parser "^1.3.3" + cson-parser "^4.0.9" fast-plist "0.1.2" From 56eca91b26ff4e25f548aae1969990cfa6716544 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 14:13:58 +0200 Subject: [PATCH 126/350] add vscode-policy-watcher dependency, support registerPolicyDefinitions --- package.json | 2 +- src/vs/code/electron-main/main.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 6 +- .../policy/node/nativePolicyService.ts | 68 ++++++++++++++++++ .../policy/node/windowsPolicyService.ts | 70 ------------------- yarn.lock | 8 +-- 6 files changed, 79 insertions(+), 79 deletions(-) create mode 100644 src/vs/platform/policy/node/nativePolicyService.ts delete mode 100644 src/vs/platform/policy/node/windowsPolicyService.ts diff --git a/package.json b/package.json index ed114a6ceac..412567abe45 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "tas-client-umd": "0.1.5", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.6.1", + "vscode-policy-watcher": "^1.1.0", "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", @@ -221,7 +222,6 @@ }, "optionalDependencies": { "@vscode/windows-registry": "1.0.6", - "vscode-policy-watcher": "^1.0.0", "windows-foreground-love": "0.4.0", "windows-mutex": "0.4.1", "windows-process-tree": "0.3.3" diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d1563aebe63..cb41f40555e 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -63,7 +63,7 @@ import { StateMainService } from 'vs/platform/state/electron-main/stateMainServi import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; /** @@ -170,7 +170,7 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) + const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) : new NullPolicyService(); services.set(IPolicyService, policyService); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index f0151458057..bc630b7cae8 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -42,7 +42,7 @@ import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLo import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { WindowsPolicyService } from 'vs/platform/policy/node/windowsPolicyService'; +import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -133,7 +133,9 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new WindowsPolicyService(productService.win32RegValueName) : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) : new NullPolicyService(); + const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) + : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) + : new NullPolicyService(); services.set(IPolicyService, policyService); // Configuration diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts new file mode 100644 index 00000000000..10cd2ee5446 --- /dev/null +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Iterable } from 'vs/base/common/iterator'; +import { Throttler } from 'vs/base/common/async'; +import type { Watcher } from 'vscode-policy-watcher'; + +export class NativePolicyService implements IPolicyService { + + readonly _serviceBrand: undefined; + + private policyDefinitions: IStringDictionary = {}; + private readonly policies = new Map(); + + private readonly _onDidChange = new Emitter(); + readonly onDidChange = this._onDidChange.event; + + private throttler = new Throttler(); + private watcher: Watcher | undefined; + + constructor(private readonly productName: string) { } + + async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { + const size = Object.keys(this.policyDefinitions).length; + this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + + if (size !== Object.keys(this.policyDefinitions).length) { + await this.throttler.queue(async () => { + this.watcher?.dispose(); + + const { createWatcher } = await import('vscode-policy-watcher'); + + await new Promise(c => { + this.watcher = createWatcher(this.productName, policyDefinitions, update => { + for (const key in update) { + const value = update[key] as any; + + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); + } + } + + this._onDidChange.fire(Object.keys(update)); + c(); + }); + }); + }); + } + + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } + + dispose(): void { + this._onDidChange.dispose(); + this.watcher?.dispose(); + } +} diff --git a/src/vs/platform/policy/node/windowsPolicyService.ts b/src/vs/platform/policy/node/windowsPolicyService.ts deleted file mode 100644 index 23439893dc8..00000000000 --- a/src/vs/platform/policy/node/windowsPolicyService.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; -import { createWatcher, Watcher } from 'vscode-policy-watcher'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { Iterable } from 'vs/base/common/iterator'; - -export class WindowsPolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private readonly policies = new Map(); - private init: Promise | undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - constructor(private readonly productName: string) { - super(); - } - - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - if (!this.init) { - this.init = new Promise(c => { - let first = true; - - const watcher = createWatcher(this.productName, policies, update => { - for (const key in update) { - const value = update[key] as any; - - if (value === undefined) { - this.policies.delete(key); - } else { - this.policies.set(key, value); - } - } - - if (first) { - first = false; - c(watcher); - } else { - this._onDidChange.fire(Object.keys(update)); - } - }); - - this._register(watcher); - }); - - await this.init; - } else { - const watcher = await this.init; - const promise = Event.toPromise(this.onDidChange); - watcher.addPolicies(policies); - await promise; - } - - // TODO@joao: heavy cleanup - - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); - } - - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } -} diff --git a/yarn.lock b/yarn.lock index ac4091e65aa..36cf507aa65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11796,10 +11796,10 @@ vscode-oniguruma@1.6.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== -vscode-policy-watcher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.0.0.tgz#08138a5ab15a1d6c021df51716a4daa97d74b471" - integrity sha512-j9nWBInu9wPW+3Uw/flYXP0kTewTqTfM6ADC24stqxURCAGOWxYz3ArBo21o+3zOrxpfCjc5mCbUNfLk+6IHuA== +vscode-policy-watcher@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vscode-policy-watcher/-/vscode-policy-watcher-1.1.0.tgz#2921353c5080b3452929f1e350b9fab9ff852cc9" + integrity sha512-yPvy3Or66H0l8/FyWbJeGxpWW3GDZf65EIT7fqp1Ethdz4ecEnHThuHti7SlfdRJTf5qnifrX7af02INiHqDMA== dependencies: bindings "^1.5.0" node-addon-api "*" From 089ef919ce457b047df2348360b0981b170ccb98 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 18 May 2022 14:34:19 +0200 Subject: [PATCH 127/350] update native policy service --- .../policy/node/nativePolicyService.ts | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 10cd2ee5446..9011bbf1534 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -8,35 +8,34 @@ import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/pl import { IStringDictionary } from 'vs/base/common/collections'; import { Iterable } from 'vs/base/common/iterator'; import { Throttler } from 'vs/base/common/async'; -import type { Watcher } from 'vscode-policy-watcher'; +import { createWatcher, Watcher } from 'vscode-policy-watcher'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; -export class NativePolicyService implements IPolicyService { +export class NativePolicyService extends Disposable implements IPolicyService { readonly _serviceBrand: undefined; private policyDefinitions: IStringDictionary = {}; private readonly policies = new Map(); - private readonly _onDidChange = new Emitter(); + private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; private throttler = new Throttler(); - private watcher: Watcher | undefined; + private watcher = this._register(new MutableDisposable()); - constructor(private readonly productName: string) { } + constructor(private readonly productName: string) { + super(); + } async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { const size = Object.keys(this.policyDefinitions).length; this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; if (size !== Object.keys(this.policyDefinitions).length) { - await this.throttler.queue(async () => { - this.watcher?.dispose(); - - const { createWatcher } = await import('vscode-policy-watcher'); - - await new Promise(c => { - this.watcher = createWatcher(this.productName, policyDefinitions, update => { + await this.throttler.queue(() => new Promise((c, e) => { + try { + this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { for (const key in update) { const value = update[key] as any; @@ -50,8 +49,10 @@ export class NativePolicyService implements IPolicyService { this._onDidChange.fire(Object.keys(update)); c(); }); - }); - }); + } catch (err) { + e(err); + } + })); } return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); @@ -60,9 +61,4 @@ export class NativePolicyService implements IPolicyService { getPolicyValue(name: PolicyName): PolicyValue | undefined { return this.policies.get(name); } - - dispose(): void { - this._onDidChange.dispose(); - this.watcher?.dispose(); - } } From 351e3c525ac93a118e117ff65bd839ae6cc35853 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Wed, 18 May 2022 08:00:40 -0700 Subject: [PATCH 128/350] Report extension list (#147960) * Report extensions * Rename and move installedExtensions log --- .../browser/extensionsWorkbenchService.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 3aacb593829..7fda957ef46 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -34,7 +34,7 @@ import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IProductService } from 'vs/platform/product/common/productService'; import { FileAccess } from 'vs/base/common/network'; @@ -46,11 +46,21 @@ import { IExtensionManifestPropertiesService } from 'vs/workbench/services/exten import { IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { isWeb } from 'vs/base/common/platform'; +import { GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; interface IExtensionStateProvider { (extension: Extension): T; } +interface InstalledExtensionsEvent { + readonly extensionIds: string; + readonly count: number; +} +interface ExtensionsLoadClassification extends GDPRClassification { + readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; +} + export class Extension implements IExtension { public enablementState: EnablementState = EnablementState.EnabledGlobally; @@ -716,6 +726,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.queryLocal().then(() => { this.resetIgnoreAutoUpdateExtensions(); this.eventuallyCheckForUpdates(true); + this._reportTelemetry(); // Always auto update builtin extensions in web if (isWeb && !this.isAutoUpdateEnabled()) { this.autoUpdateBuiltinExtensions(); @@ -727,6 +738,14 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.updateActivity(); })); } + private _reportTelemetry() { + const extensionIds = this.installed.filter(extension => + extension.type === ExtensionType.User && + (extension.enablementState === EnablementState.EnabledWorkspace || + extension.enablementState === EnablementState.EnabledGlobally)) + .map(extension => ExtensionIdentifier.toKey(extension.identifier.id)); + this.telemetryService.publicLog2('installedExtensions', { extensionIds: extensionIds.join(';'), count: extensionIds.length }); + } get local(): IExtension[] { const byId = groupByExtension(this.installed, r => r.identifier); From f9264e31b5c4ae909c20afc339b54d31738f840d Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 18 May 2022 09:02:15 -0700 Subject: [PATCH 129/350] fixes #149013 (#149785) --- src/vs/workbench/browser/parts/panel/media/basepanelpart.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css index 6cb382b8a5d..1f4f10f21c3 100644 --- a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css @@ -176,13 +176,13 @@ } .monaco-workbench .part.basepanel > .composite.title > .panel-switcher-container > .monaco-action-bar .action-item .active-item-indicator { - top: -6px; + top: -4px; left: 10px; width: calc(100% - 20px); } .monaco-workbench .part.basepanel > .composite.title > .panel-switcher-container > .monaco-action-bar .action-item.icon .active-item-indicator { - top: -1px; + top: 1px; left: 2px; width: calc(100% - 4px); } From ae2202631366eea0aaed6f881f87c2b44ae1b647 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 18 May 2022 09:23:39 -0700 Subject: [PATCH 130/350] fixes #143887 (#149836) --- src/vs/base/browser/ui/menu/menu.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 24feda55daa..e4984b09654 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -90,14 +90,13 @@ export class Menu extends ActionBar { context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, + ariaRole: 'menu', focusOnlyEnabledItems: true, triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true } }); this.menuElement = menuElement; - this.actionsList.setAttribute('role', 'menu'); - this.actionsList.tabIndex = 0; this.menuDisposables = this._register(new DisposableStore()); From 5cdfdb435f6fb846c15a65ba3c8f7978003413fc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 18 May 2022 09:45:17 -0700 Subject: [PATCH 131/350] Support selection foreground in terminal Fixes #149505 --- package.json | 10 +++--- remote/package.json | 8 ++--- remote/web/package.json | 6 ++-- remote/web/yarn.lock | 24 +++++++------- remote/yarn.lock | 32 +++++++++---------- .../terminal/browser/xterm/xtermTerminal.ts | 4 ++- .../terminal/common/terminalColorRegistry.ts | 6 ++++ yarn.lock | 32 +++++++++---------- 8 files changed, 65 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 5ca24d43a8d..035efff2f33 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.43", - "xterm-addon-search": "0.9.0-beta.35", + "xterm": "4.19.0-beta.47", + "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.34", - "xterm-headless": "4.19.0-beta.43", + "xterm-addon-webgl": "0.12.0-beta.36", + "xterm-headless": "4.19.0-beta.47", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -229,4 +229,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} \ No newline at end of file +} diff --git a/remote/package.json b/remote/package.json index 80e835671ce..75e83acbde5 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.43", - "xterm-addon-search": "0.9.0-beta.35", + "xterm": "4.19.0-beta.47", + "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.34", - "xterm-headless": "4.19.0-beta.43", + "xterm-addon-webgl": "0.12.0-beta.36", + "xterm-headless": "4.19.0-beta.47", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index cdbbf667adf..ece7c79fe5c 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,9 +10,9 @@ "tas-client-umd": "0.1.5", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.43", - "xterm-addon-search": "0.9.0-beta.35", + "xterm": "4.19.0-beta.47", + "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.34" + "xterm-addon-webgl": "0.12.0-beta.36" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 8316dc85a0e..97609688b6d 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -113,22 +113,22 @@ vscode-textmate@7.0.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== -xterm-addon-search@0.9.0-beta.35: - version "0.9.0-beta.35" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.35.tgz#524ee3be855c1e8db234c6795bdb44bb6baff8fd" - integrity sha512-hTDqAhqlhBvz3dtdK1Tg5Al2U3HquSHpV1xCX+bbOmbgprAxUrSQxslUPDD69CTazzTyif3L19M08hccRyr1Ug== +xterm-addon-search@0.9.0-beta.37: + version "0.9.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.37.tgz#84a020f03b2cacc5afac78ca6ff2f1eb86fb6710" + integrity sha512-bfeFgKJkDYyIgqpWiV1oWYqDiTo+SHTeIPEbpOfxDr97pc3PtDF1Tyd79PrvJNfoxaV3VMUo//UEOy4D+KY9OQ== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.34: - version "0.12.0-beta.34" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" - integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== +xterm-addon-webgl@0.12.0-beta.36: + version "0.12.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" + integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm@4.19.0-beta.43: - version "4.19.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" - integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== +xterm@4.19.0-beta.47: + version "4.19.0-beta.47" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" + integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index a6201c6d433..f023194fa26 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -914,10 +914,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.9.0-beta.35: - version "0.9.0-beta.35" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.35.tgz#524ee3be855c1e8db234c6795bdb44bb6baff8fd" - integrity sha512-hTDqAhqlhBvz3dtdK1Tg5Al2U3HquSHpV1xCX+bbOmbgprAxUrSQxslUPDD69CTazzTyif3L19M08hccRyr1Ug== +xterm-addon-search@0.9.0-beta.37: + version "0.9.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.37.tgz#84a020f03b2cacc5afac78ca6ff2f1eb86fb6710" + integrity sha512-bfeFgKJkDYyIgqpWiV1oWYqDiTo+SHTeIPEbpOfxDr97pc3PtDF1Tyd79PrvJNfoxaV3VMUo//UEOy4D+KY9OQ== xterm-addon-serialize@0.7.0-beta.12: version "0.7.0-beta.12" @@ -929,20 +929,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.34: - version "0.12.0-beta.34" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" - integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== +xterm-addon-webgl@0.12.0-beta.36: + version "0.12.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" + integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.43: - version "4.19.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.43.tgz#12fe4abe624265240a7de8a922bfc4fd28c5f92a" - integrity sha512-4T8TlWy5u+sS23aPtd8gBHJ0BVljbNQRPMFHzLigDNOMCwc4uWa9JsxYmKteKifcG5aMm11ALPUTxWZCgpATww== +xterm-headless@4.19.0-beta.47: + version "4.19.0-beta.47" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" + integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== -xterm@4.19.0-beta.43: - version "4.19.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" - integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== +xterm@4.19.0-beta.47: + version "4.19.0-beta.47" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" + integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 509db6cc5a6..3bf9f12b530 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -27,7 +27,7 @@ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeServic import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, ansiColorIdentifiers, TERMINAL_SELECTION_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_HIGHLIGHT_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_BORDER_COLOR, TERMINAL_OVERVIEW_RULER_FIND_MATCH_FOREGROUND_COLOR, TERMINAL_FIND_MATCH_HIGHLIGHT_BORDER_COLOR, TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { TERMINAL_FOREGROUND_COLOR, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, ansiColorIdentifiers, TERMINAL_SELECTION_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_HIGHLIGHT_BACKGROUND_COLOR, TERMINAL_FIND_MATCH_BORDER_COLOR, TERMINAL_OVERVIEW_RULER_FIND_MATCH_FOREGROUND_COLOR, TERMINAL_FIND_MATCH_HIGHLIGHT_BORDER_COLOR, TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR, TERMINAL_SELECTION_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { Color } from 'vs/base/common/color'; import { ShellIntegrationAddon } from 'vs/platform/terminal/common/xterm/shellIntegrationAddon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -567,6 +567,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor; const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor; const selectionColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR); + const selectionForegroundColor = theme.getColor(TERMINAL_SELECTION_FOREGROUND_COLOR) || undefined; return { background: backgroundColor ? backgroundColor.toString() : undefined, @@ -574,6 +575,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { cursor: cursorColor ? cursorColor.toString() : undefined, cursorAccent: cursorAccentColor ? cursorAccentColor.toString() : undefined, selection: selectionColor ? selectionColor.toString() : undefined, + selectionForeground: selectionForegroundColor?.toString(), black: theme.getColor(ansiColorIdentifiers[0])?.toString(), red: theme.getColor(ansiColorIdentifiers[1])?.toString(), green: theme.getColor(ansiColorIdentifiers[2])?.toString(), diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index bdfbe2ad4d0..44061cca143 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -29,6 +29,12 @@ export const TERMINAL_SELECTION_BACKGROUND_COLOR = registerColor('terminal.selec hcDark: editorSelectionBackground, hcLight: editorSelectionBackground }, nls.localize('terminal.selectionBackground', 'The selection background color of the terminal.')); +export const TERMINAL_SELECTION_FOREGROUND_COLOR = registerColor('terminal.selectionForeground', { + light: null, + dark: null, + hcDark: '#000000', + hcLight: '#ffffff' +}, nls.localize('terminal.selectionForeground', 'The selection foreground color of the terminal. When this is null the selection foreground will be retained and have the minimum contrast ratio feature applied.')); export const TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.defaultBackground', { light: '#00000040', dark: '#ffffff40', diff --git a/yarn.lock b/yarn.lock index c4c6630ffe3..cee415886c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12194,10 +12194,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.9.0-beta.35: - version "0.9.0-beta.35" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.35.tgz#524ee3be855c1e8db234c6795bdb44bb6baff8fd" - integrity sha512-hTDqAhqlhBvz3dtdK1Tg5Al2U3HquSHpV1xCX+bbOmbgprAxUrSQxslUPDD69CTazzTyif3L19M08hccRyr1Ug== +xterm-addon-search@0.9.0-beta.37: + version "0.9.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.37.tgz#84a020f03b2cacc5afac78ca6ff2f1eb86fb6710" + integrity sha512-bfeFgKJkDYyIgqpWiV1oWYqDiTo+SHTeIPEbpOfxDr97pc3PtDF1Tyd79PrvJNfoxaV3VMUo//UEOy4D+KY9OQ== xterm-addon-serialize@0.7.0-beta.12: version "0.7.0-beta.12" @@ -12209,20 +12209,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.34: - version "0.12.0-beta.34" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.34.tgz#51cac31cc7a78377be5d481b624ee82948360de1" - integrity sha512-TTIwun+a45oDN54sHhdUxsEx6VflgF2p9YGqS5+gVzpvPrEqP6GoDr6XFCDsZcSqi0ZT2FNGAKWlh7XSxsKQQw== +xterm-addon-webgl@0.12.0-beta.36: + version "0.12.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" + integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.43: - version "4.19.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.43.tgz#12fe4abe624265240a7de8a922bfc4fd28c5f92a" - integrity sha512-4T8TlWy5u+sS23aPtd8gBHJ0BVljbNQRPMFHzLigDNOMCwc4uWa9JsxYmKteKifcG5aMm11ALPUTxWZCgpATww== +xterm-headless@4.19.0-beta.47: + version "4.19.0-beta.47" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" + integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== -xterm@4.19.0-beta.43: - version "4.19.0-beta.43" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.43.tgz#f2113c8ce303d22c5cfad1a4a119b81c103285fa" - integrity sha512-eQ3fzkUApGdl4/rrhzK4OIdMb3+qO0c2iCZIMbeP9SqqDltZnhWncz+3lGa0tnxKizVoUV9kmGaP7orsQ/IavQ== +xterm@4.19.0-beta.47: + version "4.19.0-beta.47" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" + integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== y18n@^3.2.1: version "3.2.2" From 3678e5fd8a773f0f3b66bf418875475b988c38ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 18 May 2022 09:58:02 -0700 Subject: [PATCH 132/350] Clean up terminal xterm theme setting function --- .../contrib/terminal/browser/xterm/xtermTerminal.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 3bf9f12b530..6849cc1ff84 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -566,15 +566,15 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { } const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor; const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor; - const selectionColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR); + const selectionBackgroundColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR); const selectionForegroundColor = theme.getColor(TERMINAL_SELECTION_FOREGROUND_COLOR) || undefined; return { - background: backgroundColor ? backgroundColor.toString() : undefined, - foreground: foregroundColor ? foregroundColor.toString() : undefined, - cursor: cursorColor ? cursorColor.toString() : undefined, - cursorAccent: cursorAccentColor ? cursorAccentColor.toString() : undefined, - selection: selectionColor ? selectionColor.toString() : undefined, + background: backgroundColor?.toString(), + foreground: foregroundColor?.toString(), + cursor: cursorColor?.toString(), + cursorAccent: cursorAccentColor?.toString(), + selection: selectionBackgroundColor?.toString(), selectionForeground: selectionForegroundColor?.toString(), black: theme.getColor(ansiColorIdentifiers[0])?.toString(), red: theme.getColor(ansiColorIdentifiers[1])?.toString(), From ca280a1685a34207a8bcf4826f8294d824b3caf7 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Wed, 18 May 2022 10:15:11 -0700 Subject: [PATCH 133/350] Update menubar button styles (Fix #149834) (#149835) * Use border radius/padding for classic menubar * Removed unused line-height rule --- src/vs/base/browser/ui/menu/menubar.css | 4 +++- .../workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css index d2a70146718..b9dd13780e9 100644 --- a/src/vs/base/browser/ui/menu/menubar.css +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -9,7 +9,8 @@ display: flex; flex-shrink: 1; box-sizing: border-box; - height: 30px; + height: 100%; + padding: 4px 0; overflow: hidden; flex-wrap: wrap; } @@ -23,6 +24,7 @@ align-items: center; box-sizing: border-box; padding: 0px 8px; + border-radius: 5px; cursor: default; -webkit-app-region: no-drag; zoom: 1; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 35b374ece47..8b516b544a7 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -42,7 +42,7 @@ .monaco-workbench.windows .part.titlebar>.titlebar-container, .monaco-workbench.linux .part.titlebar>.titlebar-container { height: 30px; - line-height: 30px; + line-height: 22px; justify-content: left; } From ff975bc211f6546ae2da0a7e8c87d73fc080fa46 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 10:39:40 -0700 Subject: [PATCH 134/350] Update list of enabled APIs (#149838) We use `notebookWorkspaceEdit` even though it's not enforced --- extensions/ipynb/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 32802630b84..901f65e56e1 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -9,7 +9,8 @@ "vscode": "^1.57.0" }, "enabledApiProposals": [ - "notebookEditor" + "notebookEditor", + "notebookWorkspaceEdit" ], "activationEvents": [ "*" From 6bd7605ae151f75d4bf865f10a6a6fcab7d3c4e9 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 10:47:37 -0700 Subject: [PATCH 135/350] Enable strictNullChecks for implicit projects (#149844) Mistakenly enabled checkJS instead of strictNullChecks --- extensions/typescript-language-features/package.json | 4 ++-- .../typescript-language-features/src/utils/configuration.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index e5ca635d50c..14618b75330 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -686,7 +686,7 @@ }, "js/ts.implicitProjectConfig.checkJs": { "type": "boolean", - "default": true, + "default": false, "markdownDescription": "%configuration.implicitProjectConfig.checkJs%", "scope": "window" }, @@ -705,7 +705,7 @@ }, "js/ts.implicitProjectConfig.strictNullChecks": { "type": "boolean", - "default": false, + "default": true, "markdownDescription": "%configuration.implicitProjectConfig.strictNullChecks%", "scope": "window" }, diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts index 34355b5e3e5..2690f743848 100644 --- a/extensions/typescript-language-features/src/utils/configuration.ts +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -82,7 +82,7 @@ export class ImplicitProjectConfiguration { private static readCheckJs(configuration: vscode.WorkspaceConfiguration): boolean { return configuration.get('js/ts.implicitProjectConfig.checkJs') - ?? configuration.get('javascript.implicitProjectConfig.checkJs', true); + ?? configuration.get('javascript.implicitProjectConfig.checkJs', false); } private static readExperimentalDecorators(configuration: vscode.WorkspaceConfiguration): boolean { @@ -91,7 +91,7 @@ export class ImplicitProjectConfiguration { } private static readImplicitStrictNullChecks(configuration: vscode.WorkspaceConfiguration): boolean { - return configuration.get('js/ts.implicitProjectConfig.strictNullChecks', false); + return configuration.get('js/ts.implicitProjectConfig.strictNullChecks', true); } private static readImplicitStrictFunctionTypes(configuration: vscode.WorkspaceConfiguration): boolean { From 7cf37cbb25a35c7e05d9f907dc15ed950e94fb04 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 18 May 2022 10:53:18 -0700 Subject: [PATCH 136/350] add ExtensionVersion filter in assignment service (#149845) --- src/vs/platform/assignment/common/assignment.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/platform/assignment/common/assignment.ts b/src/vs/platform/assignment/common/assignment.ts index 7ca130fe9f3..9f9b72aea17 100644 --- a/src/vs/platform/assignment/common/assignment.ts +++ b/src/vs/platform/assignment/common/assignment.ts @@ -31,6 +31,7 @@ https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/A "X-VSCode-Build": "build", "X-MSEdge-ClientId": "clientid", "X-VSCode-ExtensionName": "extensionname", +"X-VSCode-ExtensionVersion": "extensionversion", "X-VSCode-TargetPopulation": "targetpopulation", "X-VSCode-Language": "language" */ @@ -65,6 +66,11 @@ export enum Filters { */ ExtensionName = 'X-VSCode-ExtensionName', + /** + * The version of the extension. + */ + ExtensionVersion = 'X-VSCode-ExtensionVersion', + /** * The language in use by VS Code */ @@ -97,6 +103,8 @@ export class AssignmentFilterProvider implements IExperimentationFilterProvider return platform.language; case Filters.ExtensionName: return 'vscode-core'; // always return vscode-core for exp service + case Filters.ExtensionVersion: + return '999999.0'; // always return a very large number for cross-extension experimentation case Filters.TargetPopulation: return this.targetPopulation; default: From 49f9aecd0aa2aef56324716f943bae65053f0302 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 18 May 2022 11:11:31 -0700 Subject: [PATCH 137/350] Fix terminal theme tests --- src/vs/platform/theme/test/common/testThemeService.ts | 2 +- .../terminal/test/browser/xterm/xtermTerminal.test.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 6655be6e360..a7911a49048 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -14,7 +14,7 @@ export class TestColorTheme implements IColorTheme { public readonly label = 'test'; constructor( - private colors: { [id: string]: string } = {}, + private colors: { [id: string]: string | undefined } = {}, public type = ColorScheme.DARK, public readonly semanticHighlighting = false ) { } diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index c9bbe8dedaf..eaec5127e56 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -16,7 +16,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewDescriptor, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Emitter } from 'vs/base/common/event'; -import { TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR, TERMINAL_SELECTION_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { WebglAddon } from 'xterm-addon-webgl'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; @@ -146,6 +146,7 @@ suite('XtermTerminal', () => { [TERMINAL_CURSOR_FOREGROUND_COLOR]: '#000300', [TERMINAL_CURSOR_BACKGROUND_COLOR]: '#000400', [TERMINAL_SELECTION_BACKGROUND_COLOR]: '#000500', + [TERMINAL_SELECTION_FOREGROUND_COLOR]: undefined, 'terminal.ansiBlack': '#010000', 'terminal.ansiRed': '#020000', 'terminal.ansiGreen': '#030000', @@ -170,6 +171,7 @@ suite('XtermTerminal', () => { cursor: '#000300', cursorAccent: '#000400', selection: '#000500', + selectionForeground: undefined, black: '#010000', green: '#030000', red: '#020000', @@ -193,6 +195,7 @@ suite('XtermTerminal', () => { [TERMINAL_CURSOR_FOREGROUND_COLOR]: '#00030f', [TERMINAL_CURSOR_BACKGROUND_COLOR]: '#00040f', [TERMINAL_SELECTION_BACKGROUND_COLOR]: '#00050f', + [TERMINAL_SELECTION_FOREGROUND_COLOR]: '#00060f', 'terminal.ansiBlack': '#01000f', 'terminal.ansiRed': '#02000f', 'terminal.ansiGreen': '#03000f', @@ -216,6 +219,7 @@ suite('XtermTerminal', () => { cursor: '#00030f', cursorAccent: '#00040f', selection: '#00050f', + selectionForeground: '#00060f', black: '#01000f', green: '#03000f', red: '#02000f', From 64e09597f108f58777167874c76600cd55ceb685 Mon Sep 17 00:00:00 2001 From: Roj Date: Wed, 18 May 2022 21:13:32 +0300 Subject: [PATCH 138/350] Handle multiline commit messages when creating PR (#149426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This makes the body of the pull request non-empty when the commit message consists more than one line. - The GitHub web client fills the body the same way, too, when you create a pull request. - Some commit messages consist more than one line, like this one, which is giving more detail about the commit, and can be used as a shortcut to not write the same text when opening a pull request and on GitHub. - Without these changes, the pull request title will be messed up if the commit message is similar to this. Co-authored-by: João Moreno --- extensions/github/src/pushErrorHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index 37f138caffb..5569d4fb480 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -102,13 +102,14 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: let title = `Update ${remoteName}`; const head = repository.state.HEAD?.name; + let body: string | undefined; + if (head) { const commit = await repository.getCommit(head); - title = commit.message.replace(/\n.*$/m, ''); + title = commit.message.split('\n')[0]; + body = commit.message.slice(title.length + 1).trim(); } - let body: string | undefined; - const templates = await findPullRequestTemplates(repository.rootUri); if (templates.length > 0) { templates.sort((a, b) => a.path.localeCompare(b.path)); From d346ca8c37a0aab9d4f7b735cfd5b9fc92e4996e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 May 2022 22:34:18 +0200 Subject: [PATCH 139/350] - separate deprecated and prerelease migration extensions in the control file - Merge deprecated and prerelease migration extensions - Support deprecated extensions with settings - Do not auto migrate deprecated extensions - Support disallowing install for deprecated extensions --- .../abstractExtensionManagementService.ts | 11 ++--- .../common/extensionGalleryService.ts | 44 +++++++++++++++++-- .../common/extensionManagement.ts | 17 +++++-- .../common/unsupportedExtensionsMigration.ts | 10 +++-- .../extensions/browser/extensionEditor.ts | 2 +- .../extensions/browser/extensionsActions.ts | 24 +++++----- .../extensions/browser/extensionsList.ts | 2 +- .../extensions/browser/extensionsWidgets.ts | 4 +- .../browser/extensionsWorkbenchService.ts | 12 +++-- .../contrib/extensions/common/extensions.ts | 4 +- .../extensionRecommendationsService.test.ts | 2 +- .../extensionsActions.test.ts | 4 +- .../electron-browser/extensionsViews.test.ts | 2 +- .../extensionsWorkbenchService.test.ts | 4 +- .../browser/webExtensionsScannerService.ts | 10 ++--- .../common/extensionManagementService.ts | 2 +- 16 files changed, 101 insertions(+), 53 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index a73af96a7c8..94e9a31abc7 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -10,7 +10,6 @@ import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { isBoolean } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { @@ -364,9 +363,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - const deprecated = report.deprecated ? report.deprecated[extension.identifier.id.toLowerCase()] : undefined; - if (deprecated && !isBoolean(deprecated)) { - throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is deprecated. Use '{1}' extension instead.", extension.identifier.id, deprecated.displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); + const deprecated = report.deprecated[extension.identifier.id.toLowerCase()]; + if (deprecated?.disallowInstall) { + const message = deprecated.extension ? nls.localize('unsupported extension with alternative', "Can't install '{0}' extension because it is deprecated. Use {1} extension instead.", extension.identifier.id, deprecated.extension.displayName) + : nls.localize('unsupported extension without alternative and no message', "Can't install '{0}' extension because it is deprecated.", extension.identifier.id); + throw new ExtensionManagementError(message, ExtensionManagementErrorCode.Deprecated); } if (!await this.canInstall(extension)) { @@ -581,7 +582,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return manifest; } catch (err) { this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest'); - return { malicious: [] }; + return { malicious: [], deprecated: {} }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index b75a76752b8..a6556211581 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri'; import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -537,7 +537,20 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller interface IRawExtensionsControlManifest { malicious: string[]; - deprecated?: IStringDictionary; + migrateToPreRelease?: IStringDictionary<{ + id: string; + displayName: string; + migrateStorage?: boolean; + engine?: string; + }>; + deprecated?: IStringDictionary; } abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { @@ -1138,7 +1151,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } if (!this.extensionsControlUrl) { - return { malicious: [] }; + return { malicious: [], deprecated: {} }; } const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None); @@ -1148,13 +1161,36 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const result = await asJson(context); const malicious: IExtensionIdentifier[] = []; + const deprecated: IStringDictionary = {}; if (result) { for (const id of result.malicious) { malicious.push({ id }); } + if (result.migrateToPreRelease) { + for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) { + if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) { + deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = { + disallowInstall: true, + extension: { + id: preReleaseExtensionInfo.id, + displayName: preReleaseExtensionInfo.displayName, + autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage }, + preRelease: true + } + }; + } + } + } + if (result.deprecated) { + for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) { + if (deprecationInfo) { + deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo; + } + } + } } - return { malicious, deprecated: result?.deprecated }; + return { malicious, deprecated }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 061de6580a9..d041814268b 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -282,9 +282,20 @@ export const enum StatisticType { Uninstall = 'uninstall' } +export interface IDeprecationInfo { + readonly disallowInstall?: boolean; + readonly extension?: { + readonly id: string; + readonly displayName: string; + readonly autoMigrate?: { readonly storage: boolean }; + readonly preRelease?: boolean; + }; + readonly settings?: readonly string[]; +} + export interface IExtensionsControlManifest { - malicious: IExtensionIdentifier[]; - deprecated?: IStringDictionary; + readonly malicious: IExtensionIdentifier[]; + readonly deprecated: IStringDictionary; } export const enum InstallOperation { @@ -349,7 +360,7 @@ export interface DidUninstallExtensionEvent { export enum ExtensionManagementErrorCode { Unsupported = 'Unsupported', - UnsupportedPreRelease = 'UnsupportedPreRelease', + Deprecated = 'Deprecated', Malicious = 'Malicious', Incompatible = 'Incompatible', IncompatiblePreRelease = 'IncompatiblePreRelease', diff --git a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts index 87eaf1447a6..57aadd8fc1d 100644 --- a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts +++ b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { isBoolean } from 'vs/base/common/types'; import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; @@ -26,10 +25,13 @@ export async function migrateUnsupportedExtensions(extensionManagementService: I } const installed = await extensionManagementService.getInstalled(ExtensionType.User); for (const [unsupportedExtensionId, deprecated] of Object.entries(extensionsControlManifest.deprecated)) { - if (isBoolean(deprecated)) { + if (!deprecated?.extension) { + continue; + } + const { id: preReleaseExtensionId, autoMigrate, preRelease } = deprecated.extension; + if (!autoMigrate) { continue; } - const { id: preReleaseExtensionId, migrateStorage, preRelease } = deprecated; const unsupportedExtension = installed.find(i => areSameExtensions(i.identifier, { id: unsupportedExtensionId })); // Unsupported Extension is not installed if (!unsupportedExtension) { @@ -57,7 +59,7 @@ export async function migrateUnsupportedExtensions(extensionManagementService: I await extensionEnablementService.disableExtension(preReleaseExtension.identifier); logService.info(`Disabled the pre-release extension '${preReleaseExtension.identifier.id}' because the unsupported extension '${unsupportedExtension.identifier.id}' is disabled`); } - if (migrateStorage) { + if (autoMigrate.storage) { extensionStorageService.addToMigrationList(getExtensionId(unsupportedExtension.manifest.publisher, unsupportedExtension.manifest.name), getExtensionId(preReleaseExtension.manifest.publisher, preReleaseExtension.manifest.name)); logService.info(`Added pre-release extension to the storage migration list`); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 2f1244e6ebd..b3b6109f4d3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -658,7 +658,7 @@ export class ExtensionEditor extends EditorPane { } }; reset(template.recommendation); - if (extension.deprecated || extension.state === ExtensionState.Installed) { + if (extension.deprecationInfo || extension.state === ExtensionState.Installed) { return; } updateRecommendationText(false); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d3b1573086e..e578b8359cb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -64,7 +64,6 @@ import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; -import { isBoolean } from 'vs/base/common/types'; import { fromNow } from 'vs/base/common/date'; export class PromptExtensionInstallFailureAction extends Action { @@ -104,7 +103,7 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.ReleaseVersionNotFound, ExtensionManagementErrorCode.UnsupportedPreRelease].includes(this.error.name)) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.ReleaseVersionNotFound, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.show(Severity.Info, getErrorMessage(this.error)); return; } @@ -276,11 +275,11 @@ export abstract class AbstractInstallAction extends ExtensionAction { return; } - if (this.extension.deprecated && isBoolean(this.extension.deprecated)) { + if (this.extension.deprecationInfo) { const result = await this.dialogService.confirm({ type: 'warning', - title: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), - message: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), + message: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), + detail: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), primaryButton: localize('install anyway', "Install Anyway"), }); if (!result.confirmed) { @@ -2172,14 +2171,15 @@ export class ExtensionStatusAction extends ExtensionAction { return; } - if (this.extension.deprecated) { - if (isBoolean(this.extension.deprecated)) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension is no longer being maintained and is deprecated.")) }, true); + if (this.extension.deprecationInfo) { + if (this.extension.deprecationInfo.extension) { + const link = `[${this.extension.deprecationInfo.extension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.extension.id]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension has been deprecated. Use {0} extension instead.", link)) }, true); + } else if (this.extension.deprecationInfo.settings) { + const link = `[${localize('settings', "settings")}](${URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' ')]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated and has become a native feature in VS Code. Configure these {0} instead.", link)) }, true); } else { - const link = `[${this.extension.deprecated.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecated.id]))}`)})`; - if (this.extension.state !== ExtensionState.Installed) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension has been deprecated. Use {0} instead.", link)) }, true); - } + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated tooltip', "This extension is no longer being maintained and is deprecated.")) }, true); } return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 4755c672581..9b5c1397ee4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -188,7 +188,7 @@ export class Renderer implements IPagedRenderer { const updateEnablement = async () => { let isDisabled = false; if (extension.state === ExtensionState.Uninstalled) { - isDisabled = !!extension.deprecated || !(await this.extensionsWorkbenchService.canInstall(extension)); + isDisabled = !!extension.deprecationInfo || !(await this.extensionsWorkbenchService.canInstall(extension)); } else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtensions = await this.extensionService.getExtensions(); const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index e4592383fe6..1760db32044 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -185,7 +185,7 @@ export class RecommendationWidget extends ExtensionWidget { render(): void { this.clear(); - if (!this.extension || this.extension.state === ExtensionState.Installed || this.extension.deprecated) { + if (!this.extension || this.extension.state === ExtensionState.Installed || this.extension.deprecationInfo) { return; } const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); @@ -549,7 +549,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.state === ExtensionState.Installed) { return undefined; } - if (extension.deprecated) { + if (extension.deprecationInfo) { return undefined; } const recommendation = this.extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7fda957ef46..ece35b058d1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, - IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions + IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionKey, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -198,7 +198,7 @@ export class Extension implements IExtension { } public isMalicious: boolean = false; - public deprecated: boolean | { id: string; displayName: string } = false; + public deprecationInfo: IDeprecationInfo | undefined; get installCount(): number | undefined { return this.gallery ? this.gallery.installCount : undefined; @@ -399,10 +399,8 @@ ${this.description} class Extensions extends Disposable { static updateExtensionFromControlManifest(extension: Extension, extensionsControlManifest: IExtensionsControlManifest): void { - const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); - extension.isMalicious = isMalicious; - const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; - extension.deprecated = deprecated ?? false; + extension.isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); + extension.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; } private readonly _onChange: Emitter<{ extension: Extension; operation?: InstallOperation } | undefined> = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); @@ -1166,7 +1164,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } - if (extension.deprecated && !isBoolean(extension.deprecated)) { + if (extension.deprecationInfo?.disallowInstall) { return false; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 23744cd4f9b..47be2a6e355 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -78,7 +78,7 @@ export interface IExtension { readonly local?: ILocalExtension; gallery?: IGalleryExtension; readonly isMalicious: boolean; - readonly deprecated: boolean | { id: string; displayName: string }; + readonly deprecationInfo?: IDeprecationInfo; } export const SERVICE_ID = 'extensionsWorkbenchService'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 54ddc028ea9..add1a477c3f 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -218,7 +218,7 @@ suite('ExtensionRecommendationsService Test', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IExtensionService, >{ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 7e5b0d2a156..93b1ba0e871 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -100,7 +100,7 @@ async function setupTest() { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; @@ -2424,7 +2424,7 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IE return local; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, }; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 39f0b2ca026..1212a0ed3b2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -98,7 +98,7 @@ suite('ExtensionsListView Tests', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IRemoteAgentService, RemoteAgentService); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 09723bab668..0c7d5816688 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -95,7 +95,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; @@ -1489,7 +1489,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return local; }, getTargetPlatform: async () => getTargetPlatform(platform, arch), - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, }; } }); diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index aa1eb99bc0e..1124b843b01 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -21,7 +21,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { localize } from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; -import { isBoolean, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ResourceMap } from 'vs/base/common/map'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; @@ -140,11 +140,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten this.logService.info(`Checking additional builtin extensions: Ignoring '${extension.id}' because it is reported to be malicious.`); continue; } - const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.id.toLowerCase()] : undefined; - if (deprecated && !isBoolean(deprecated)) { - const preReleaseExtensionId = deprecated.id; + const deprecationInfo = extensionsControlManifest.deprecated[extension.id.toLowerCase()]; + if (deprecationInfo?.extension?.autoMigrate) { + const preReleaseExtensionId = deprecationInfo.extension.id; this.logService.info(`Checking additional builtin extensions: '${extension.id}' is deprecated, instead using '${preReleaseExtensionId}'`); - result.push({ id: preReleaseExtensionId, preRelease: !!deprecated.preRelease }); + result.push({ id: preReleaseExtensionId, preRelease: !!extension.preRelease }); } else { result.push(extension); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index cc9bcdd1045..46c26ac1795 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -394,7 +394,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench if (this.extensionManagementServerService.webExtensionManagementServer) { return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } - return Promise.resolve({ malicious: [] }); + return Promise.resolve({ malicious: [], deprecated: {} }); } private getServer(extension: ILocalExtension): IExtensionManagementServer | null { From daf0d5e551a79a69b4482a2abaa9cbc83775e967 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 14:37:08 -0700 Subject: [PATCH 140/350] Add skipPaths option for markdown link validation (#149859) The new `markdown.experimental.validate.fileLinks.skipPaths` setting lets you specify a list of paths (as globs) that should not be validation This is useful since markdown is used in a range of environments, and sometimes you may need to link to paths that don't exist on disk but will exist on deployment A few other changes here: - Adds a quick fix that adds paths to `skipPaths` - Rename existing settings to use the `.enabled` prefix --- .../markdown-language-features/package.json | 22 +++-- .../package.nls.json | 7 +- .../src/extension.ts | 2 +- .../src/languageFeatures/diagnostics.ts | 88 +++++++++++++++++-- .../languageFeatures/documentLinkProvider.ts | 24 ++++- .../src/test/diagnostic.test.ts | 61 +++++++++++++ .../markdown-language-features/yarn.lock | 10 +++ 7 files changed, 194 insertions(+), 20 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 1af496d0fce..b1d4d1bc529 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -420,10 +420,10 @@ "description": "%configuration.markdown.experimental.validate.enabled.description%", "default": false }, - "markdown.experimental.validate.referenceLinks": { + "markdown.experimental.validate.referenceLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.referenceLinks.description%", + "markdownDescription": "%configuration.markdown.experimental.validate.referenceLinks.enabled.description%", "default": "warning", "enum": [ "ignore", @@ -431,10 +431,10 @@ "error" ] }, - "markdown.experimental.validate.headerLinks": { + "markdown.experimental.validate.headerLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.headerLinks.description%", + "markdownDescription": "%configuration.markdown.experimental.validate.headerLinks.enabled.description%", "default": "warning", "enum": [ "ignore", @@ -442,16 +442,24 @@ "error" ] }, - "markdown.experimental.validate.fileLinks": { + "markdown.experimental.validate.fileLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.description%", + "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.enabled.description%", "default": "warning", "enum": [ "ignore", "warning", "error" ] + }, + "markdown.experimental.validate.fileLinks.skipPaths": { + "type": "array", + "scope": "resource", + "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.skipPaths.description%", + "items": { + "type": "string" + } } } }, @@ -504,6 +512,7 @@ "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.1", "morphdom": "^2.6.1", + "picomatch": "^2.3.1", "vscode-languageserver-textdocument": "^1.0.4", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.3" @@ -512,6 +521,7 @@ "@types/dompurify": "^2.3.1", "@types/lodash.throttle": "^4.1.3", "@types/markdown-it": "12.2.3", + "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", "lodash.throttle": "^4.1.1" diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 2d9ba258003..b2958199b91 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -30,8 +30,9 @@ "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", - "configuration.markdown.experimental.validate.referenceLinks.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.headerLinks.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fileLinks.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", + "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", + "configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", + "configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", + "configuration.markdown.experimental.validate.fileLinks.skipPaths.description": "Configure glob patterns for links to treat as valid, even if they don't exist in the workspace. For example `/about` would make the link `[about](/about)` valid, while the glob `/assets/**/*.svg` would let you link to any `.svg` asset under the `assets` directory", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 4938c1004bf..f088f91391a 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -76,7 +76,7 @@ function registerMarkdownLanguageFeatures( vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier)), vscode.languages.registerDefinitionProvider(selector, new MdDefinitionProvider(referencesProvider)), MdPathCompletionProvider.register(selector, engine, linkProvider), - registerDiagnostics(engine, workspaceContents, linkProvider), + registerDiagnostics(selector, engine, workspaceContents, linkProvider, commandManager), registerDropIntoEditor(selector), registerFindFileReferences(commandManager, referencesProvider), ); diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index f53a83d670e..eb4c436afd5 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +import * as picomatch from 'picomatch'; import { MarkdownEngine } from '../markdownEngine'; import { TableOfContents } from '../tableOfContents'; import { Delayer } from '../util/async'; @@ -14,6 +15,7 @@ import { Limiter } from '../util/limiter'; import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider'; import { tryFindMdDocumentForLink } from './references'; +import { CommandManager } from '../commandManager'; const localize = nls.loadMessageBundle(); @@ -37,6 +39,7 @@ export interface DiagnosticOptions { readonly validateReferences: DiagnosticLevel; readonly validateOwnHeaders: DiagnosticLevel; readonly validateFilePaths: DiagnosticLevel; + readonly skipPaths: readonly string[]; } function toSeverity(level: DiagnosticLevel): vscode.DiagnosticSeverity | undefined { @@ -56,7 +59,13 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf super(); this._register(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('markdown.experimental.validate.enabled')) { + if ( + e.affectsConfiguration('markdown.experimental.validate.enabled') + || e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled') + || e.affectsConfiguration('markdown.experimental.validate.headerLinks.enabled') + || e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled') + || e.affectsConfiguration('markdown.experimental.validate.fileLinks.skipPaths') + ) { this._onDidChange.fire(); } })); @@ -66,9 +75,10 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf const config = vscode.workspace.getConfiguration('markdown', resource); return { enabled: config.get('experimental.validate.enabled', false), - validateReferences: config.get('experimental.validate.referenceLinks', DiagnosticLevel.ignore), - validateOwnHeaders: config.get('experimental.validate.headerLinks', DiagnosticLevel.ignore), - validateFilePaths: config.get('experimental.validate.fileLinks', DiagnosticLevel.ignore), + validateReferences: config.get('experimental.validate.referenceLinks.enabled', DiagnosticLevel.ignore), + validateOwnHeaders: config.get('experimental.validate.headerLinks.enabled', DiagnosticLevel.ignore), + validateFilePaths: config.get('experimental.validate.fileLinks.enabled', DiagnosticLevel.ignore), + skipPaths: config.get('experimental.validate.fileLinks.skipPaths', []), }; } } @@ -206,6 +216,16 @@ class LinkWatcher extends Disposable { } } +class FileDoesNotExistDiagnostic extends vscode.Diagnostic { + + public readonly path: string; + + constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, path: string) { + super(range, message, severity); + this.path = path; + } +} + export class DiagnosticManager extends Disposable { private readonly collection: vscode.DiagnosticCollection; @@ -459,9 +479,11 @@ export class DiagnosticComputer { } if (!hrefDoc && !await this.workspaceContents.pathExists(path)) { - const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.toString(true)); + const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath); for (const link of links) { - diagnostics.push(new vscode.Diagnostic(link.source.hrefRange, msg, severity)); + if (!options.skipPaths.some(glob => picomatch.isMatch(link.source.pathText, glob))) { + diagnostics.push(new FileDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText)); + } } } else if (hrefDoc) { // Validate each of the links to headers in the file @@ -482,12 +504,64 @@ export class DiagnosticComputer { } } +class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { + + private static readonly _addToSkipPathsCommandId = '_markdown.addToSkipPaths'; + + private static readonly metadata: vscode.CodeActionProviderMetadata = { + providedCodeActionKinds: [ + vscode.CodeActionKind.QuickFix + ], + }; + + public static register(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable { + const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToSkipPathsQuickFixProvider(), AddToSkipPathsQuickFixProvider.metadata); + const commandReg = commandManager.register({ + id: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + execute(resource: vscode.Uri, path: string) { + const settingId = 'experimental.validate.fileLinks.skipPaths'; + const config = vscode.workspace.getConfiguration('markdown', resource); + const paths = new Set(config.get(settingId, [])); + paths.add(path); + config.update(settingId, [...paths], vscode.ConfigurationTarget.WorkspaceFolder); + } + }); + return vscode.Disposable.from(reg, commandReg); + } + + provideCodeActions(document: vscode.TextDocument, _range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, _token: vscode.CancellationToken): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { + const fixes: vscode.CodeAction[] = []; + + for (const diagnostic of context.diagnostics) { + if (diagnostic instanceof FileDoesNotExistDiagnostic) { + const fix = new vscode.CodeAction( + localize('skipPathsQuickFix.title', "Add '{0}' to paths that skip link validation.", diagnostic.path), + vscode.CodeActionKind.QuickFix); + + fix.command = { + command: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + title: '', + arguments: [document.uri, diagnostic.path] + }; + fixes.push(fix); + } + } + + return fixes; + } +} + export function register( + selector: vscode.DocumentSelector, engine: MarkdownEngine, workspaceContents: MdWorkspaceContents, linkProvider: MdLinkProvider, + commandManager: CommandManager, ): vscode.Disposable { const configuration = new VSCodeDiagnosticConfiguration(); const manager = new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration); - return vscode.Disposable.from(configuration, manager); + return vscode.Disposable.from( + configuration, + manager, + AddToSkipPathsQuickFixProvider.register(selector, commandManager)); } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts index 2a239a04491..e58094d7b7f 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts @@ -93,7 +93,16 @@ function getWorkspaceFolder(document: SkinnyTextDocument) { } export interface MdLinkSource { + /** + * The original text of the link destination in code. + */ readonly text: string; + + /** + * The original text of just the link's path in code. + */ + readonly pathText: string; + readonly resource: vscode.Uri; readonly hrefRange: vscode.Range; readonly fragmentRange: vscode.Range | undefined; @@ -138,7 +147,7 @@ function extractDocumentLink( text: link, resource: document.uri, hrefRange: new vscode.Range(linkStart, linkEnd), - fragmentRange: getFragmentRange(link, linkStart, linkEnd), + ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), } }; } catch { @@ -154,6 +163,14 @@ function getFragmentRange(text: string, start: vscode.Position, end: vscode.Posi return new vscode.Range(start.translate({ characterDelta: index + 1 }), end); } +function getLinkSourceFragmentInfo(document: SkinnyTextDocument, link: string, linkStart: vscode.Position, linkEnd: vscode.Position): { fragmentRange: vscode.Range | undefined; pathText: string } { + const fragmentRange = getFragmentRange(link, linkStart, linkEnd); + return { + pathText: document.getText(new vscode.Range(linkStart, fragmentRange ? fragmentRange.start.translate(0, -1) : linkEnd)), + fragmentRange, + }; +} + const angleBracketLinkRe = /^<(.*)>$/; /** @@ -314,7 +331,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { text: link, resource: document.uri, hrefRange: new vscode.Range(linkStart, linkEnd), - fragmentRange: getFragmentRange(link, linkStart, linkEnd), + ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), } }; } @@ -350,6 +367,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { kind: 'link', source: { text: reference, + pathText: reference, resource: document.uri, hrefRange, fragmentRange: undefined, @@ -402,7 +420,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { text: link, resource: document.uri, hrefRange, - fragmentRange: getFragmentRange(link, linkStart, linkEnd), + ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), }, ref: { text: reference, range: refRange }, href: target, diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index a9f54978b7d..5d99d30bb52 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -26,6 +26,7 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, + skipPaths: [], }, noopToken) ).diagnostics; } @@ -43,6 +44,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { constructor( private readonly enabled: boolean = true, + private readonly skipPaths: string[] = [], ) { } getOptions(_resource: vscode.Uri): DiagnosticOptions { @@ -52,6 +54,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.ignore, validateOwnHeaders: DiagnosticLevel.ignore, validateReferences: DiagnosticLevel.ignore, + skipPaths: this.skipPaths, }; } return { @@ -59,6 +62,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, + skipPaths: this.skipPaths, }; } } @@ -179,4 +183,61 @@ suite('markdown: Diagnostics', () => { const diagnostics = await getComputedDiagnostics(doc1, new InMemoryWorkspaceMarkdownDocuments([doc1])); assert.deepStrictEqual(diagnostics.length, 0); }); + + test('Should allow ignoring invalid file link using glob', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `[text](/no-such-file)`, + `![img](/no-such-file)`, + `[text]: /no-such-file`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('skipPaths should allow skipping non-existent file', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `[text](/no-such-file#header)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('skipPaths should not consider link fragment', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `[text](/no-such-file#header)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('skipPaths should support globs', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](/images/aaa.png)`, + `![i](/images/sub/bbb.png)`, + `![i](/images/sub/sub2/ccc.png)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/images/**/*.png'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('skipPaths should resolve relative to file', async () => { + const doc1 = new InMemoryDocument(workspacePath('sub', 'doc1.md'), joinLines( + `![i](images/aaa.png)`, + `![i](images/sub/bbb.png)`, + `![i](images/sub/sub2/ccc.png)`, + `![i](/images/sub/sub2/ccc.png)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['images/**/*.png'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); }); diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5268b01f46c..eaa76d9abbf 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -39,6 +39,11 @@ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== +"@types/picomatch@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@types/picomatch/-/picomatch-2.3.0.tgz#75db5e75a713c5a83d5b76780c3da84a82806003" + integrity sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g== + "@types/trusted-types@*": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -117,6 +122,11 @@ morphdom@^2.6.1: resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e" integrity sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" From a49e21c63b6e40a33206df55d7bb40beaee2f406 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 18 May 2022 15:37:22 -0700 Subject: [PATCH 141/350] Fix notebook output double spacing (#149842) Fixes #149563 --- extensions/notebook-renderers/src/textHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index af53635710d..9daafb721f8 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -25,7 +25,7 @@ function generateViewMoreElement(outputId: string) { } export function truncatedArrayOfString(id: string, outputs: string[], linesLimit: number, container: HTMLElement) { - let buffer = outputs.join('\n').split(/\r|\n|\r\n/g); + let buffer = outputs.join('\n').split(/\r\n|\r|\n/g); let lineCount = buffer.length; if (lineCount < linesLimit) { From 27903c28aa767fec440eeed72826af79152bb3b4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 16:01:28 -0700 Subject: [PATCH 142/350] Remove unfinished test (#149864) Mistakenly checked this in --- .../src/test/diagnostic.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index 5d99d30bb52..e58dabd983e 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -227,17 +227,4 @@ suite('markdown: Diagnostics', () => { const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); assert.deepStrictEqual(diagnostics.length, 0); }); - - test('skipPaths should resolve relative to file', async () => { - const doc1 = new InMemoryDocument(workspacePath('sub', 'doc1.md'), joinLines( - `![i](images/aaa.png)`, - `![i](images/sub/bbb.png)`, - `![i](images/sub/sub2/ccc.png)`, - `![i](/images/sub/sub2/ccc.png)`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['images/**/*.png'])); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); }); From 0d55d5017316ebe7c799b86cea03fc9d26687b7f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 16:14:32 -0700 Subject: [PATCH 143/350] Expose outputItem in notebook markdown eve (#149870) For #121256 This change adds the current `ouputItem` to the notebook markdown renderer's environment Renders that extend our markdown renderer can use this to access output item metadata for example --- extensions/markdown-language-features/notebook/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 3fdb86bd8d7..079ed928f3f 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -207,7 +207,9 @@ export const activate: ActivationFunction = (ctx) => { previewNode.classList.remove('emptyMarkdownCell'); const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); - const unsanitizedRenderedMarkdown = markdownIt.render(markdownText); + const unsanitizedRenderedMarkdown = markdownIt.render(markdownText, { + outputItem: outputInfo, + }); previewNode.innerHTML = (ctx.workspace.isTrusted ? unsanitizedRenderedMarkdown : DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions)) as string; From 73b2655e9b2d21388ecbd8bb22ed599bd1c0e5a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 19 May 2022 01:30:32 +0200 Subject: [PATCH 144/350] Lots of bug fixing & minor improvements. --- src/vs/base/common/arrays.ts | 30 +- .../mergeEditor/browser/editorGutter.ts | 141 ++++++ .../mergeEditor/browser/editorGutterWidget.ts | 100 ----- .../mergeEditor/browser/mergeEditor.ts | 401 ++++++++++-------- .../mergeEditor/browser/mergeEditorModel.ts | 255 +++++++---- .../contrib/mergeEditor/browser/model.ts | 235 ++++------ .../contrib/mergeEditor/browser/utils.ts | 169 ++++++++ 7 files changed, 818 insertions(+), 513 deletions(-) create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts delete mode 100644 src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts create mode 100644 src/vs/workbench/contrib/mergeEditor/browser/utils.ts diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index e876d9d4304..826d3558c35 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -640,12 +640,38 @@ function getActualStartIndex(array: T[], start: number): number { return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length); } +/** + * When comparing two values, + * a negative number indicates that the first value is less than the second, + * a positive number indicates that the first value is greater than the second, + * and zero indicates that neither is the case. +*/ +export type CompareResult = number; + +export namespace CompareResult { + export function isLessThan(result: CompareResult): boolean { + return result < 0; + } + + export function isGreaterThan(result: CompareResult): boolean { + return result > 0; + } + + export function isNeitherLessOrGreaterThan(result: CompareResult): boolean { + return result === 0; + } + + export const greaterThan = 1; + export const lessThan = -1; + export const neitherLessOrGreaterThan = 0; +} + /** * A comparator `c` defines a total order `<=` on `T` as following: * `c(a, b) <= 0` iff `a` <= `b`. * We also have `c(a, b) == 0` iff `c(b, a) == 0`. */ -export type Comparator = (a: T, b: T) => number; +export type Comparator = (a: T, b: T) => CompareResult; export function compareBy(selector: (item: TItem) => TCompareBy, comparator: Comparator): Comparator { return (a, b) => comparator(selector(a), selector(b)); @@ -706,7 +732,7 @@ export class ArrayQueue { /** * Constructs a queue that is backed by the given array. Runtime is O(1). */ - constructor(private readonly items: T[]) { } + constructor(private readonly items: readonly T[]) { } get length(): number { return this.lastIdx - this.firstIdx + 1; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts new file mode 100644 index 00000000000..c4390489209 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts @@ -0,0 +1,141 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; + +export class EditorGutter< + T extends IGutterItemInfo = IGutterItemInfo + > extends Disposable { + private readonly scrollTop = observableFromEvent( + this._editor.onDidScrollChange, + (e) => this._editor.getScrollTop() + ); + private readonly modelAttached = observableFromEvent( + this._editor.onDidChangeModel, + (e) => this._editor.hasModel() + ); + + private readonly viewZoneChanges = new ObservableValue(0, 'counter'); + + constructor( + private readonly _editor: CodeEditorWidget, + private readonly _domNode: HTMLElement, + private readonly itemProvider: IGutterItemProvider + ) { + super(); + this._domNode.className = 'gutter'; + this._register(autorun((reader) => this.render(reader), 'Render')); + + this._editor.onDidChangeViewZones(e => { + this.viewZoneChanges.set(this.viewZoneChanges.get() + 1, undefined); + }); + } + + private readonly views = new Map(); + + private render(reader: IReader): void { + if (!this.modelAttached.read(reader)) { + return; + } + this.viewZoneChanges.read(reader); + const scrollTop = this.scrollTop.read(reader); + + const visibleRanges = this._editor.getVisibleRanges(); + const unusedIds = new Set(this.views.keys()); + + if (visibleRanges.length > 0) { + const visibleRange = visibleRanges[0]; + + const visibleRange2 = new LineRange( + visibleRange.startLineNumber, + visibleRange.endLineNumber - visibleRange.startLineNumber + ); + + const gutterItems = this.itemProvider.getIntersectingGutterItems( + visibleRange2, + reader + ); + + const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); + + for (const gutterItem of gutterItems) { + if (!gutterItem.range.touches(visibleRange2)) { + continue; + } + + unusedIds.delete(gutterItem.id); + let view = this.views.get(gutterItem.id); + if (!view) { + const viewDomNode = document.createElement('div'); + viewDomNode.className = 'gutter-item'; + this._domNode.appendChild(viewDomNode); + const itemView = this.itemProvider.createView( + gutterItem, + viewDomNode + ); + view = new ManagedGutterItemView(itemView, viewDomNode); + this.views.set(gutterItem.id, view); + } else { + view.gutterItemView.update(gutterItem); + } + + const top = + this._editor.getTopForLineNumber(gutterItem.range.startLineNumber - 1) - + scrollTop + lineHeight; + + // +1 line and -lineHeight makes the height to cover view zones at the end of the range. + const bottom = + this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive + 1) - + scrollTop - lineHeight; + + const height = bottom - top; + + view.domNode.style.top = `${top}px`; + view.domNode.style.height = `${height}px`; + + view.gutterItemView.layout(top, height, 0, -1); + } + } + + for (const id of unusedIds) { + const view = this.views.get(id)!; + view.gutterItemView.dispose(); + this._domNode.removeChild(view.domNode); + this.views.delete(id); + } + } +} + +class ManagedGutterItemView { + constructor( + public readonly gutterItemView: IGutterItemView, + public readonly domNode: HTMLDivElement + ) { } +} + +export interface IGutterItemProvider { + getIntersectingGutterItems(range: LineRange, reader: IReader): TItem[]; + + createView(item: TItem, target: HTMLElement): IGutterItemView; +} + +export interface IGutterItemInfo { + id: string; + range: LineRange; + + // To accommodate view zones: + offsetInPx: number; + additionalHeightInPx: number; +} + +export interface IGutterItemView extends IDisposable { + update(item: T): void; + layout(top: number, height: number, viewTop: number, viewHeight: number): void; +} + diff --git a/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts b/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts deleted file mode 100644 index 9b3954eed48..00000000000 --- a/src/vs/workbench/contrib/mergeEditor/browser/editorGutterWidget.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; - -export class EditorGutterWidget { - constructor( - private readonly _editor: ICodeEditor, - private readonly _domNode: HTMLElement, - private readonly itemProvider: IGutterItemProvider, - ) { - this._domNode.className = 'gutter'; - - this._editor.onDidScrollChange(() => { - this.render(); - }); - } - - private readonly views = new Map(); - - private render(): void { - const visibleRange = this._editor.getVisibleRanges()[0]; - const visibleRange2 = new LineRange( - visibleRange.startLineNumber, - visibleRange.endLineNumber - visibleRange.startLineNumber - ); - - const gutterItems = this.itemProvider.getIntersectingGutterItems(visibleRange2); - - const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); - - const unusedIds = new Set(this.views.keys()); - for (const gutterItem of gutterItems) { - if (!gutterItem.range.touches(visibleRange2)) { - continue; - } - - unusedIds.delete(gutterItem.id); - let view = this.views.get(gutterItem.id); - if (!view) { - const viewDomNode = document.createElement('div'); - viewDomNode.className = 'gutter-item'; - this._domNode.appendChild(viewDomNode); - const itemView = this.itemProvider.createView(gutterItem, viewDomNode); - view = new ManagedGutterItemView(itemView, viewDomNode); - this.views.set(gutterItem.id, view); - } - - const scrollTop = this._editor.getScrollTop(); - const top = this._editor.getTopForLineNumber(gutterItem.range.startLineNumber) - scrollTop; - const height = lineHeight * gutterItem.range.lineCount; - - view.domNode.style.top = `${top}px`; - view.domNode.style.height = `${height}px`; - - view.gutterItemView.layout(top, height, 0, -1); - } - - for (const id of unusedIds) { - const view = this.views.get(id)!; - view.gutterItemView.dispose(); - this._domNode.removeChild(view.domNode); - this.views.delete(id); - } - } -} - -class ManagedGutterItemView { - constructor( - public readonly gutterItemView: IGutterItemView, - public readonly domNode: HTMLDivElement - ) { } -} - -export interface IGutterItemProvider { - // onDidChange - getIntersectingGutterItems(range: LineRange): TItem[]; - - createView(item: TItem, target: HTMLElement): IGutterItemView; -} - -export interface IGutterItemInfo { - id: string; - range: LineRange; - - // To accommodate view zones: - offsetInPx: number; - additionalHeightInPx: number; -} - -export interface IGutterItemView extends IDisposable { - update(item: T): void; - layout(top: number, height: number, viewTop: number, viewHeight: number): void; -} - diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 3cb4562da9e..f46dce24ff1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -15,13 +15,17 @@ import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { deepClone } from 'vs/base/common/objects'; import { noBreakWhitespace } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { localize } from 'vs/nls'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -36,11 +40,12 @@ import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { autorun, derivedObservable, IObservable, ITransaction } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { ModifiedBaseRangeState, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; +import { applyObservableDecorations, n, ReentrancyBarrier, setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; -import { EditorGutterWidget, IGutterItemInfo, IGutterItemView } from './editorGutterWidget'; +import { EditorGutter, IGutterItemInfo, IGutterItemView } from './editorGutter'; export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); export const ctxUsesColumnLayout = new RawContextKey('mergeEditorUsesColumnLayout', false); @@ -51,11 +56,11 @@ export class MergeEditor extends EditorPane { private readonly _sessionDisposables = new DisposableStore(); - private _grid!: Grid; + private _grid!: Grid; - private readonly input1View = this.instantiation.createInstance(CodeEditorView, { readonly: true }); - private readonly input2View = this.instantiation.createInstance(CodeEditorView, { readonly: true }); - private readonly inputResultView = this.instantiation.createInstance(CodeEditorView, { readonly: false }); + private readonly input1View = this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: true }); + private readonly input2View = this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: true }); + private readonly inputResultView = this.instantiation.createInstance(ResultCodeEditorView, { readonly: false }); private readonly _ctxIsMergeEditor: IContextKey; private readonly _ctxUsesColumnLayout: IContextKey; @@ -68,6 +73,7 @@ export class MergeEditor extends EditorPane { @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, @IThemeService themeService: IThemeService, + @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { super(MergeEditor.ID, telemetryService, themeService, storageService); @@ -75,6 +81,7 @@ export class MergeEditor extends EditorPane { this._ctxUsesColumnLayout = ctxUsesColumnLayout.bindTo(_contextKeyService); const reentrancyBarrier = new ReentrancyBarrier(); + this._store.add(this.input1View.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { @@ -100,6 +107,7 @@ export class MergeEditor extends EditorPane { } })); + // TODO@jrieken make this proper: add menu id and allow extensions to contribute const toolbarMenu = this._menuService.createMenu(MenuId.MergeToolbar, this._contextKeyService); const toolbarMenuDisposables = new DisposableStore(); @@ -128,6 +136,11 @@ export class MergeEditor extends EditorPane { super.dispose(); } + // TODO use this method & make it private + getEditorOptions(resource: URI): IEditorConfiguration { + return deepClone(this.textResourceConfigurationService.getValue(resource)); + } + protected createEditor(parent: HTMLElement): void { parent.classList.add('merge-editor'); @@ -138,14 +151,14 @@ export class MergeEditor extends EditorPane { { size: 38, groups: [{ - data: this.input1View + data: this.input1View.view }, { - data: this.input2View + data: this.input2View.view }] }, { size: 62, - data: this.inputResultView + data: this.inputResultView.view }, ] }, { @@ -170,113 +183,48 @@ export class MergeEditor extends EditorPane { this._sessionDisposables.clear(); const model = await input.resolve(); - this.input1View.setModel(model.input1, localize('yours', 'Yours'), model.input1Detail, model.input1Description); - this.input2View.setModel(model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input1Description); - this.inputResultView.setModel(model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); + this.input1View.setModel(model, model.input1, localize('yours', 'Yours'), model.input1Detail, model.input1Description); + this.input2View.setModel(model, model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input1Description); + this.inputResultView.setModel(model, model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); - let input1Decorations = new Array(); - let input2Decorations = new Array(); + // TODO: Update editor options! + const input1ViewZoneIds: string[] = []; + const input2ViewZoneIds: string[] = []; for (const m of model.modifiedBaseRanges) { - if (!m.input1Range.isEmpty) { - input1Decorations.push({ - range: new Range(m.input1Range.startLineNumber, 1, m.input1Range.endLineNumberExclusive - 1, 1), - options: { - isWholeLine: true, - className: 'merge-accept-foo', - description: 'foo2' - } - }); - } - - if (!m.input2Range.isEmpty) { - input2Decorations.push({ - range: new Range(m.input2Range.startLineNumber, 1, m.input2Range.endLineNumberExclusive - 1, 1), - options: { - isWholeLine: true, - className: 'merge-accept-foo', - description: 'foo2' - } - }); - } - const max = Math.max(m.input1Range.lineCount, m.input2Range.lineCount, 1); this.input1View.editor.changeViewZones(a => { - a.addZone({ + input1ViewZoneIds.push(a.addZone({ afterLineNumber: m.input1Range.endLineNumberExclusive - 1, heightInLines: max - m.input1Range.lineCount, domNode: $('div.diagonal-fill'), - }); + })); }); this.input2View.editor.changeViewZones(a => { - a.addZone({ + input2ViewZoneIds.push(a.addZone({ afterLineNumber: m.input2Range.endLineNumberExclusive - 1, heightInLines: max - m.input2Range.lineCount, domNode: $('div.diagonal-fill'), - }); + })); }); } - this.input1View.editor.deltaDecorations([], input1Decorations); - this.input2View.editor.deltaDecorations([], input2Decorations); - - new EditorGutterWidget(this.input1View.editor, this.input1View._gutterDiv, { - getIntersectingGutterItems: (range) => - model.modifiedBaseRanges - .filter((r) => r.input1Diffs.length > 0) - .map((baseRange, idx) => ({ - id: idx.toString(), - additionalHeightInPx: 0, - offsetInPx: 0, - range: baseRange.input1Range, - toggleState: derivedObservable( - 'toggle', - (reader) => model.getState(baseRange).read(reader)?.input1 - ), - setState(value, tx) { - model.setState( - baseRange, - ( - model.getState(baseRange).get() || - new ModifiedBaseRangeState(false, false, false) - ).withInput1(value), - tx - ); - }, - })), - createView: (item, target) => new ButtonView(item, target), + this._sessionDisposables.add({ + dispose: () => { + this.input1View.editor.changeViewZones(a => { + for (const zone of input1ViewZoneIds) { + a.removeZone(zone); + } + }); + this.input2View.editor.changeViewZones(a => { + for (const zone of input2ViewZoneIds) { + a.removeZone(zone); + } + }); + } }); - - new EditorGutterWidget(this.input2View.editor, this.input2View._gutterDiv, { - getIntersectingGutterItems: (range) => - model.modifiedBaseRanges - .filter((r) => r.input2Diffs.length > 0) - .map((baseRange, idx) => ({ - id: idx.toString(), - additionalHeightInPx: 0, - offsetInPx: 0, - range: baseRange.input2Range, - baseRange, - toggleState: derivedObservable( - 'toggle', - (reader) => model.getState(baseRange).read(reader)?.input2 - ), - setState(value, tx) { - model.setState( - baseRange, - ( - model.getState(baseRange).get() || - new ModifiedBaseRangeState(false, false, false) - ).withInput2(value), - tx - ); - }, - })), - createView: (item, target) => new ButtonView(item, target), - }); - } protected override setEditorVisible(visible: boolean): void { @@ -307,23 +255,170 @@ export class MergeEditor extends EditorPane { toggleLayout(): void { if (!this._usesColumnLayout) { - this._grid.moveView(this.inputResultView, Sizing.Distribute, this.input1View, Direction.Right); + this._grid.moveView(this.inputResultView.view, Sizing.Distribute, this.input1View.view, Direction.Right); } else { - this._grid.moveView(this.inputResultView, this._grid.height * .62, this.input1View, Direction.Down); - this._grid.moveView(this.input2View, Sizing.Distribute, this.input1View, Direction.Right); + this._grid.moveView(this.inputResultView.view, this._grid.height * .62, this.input1View.view, Direction.Down); + this._grid.moveView(this.input2View.view, Sizing.Distribute, this.input1View.view, Direction.Right); } this._usesColumnLayout = !this._usesColumnLayout; this._ctxUsesColumnLayout.set(this._usesColumnLayout); } } -interface ButtonViewData extends IGutterItemInfo { +interface ICodeEditorViewOptions { + readonly: boolean; +} + + +abstract class CodeEditorView extends Disposable { + private readonly _model = new ObservableValue(undefined, 'model'); + protected readonly model: IObservable = this._model; + + protected readonly htmlElements = n('div.code-view', [ + n('div.title', { $: 'title' }), + n('div.container', [ + n('div.gutter', { $: 'gutterDiv' }), + n('div', { $: 'editor' }), + ]), + ]); + + private readonly _onDidViewChange = new Emitter(); + + public readonly view: IView = { + element: this.htmlElements.root, + minimumWidth: 10, + maximumWidth: Number.MAX_SAFE_INTEGER, + minimumHeight: 10, + maximumHeight: Number.MAX_SAFE_INTEGER, + onDidChange: this._onDidViewChange.event, + + layout: (width: number, height: number, top: number, left: number) => { + setStyle(this.htmlElements.root, { width, height, top, left }); + this.editor.layout({ + width: width - this.htmlElements.gutterDiv.clientWidth, + height: height - this.htmlElements.title.clientHeight, + }); + } + + // preferredWidth?: number | undefined; + // preferredHeight?: number | undefined; + // priority?: LayoutPriority | undefined; + // snap?: boolean | undefined; + }; + + private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true }); + private readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true }); + + public readonly editor = this.instantiationService.createInstance( + CodeEditorWidget, + this.htmlElements.editor, + { + minimap: { enabled: false }, + readOnly: this._options.readonly, + glyphMargin: false, + lineNumbersMinChars: 2, + }, + { contributions: [] } + ); + + constructor( + private readonly _options: ICodeEditorViewOptions, + @IInstantiationService + private readonly instantiationService: IInstantiationService + ) { + super(); + } + + public setModel( + model: MergeEditorModel, + textModel: ITextModel, + title: string, + description: string | undefined, + detail: string | undefined + ): void { + this.editor.setModel(textModel); + this._title.setLabel(title, description); + this._detail.setLabel('', detail); + + this._model.set(model, undefined); + } +} + +class InputCodeEditorView extends CodeEditorView { + private readonly decorations = derivedObservable('decorations', reader => { + const model = this.model.read(reader); + if (!model) { + return []; + } + const result = new Array(); + for (const m of model.modifiedBaseRanges) { + const range = m.getInputRange(this.inputNumber); + if (!range.isEmpty) { + result.push({ + range: new Range(range.startLineNumber, 1, range.endLineNumberExclusive - 1, 1), + options: { + isWholeLine: true, + className: 'merge-accept-foo', + description: 'foo2' + } + }); + } + } + return result; + }); + + constructor( + public readonly inputNumber: 1 | 2, + options: ICodeEditorViewOptions, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(options, instantiationService); + + this._register(applyObservableDecorations(this.editor, this.decorations)); + + this._register( + new EditorGutter(this.editor, this.htmlElements.gutterDiv, { + getIntersectingGutterItems: (range, reader) => { + const model = this.model.read(reader); + if (!model) { return []; } + return model.modifiedBaseRanges + .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + additionalHeightInPx: 0, + offsetInPx: 0, + range: baseRange.getInputRange(this.inputNumber), + toggleState: derivedObservable('toggle', (reader) => + model + .getState(baseRange) + .read(reader) + .getInput(this.inputNumber) + ), + setState: (value, tx) => + model.setState( + baseRange, + model + .getState(baseRange) + .get() + .withInputValue(this.inputNumber, value), + tx + ), + })); + }, + createView: (item, target) => + new MergeConflictGutterItemView(item, target), + }) + ); + } +} + +interface MergeConflictData extends IGutterItemInfo { toggleState: IObservable; setState(value: boolean, tx: ITransaction | undefined): void; } -class ButtonView extends Disposable implements IGutterItemView { - constructor(item: ButtonViewData, target: HTMLElement) { +class MergeConflictGutterItemView extends Disposable implements IGutterItemView { + constructor(private item: MergeConflictData, target: HTMLElement) { super(); target.classList.add('merge-accept-gutter-marker'); @@ -334,93 +429,57 @@ class ButtonView extends Disposable implements IGutterItemView { this._register( autorun((reader) => { - const value = item.toggleState.read(reader); + const value = this.item.toggleState.read(reader); checkBox.checked = value === true; }, 'Update Toggle State') ); this._register(checkBox.onChange(() => { - item.setState(checkBox.checked, undefined); + this.item.setState(checkBox.checked, undefined); })); target.appendChild($('div.background', {}, noBreakWhitespace)); target.appendChild($('div.checkbox', {}, checkBox.domNode)); } + layout(top: number, height: number, viewTop: number, viewHeight: number): void { } - update(baseRange: ButtonViewData): void { + update(baseRange: MergeConflictData): void { + this.item = baseRange; } } -interface ICodeEditorViewOptions { - readonly: boolean; -} - -class CodeEditorView implements IView { - - // preferredWidth?: number | undefined; - // preferredHeight?: number | undefined; - - element: HTMLElement; - private readonly _titleElement: HTMLElement; - private readonly _editorElement: HTMLElement; - public readonly _gutterDiv: HTMLElement; - - minimumWidth: number = 10; - maximumWidth: number = Number.MAX_SAFE_INTEGER; - minimumHeight: number = 10; - maximumHeight: number = Number.MAX_SAFE_INTEGER; - // priority?: LayoutPriority | undefined; - // snap?: boolean | undefined; - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; - - private readonly _title: IconLabel; - private readonly _detail: IconLabel; - - public readonly editor: CodeEditorWidget; - // private readonly gutter: EditorGutterWidget; - +class ResultCodeEditorView extends CodeEditorView { + private readonly decorations = derivedObservable('decorations', reader => { + const model = this.model.read(reader); + if (!model) { + return []; + } + const result = new Array(); + for (const m of model.resultDiffs.read(reader)) { + const range = m.modifiedRange; + if (!range.isEmpty) { + result.push({ + range: new Range(range.startLineNumber, 1, range.endLineNumberExclusive - 1, 1), + options: { + isWholeLine: true, + className: 'merge-accept-foo', + description: 'foo2' + } + }); + } + } + return result; + }); constructor( - private readonly _options: ICodeEditorViewOptions, - @IInstantiationService private readonly instantiationService: IInstantiationService + options: ICodeEditorViewOptions, + @IInstantiationService instantiationService: IInstantiationService ) { - this.element = $( - 'div.code-view', - {}, - this._titleElement = $('div.title'), - $('div.container', {}, - this._gutterDiv = $('div.gutter'), - this._editorElement = $('div'), - ), - ); + super(options, instantiationService); - this.editor = this.instantiationService.createInstance( - CodeEditorWidget, - this._editorElement, - { minimap: { enabled: false }, readOnly: this._options.readonly, glyphMargin: false, lineNumbersMinChars: 2 }, - { contributions: [] } - ); - - this._title = new IconLabel(this._titleElement, { supportIcons: true }); - this._detail = new IconLabel(this._titleElement, { supportIcons: true }); - } - - public setModel(model: ITextModel, title: string, description: string | undefined, detail: string | undefined): void { - this.editor.setModel(model); - this._title.setLabel(title, description); - this._detail.setLabel('', detail); - } - - layout(width: number, height: number, top: number, left: number): void { - this.element.style.width = `${width}px`; - this.element.style.height = `${height}px`; - this.element.style.top = `${top}px`; - this.element.style.left = `${left}px`; - this.editor.layout({ width: width - this._gutterDiv.clientWidth, height: height - this._titleElement.clientHeight }); + this._register(applyObservableDecorations(this.editor, this.decorations)); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index ef5003afc4c..024aa737d42 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import { compareBy, numberComparator } from 'vs/base/common/arrays'; +import { compareBy, CompareResult, equals, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { ModifiedBaseRange, LineEdit, LineDiff, ModifiedBaseRangeState, LineRange, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { ModifiedBaseRange, LineEdit, LineDiff, ModifiedBaseRangeState, LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; +import { leftJoin, ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; export class MergeEditorModelFactory { constructor( @@ -41,10 +42,17 @@ export class MergeEditorModelFactory { false, 1000 ); + const baseToResultDiffPromise = this._editorWorkerService.computeDiff( + base.uri, + result.uri, + false, + 1000 + ); - const [baseToInput1Diff, baseToInput2Diff] = await Promise.all([ + const [baseToInput1Diff, baseToInput2Diff, baseToResultDiff] = await Promise.all([ baseToInput1DiffPromise, baseToInput2DiffPromise, + baseToResultDiffPromise ]); const changesInput1 = @@ -55,6 +63,10 @@ export class MergeEditorModelFactory { baseToInput2Diff?.changes.map((c) => LineDiff.fromLineChange(c, base, input2) ) || []; + const changesResult = + baseToResultDiff?.changes.map((c) => + LineDiff.fromLineChange(c, base, result) + ) || []; return new MergeEditorModel( InternalSymbol, @@ -68,7 +80,8 @@ export class MergeEditorModelFactory { result, changesInput1, changesInput2, - this._editorWorkerService + changesResult, + this._editorWorkerService, ); } } @@ -76,7 +89,7 @@ export class MergeEditorModelFactory { const InternalSymbol: unique symbol = null!; export class MergeEditorModel extends EditorModel { - private resultEdits = new ResultEdits([], this.base, this.result, this.editorWorkerService); + private resultEdits: ResultEdits; constructor( _symbol: typeof InternalSymbol, @@ -88,77 +101,104 @@ export class MergeEditorModel extends EditorModel { readonly input2Detail: string | undefined, readonly input2Description: string | undefined, readonly result: ITextModel, - private readonly inputOneLinesDiffs: readonly LineDiff[], - private readonly inputTwoLinesDiffs: readonly LineDiff[], + private readonly input1LinesDiffs: readonly LineDiff[], + private readonly input2LinesDiffs: readonly LineDiff[], + resultDiffs: LineDiff[], private readonly editorWorkerService: IEditorWorkerService ) { super(); - result.setValue(base.getValue()); - + this.resultEdits = new ResultEdits(resultDiffs, this.base, this.result, this.editorWorkerService); this.resultEdits.onDidChange(() => { - transaction(tx => { - for (const [key, value] of this.modifiedBaseRangeStateStores) { - value.set(this.computeState(key), tx); - } - }); + this.recomputeState(); }); - - /* - // Apply all non-conflicts diffs - const lineEditsArr: LineEdit[] = []; - for (const diff of this.mergeableDiffs) { - if (!diff.isConflicting) { - for (const d of diff.inputOneDiffs) { - lineEditsArr.push(d.getLineEdit()); - } - for (const d of diff.inputTwoDiffs) { - lineEditsArr.push(d.getLineEdit()); - } - } - } - new LineEdits(lineEditsArr).apply(result); - */ + this.recomputeState(); } - public get resultDiffs(): readonly LineDiff[] { + private recomputeState(): void { + transaction(tx => { + const baseRangeWithStoreAndTouchingDiffs = leftJoin( + this.modifiedBaseRangeStateStores, + this.resultEdits.diffs.get(), + (baseRange, diff) => + baseRange[0].baseRange.touches(diff.originalRange) + ? CompareResult.neitherLessOrGreaterThan + : LineRange.compareByStart( + baseRange[0].baseRange, + diff.originalRange + ) + ); + + for (const row of baseRangeWithStoreAndTouchingDiffs) { + row.left[1].set(this.computeState(row.left[0], row.rights), tx); + } + }); + } + + public mergeNonConflictingDiffs(): void { + transaction((tx) => { + for (const m of this.modifiedBaseRanges) { + if (m.isConflicting) { + continue; + } + this.setState( + m, + m.input1Diffs.length > 0 + ? ModifiedBaseRangeState.default.withInput1(true) + : ModifiedBaseRangeState.default.withInput2(true), + tx + ); + } + }); + } + + public get resultDiffs(): IObservable { return this.resultEdits.diffs; } public readonly modifiedBaseRanges = ModifiedBaseRange.fromDiffs( this.base, this.input1, - this.inputOneLinesDiffs, + this.input1LinesDiffs, this.input2, - this.inputTwoLinesDiffs + this.input2LinesDiffs ); - private readonly modifiedBaseRangeStateStores = new Map>( - this.modifiedBaseRanges.map(s => ([s, new ObservableValue(new ModifiedBaseRangeState(false, false, false), 'State')])) + private readonly modifiedBaseRangeStateStores = new Map>( + this.modifiedBaseRanges.map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')])) ); - private computeState(baseRange: ModifiedBaseRange): ModifiedBaseRangeState | undefined { - const existingDiff = this.resultEdits.findConflictingDiffs( - baseRange.baseRange - ); - if (!existingDiff) { - return new ModifiedBaseRangeState(false, false, false); + private computeState(baseRange: ModifiedBaseRange, conflictingDiffs?: LineDiff[]): ModifiedBaseRangeState { + if (!conflictingDiffs) { + conflictingDiffs = this.resultEdits.findTouchingDiffs( + baseRange.baseRange + ); } - const input1Edit = baseRange.getInput1LineEdit(); - if (input1Edit && existingDiff.getLineEdit().equals(input1Edit)) { - return new ModifiedBaseRangeState(true, false, false); + if (conflictingDiffs.length === 0) { + return ModifiedBaseRangeState.default; + } + const conflictingEdits = conflictingDiffs.map((d) => d.getLineEdit()); + + function editsAgreeWithDiffs(diffs: readonly LineDiff[]): boolean { + return equals( + conflictingEdits, + diffs.map((d) => d.getLineEdit()), + (a, b) => a.equals(b) + ); } - const input2Edit = baseRange.getInput2LineEdit(); - if (input2Edit && existingDiff.getLineEdit().equals(input2Edit)) { - return new ModifiedBaseRangeState(false, true, false); + if (editsAgreeWithDiffs(baseRange.input1Diffs)) { + return ModifiedBaseRangeState.default.withInput1(true); + } + if (editsAgreeWithDiffs(baseRange.input2Diffs)) { + return ModifiedBaseRangeState.default.withInput2(true); } - return undefined; + return ModifiedBaseRangeState.conflicting; } - public getState(baseRange: ModifiedBaseRange): IObservable { + public getState(baseRange: ModifiedBaseRange): IObservable { const existingState = this.modifiedBaseRangeStateStores.get(baseRange); if (!existingState) { throw new BugIndicatingError('object must be from this instance'); @@ -177,22 +217,26 @@ export class MergeEditorModel extends EditorModel { } existingState.set(state, transaction); - const existingDiff = this.resultEdits.findConflictingDiffs( + const conflictingDiffs = this.resultEdits.findTouchingDiffs( baseRange.baseRange ); - if (existingDiff) { - this.resultEdits.removeDiff(existingDiff); + if (conflictingDiffs) { + this.resultEdits.removeDiffs(conflictingDiffs, transaction); } - const edit = state.input1 - ? baseRange.getInput1LineEdit() + const diff = state.input1 + ? baseRange.input1CombinedDiff : state.input2 - ? baseRange.getInput2LineEdit() + ? baseRange.input2CombinedDiff : undefined; - if (edit) { - this.resultEdits.applyEditRelativeToOriginal(edit); + if (diff) { + this.resultEdits.applyEditRelativeToOriginal(diff.getLineEdit(), transaction); } } + + public getResultRange(baseRange: LineRange): LineRange { + return this.resultEdits.getResultRange(baseRange); + } } class ResultEdits { @@ -201,12 +245,13 @@ class ResultEdits { public readonly onDidChange = this.onDidChangeEmitter.event; constructor( - private _diffs: LineDiff[], + diffs: LineDiff[], private readonly baseTextModel: ITextModel, private readonly resultTextModel: ITextModel, private readonly _editorWorkerService: IEditorWorkerService ) { - this._diffs.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator)); + diffs.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator)); + this._diffs.set(diffs, undefined); resultTextModel.onDidChangeContent(e => { this.barrier.runExclusively(() => { @@ -220,7 +265,7 @@ class ResultEdits { e?.changes.map((c) => LineDiff.fromLineChange(c, baseTextModel, resultTextModel) ) || []; - this._diffs = diffs; + this._diffs.set(diffs, undefined); this.onDidChangeEmitter.fire(undefined); }); @@ -228,47 +273,55 @@ class ResultEdits { }); } - public get diffs(): readonly LineDiff[] { - return this._diffs; - } + private readonly _diffs = new ObservableValue([], 'diffs'); - public removeDiff(diffToRemove: LineDiff): void { - const len = this._diffs.length; - this._diffs = this._diffs.filter((d) => d !== diffToRemove); - if (len === this._diffs.length) { - throw new BugIndicatingError(); + public readonly diffs: IObservable = this._diffs; + + public removeDiffs(diffToRemoves: LineDiff[], transaction: ITransaction | undefined): void { + diffToRemoves.sort(compareBy((d) => d.originalRange.startLineNumber, numberComparator)); + diffToRemoves.reverse(); + + let diffs = this._diffs.get(); + + for (const diffToRemove of diffToRemoves) { + // TODO improve performance + const len = diffs.length; + diffs = diffs.filter((d) => d !== diffToRemove); + if (len === diffs.length) { + throw new BugIndicatingError(); + } + + this.barrier.runExclusivelyOrThrow(() => { + diffToRemove.getReverseLineEdit().apply(this.resultTextModel); + }); + + diffs = diffs.map((d) => + d.modifiedRange.isAfter(diffToRemove.modifiedRange) + ? new LineDiff( + d.originalTextModel, + d.originalRange, + d.modifiedTextModel, + d.modifiedRange.delta( + diffToRemove.originalRange.lineCount - diffToRemove.modifiedRange.lineCount + ) + ) + : d + ); } - this.barrier.runExclusivelyOrThrow(() => { - diffToRemove.getReverseLineEdit().apply(this.resultTextModel); - }); - - this._diffs = this._diffs.map((d) => - d.modifiedRange.isAfter(diffToRemove.modifiedRange) - ? new LineDiff( - d.originalTextModel, - d.originalRange, - d.modifiedTextModel, - d.modifiedRange.delta( - diffToRemove.originalRange.lineCount - diffToRemove.modifiedRange.lineCount - ) - ) - : d - ); + this._diffs.set(diffs, transaction); } /** * Edit must be conflict free. */ - public applyEditRelativeToOriginal(edit: LineEdit): void { + public applyEditRelativeToOriginal(edit: LineEdit, transaction: ITransaction | undefined): void { let firstAfter = false; let delta = 0; const newDiffs = new Array(); - for (let i = 0; i < this._diffs.length; i++) { - const diff = this._diffs[i]; - + for (const diff of this._diffs.get()) { if (diff.originalRange.touches(edit.range)) { - throw new BugIndicatingError(); + throw new BugIndicatingError('Edit must be conflict free.'); } else if (diff.originalRange.isAfter(edit.range)) { if (!firstAfter) { firstAfter = true; @@ -306,16 +359,30 @@ class ResultEdits { new LineRange(edit.range.startLineNumber + delta, edit.newLines.length) )); } - this._diffs = newDiffs; + this._diffs.set(newDiffs, transaction); this.barrier.runExclusivelyOrThrow(() => { new LineEdit(edit.range.delta(delta), edit.newLines).apply(this.resultTextModel); }); } - // TODO return many! - public findConflictingDiffs(rangeInOriginalTextModel: LineRange): LineDiff | undefined { - // TODO binary search - return this.diffs.find(d => d.originalRange.touches(rangeInOriginalTextModel)); + public findTouchingDiffs(baseRange: LineRange): LineDiff[] { + return this.diffs.get().filter(d => d.originalRange.touches(baseRange)); + } + + public getResultRange(baseRange: LineRange): LineRange { + let startOffset = 0; + let lengthOffset = 0; + for (const diff of this.diffs.get()) { + if (diff.originalRange.endLineNumberExclusive <= baseRange.startLineNumber) { + startOffset += diff.resultingDeltaFromOriginalToModified; + } else if (diff.originalRange.startLineNumber <= baseRange.endLineNumberExclusive) { + lengthOffset += diff.resultingDeltaFromOriginalToModified; + } else { + break; + } + } + + return new LineRange(baseRange.startLineNumber + startOffset, baseRange.lineCount + lengthOffset); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model.ts b/src/vs/workbench/contrib/mergeEditor/browser/model.ts index 5d21dc17180..9a045854182 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ArrayQueue, compareBy, equals, numberComparator } from 'vs/base/common/arrays'; +import { Comparator, compareBy, equals, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Range } from 'vs/editor/common/core/range'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; @@ -40,7 +40,9 @@ export class LineEdits { } export class LineRange { - public static hull(ranges: LineRange[]): LineRange | undefined { + public static readonly compareByStart: Comparator = compareBy(l => l.startLineNumber, numberComparator); + + public static join(ranges: LineRange[]): LineRange | undefined { if (ranges.length === 0) { return undefined; } @@ -63,6 +65,10 @@ export class LineRange { } } + public join(other: LineRange): LineRange { + return new LineRange(Math.min(this.startLineNumber, other.startLineNumber), Math.max(this.endLineNumberExclusive, other.endLineNumberExclusive) - this.startLineNumber); + } + public get endLineNumberExclusive(): number { return this.startLineNumber + this.lineCount; } @@ -131,9 +137,9 @@ export class LineDiff { return new LineDiff( lineDiffs[0].originalTextModel, - LineRange.hull(lineDiffs.map((d) => d.originalRange))!, + LineRange.join(lineDiffs.map((d) => d.originalRange))!, lineDiffs[0].modifiedTextModel, - LineRange.hull(lineDiffs.map((d) => d.modifiedRange))!, + LineRange.join(lineDiffs.map((d) => d.modifiedRange))!, ); } @@ -141,7 +147,7 @@ export class LineDiff { if (lineDiffs.length === 0) { return []; } - const originalRange = LineRange.hull(lineDiffs.map((d) => d.originalRange))!; + const originalRange = LineRange.join(lineDiffs.map((d) => d.originalRange))!; return lineDiffs.map(l => { const startDelta = originalRange.startLineNumber - l.originalRange.startLineNumber; const endDelta = originalRange.endLineNumberExclusive - l.originalRange.endLineNumberExclusive; @@ -231,7 +237,7 @@ export class ModifiedBaseRange { * This method computes strongly connected components of that graph while maintaining the side of each diff. */ public static fromDiffs( - originalTextModel: ITextModel, + baseTextModel: ITextModel, input1TextModel: ITextModel, diffs1: readonly LineDiff[], input2TextModel: ITextModel, @@ -242,90 +248,52 @@ export class ModifiedBaseRange { numberComparator ); - const queueDiffs1 = new ArrayQueue( - diffs1.slice().sort(compareByStartLineNumber) - ); - const queueDiffs2 = new ArrayQueue( - diffs2.slice().sort(compareByStartLineNumber) - ); + const diffs = diffs1 + .map((diff) => ({ source: 0 as 0 | 1, diff })) + .concat(diffs2.map((diff) => ({ source: 1 as const, diff }))); + + diffs.sort(compareBy(d => d.diff, compareByStartLineNumber)); + + const currentDiffs = [ + new Array(), + new Array(), + ]; + let deltaFromBaseToInput = [0, 0]; const result = new Array(); - while (true) { - const lastDiff1 = queueDiffs1.peekLast(); - const lastDiff2 = queueDiffs2.peekLast(); - - if ( - lastDiff1 && - (!lastDiff2 || - lastDiff1.originalRange.startLineNumber >= - lastDiff2.originalRange.startLineNumber) - ) { - queueDiffs1.removeLast(); - - const otherConflictingWith = - queueDiffs2.takeFromEndWhile((d) => d.conflicts(lastDiff1)) || []; - - const singleLinesDiff = LineDiff.hull(otherConflictingWith); - - const moreConflictingWith = - (singleLinesDiff && - queueDiffs1.takeFromEndWhile((d) => - d.conflicts(singleLinesDiff) - )) || - []; - moreConflictingWith.push(lastDiff1); - - result.push( - new ModifiedBaseRange( - originalTextModel, - input1TextModel, - moreConflictingWith, - queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - input2TextModel, - otherConflictingWith, - queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - ) - ); - } else if (lastDiff2) { - queueDiffs2.removeLast(); - - const otherConflictingWith = - queueDiffs1.takeFromEndWhile((d) => d.conflicts(lastDiff2)) || []; - - const singleLinesDiff = LineDiff.hull(otherConflictingWith); - - const moreConflictingWith = - (singleLinesDiff && - queueDiffs2.takeFromEndWhile((d) => - d.conflicts(singleLinesDiff) - )) || - []; - moreConflictingWith.push(lastDiff2); - - result.push( - new ModifiedBaseRange( - originalTextModel, - input1TextModel, - otherConflictingWith, - queueDiffs1.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - input2TextModel, - moreConflictingWith, - queueDiffs2.peekLast()?.resultingDeltaFromOriginalToModified ?? 0, - ) - ); - } else { - break; - } + function pushAndReset() { + result.push(new ModifiedBaseRange( + baseTextModel, + input1TextModel, + currentDiffs[0], + deltaFromBaseToInput[0], + input2TextModel, + currentDiffs[1], + deltaFromBaseToInput[1], + )); + currentDiffs[0] = []; + currentDiffs[1] = []; } - result.reverse(); + let currentRange: LineRange | undefined; + + for (const diff of diffs) { + const range = diff.diff.originalRange; + if (currentRange && !currentRange.touches(range)) { + pushAndReset(); + } + deltaFromBaseToInput[diff.source] = diff.diff.resultingDeltaFromOriginalToModified; + currentRange = currentRange ? currentRange.join(range) : range; + currentDiffs[diff.source].push(diff.diff); + } + pushAndReset(); return result; } - private readonly input1FullDiff = LineDiff.hull(this.input1Diffs); - private readonly input2FullDiff = LineDiff.hull(this.input2Diffs); + public readonly input1CombinedDiff = LineDiff.hull(this.input1Diffs); + public readonly input2CombinedDiff = LineDiff.hull(this.input2Diffs); public readonly baseRange: LineRange; public readonly input1Range: LineRange; @@ -335,31 +303,31 @@ export class ModifiedBaseRange { public readonly baseTextModel: ITextModel, public readonly input1TextModel: ITextModel, public readonly input1Diffs: readonly LineDiff[], - public readonly input1DeltaLineCount: number, + input1DeltaLineCount: number, public readonly input2TextModel: ITextModel, public readonly input2Diffs: readonly LineDiff[], - public readonly input2DeltaLineCount: number, + input2DeltaLineCount: number, ) { if (this.input1Diffs.length === 0 && this.input2Diffs.length === 0) { throw new BugIndicatingError('must have at least one diff'); } const input1Diff = - this.input1FullDiff || + this.input1CombinedDiff || new LineDiff( baseTextModel, - this.input2FullDiff!.originalRange, + this.input2CombinedDiff!.originalRange, input1TextModel, - this.input2FullDiff!.originalRange.delta(input1DeltaLineCount) + this.input2CombinedDiff!.originalRange.delta(input1DeltaLineCount) ); const input2Diff = - this.input2FullDiff || + this.input2CombinedDiff || new LineDiff( baseTextModel, - this.input1FullDiff!.originalRange, + this.input1CombinedDiff!.originalRange, input1TextModel, - this.input1FullDiff!.originalRange.delta(input2DeltaLineCount) + this.input1CombinedDiff!.originalRange.delta(input2DeltaLineCount) ); const results = LineDiff.alignOriginalRange([input1Diff, input2Diff]); @@ -368,54 +336,57 @@ export class ModifiedBaseRange { this.input2Range = results[1].modifiedRange; } + public getInputRange(inputNumber: 1 | 2): LineRange { + return inputNumber === 1 ? this.input1Range : this.input2Range; + } + + public getInputDiffs(inputNumber: 1 | 2): readonly LineDiff[] { + return inputNumber === 1 ? this.input1Diffs : this.input2Diffs; + } + public get isConflicting(): boolean { return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; } - - public getInput1LineEdit(): LineEdit | undefined { - if (this.input1Diffs.length === 0) { - return undefined; - } - //new LineDiff(this.baseTextModel, this.tota) - if (this.input1Diffs.length === 1) { - return this.input1Diffs[0].getLineEdit(); - } else { - throw new Error('Method not implemented.'); - } - } - - public getInput2LineEdit(): LineEdit | undefined { - if (this.input2Diffs.length === 0) { - return undefined; - } - if (this.input2Diffs.length === 1) { - return this.input2Diffs[0].getLineEdit(); - } else { - throw new Error('Method not implemented.'); - } - } } export class ModifiedBaseRangeState { - constructor( + public static readonly default = new ModifiedBaseRangeState(false, false, false, false); + public static readonly conflicting = new ModifiedBaseRangeState(false, false, false, true); + + private constructor( public readonly input1: boolean, public readonly input2: boolean, - public readonly input2First: boolean + public readonly input2First: boolean, + public readonly conflicting: boolean, ) { } + public getInput(inputNumber: 1 | 2): boolean { + if (inputNumber === 1) { + return this.input1; + } else { + return this.input2; + } + } + + public withInputValue(inputNumber: 1 | 2, value: boolean): ModifiedBaseRangeState { + return inputNumber === 1 ? this.withInput1(value) : this.withInput2(value); + } + public withInput1(value: boolean): ModifiedBaseRangeState { return new ModifiedBaseRangeState( value, - this.input2, - value && this.isEmpty ? false : this.input2First + false, + value && this.isEmpty ? false : this.input2First, + false, ); } public withInput2(value: boolean): ModifiedBaseRangeState { return new ModifiedBaseRangeState( - this.input1, + false, value, - value && this.isEmpty ? true : this.input2First + value && this.isEmpty ? true : this.input2First, + false ); } @@ -445,31 +416,3 @@ export class ModifiedBaseRangeState { return arr.join(','); } } - -export class ReentrancyBarrier { - private isActive = false; - - public runExclusively(fn: () => void): void { - if (this.isActive) { - return; - } - this.isActive = true; - try { - fn(); - } finally { - this.isActive = false; - } - } - - public runExclusivelyOrThrow(fn: () => void): void { - if (this.isActive) { - throw new BugIndicatingError(); - } - this.isActive = true; - try { - fn(); - } finally { - this.isActive = false; - } - } -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts new file mode 100644 index 00000000000..cf5aaa569d7 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CompareResult, ArrayQueue } from 'vs/base/common/arrays'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { IDisposable } from 'xterm'; + +export class ReentrancyBarrier { + private isActive = false; + + public runExclusively(fn: () => void): void { + if (this.isActive) { + return; + } + this.isActive = true; + try { + fn(); + } finally { + this.isActive = false; + } + } + + public runExclusivelyOrThrow(fn: () => void): void { + if (this.isActive) { + throw new BugIndicatingError(); + } + this.isActive = true; + try { + fn(); + } finally { + this.isActive = false; + } + } +} + +export function n(tag: TTag): never; +export function n( + tag: TTag, + children: T +): (ArrayToObj & Record<'root', TagToElement>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; +export function n( + tag: TTag, + attributes: { $: TId } +): Record>; +export function n( + tag: TTag, + attributes: { $: TId }, + children: T +): (ArrayToObj & Record>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; +export function n(tag: string, ...args: [] | [attributes: { $: string } | Record, children?: any[]] | [children: any[]]): Record { + let attributes: Record; + let children: (Record | HTMLElement)[] | undefined; + + if (Array.isArray(args[0])) { + attributes = {}; + children = args[0]; + } else { + attributes = args[0] as any || {}; + children = args[1]; + } + + const [tagName, className] = tag.split('.'); + const el = document.createElement(tagName); + if (className) { + el.className = className; + } + + const result: Record = {}; + + if (children) { + for (const c of children) { + if (c instanceof HTMLElement) { + el.appendChild(c); + } else { + Object.assign(result, c); + el.appendChild(c.root); + } + } + } + + result['root'] = el; + + for (const [key, value] of Object.entries(attributes)) { + if (key === '$') { + result[value] = el; + continue; + } + el.setAttribute(key, value); + } + + return result; +} + +type RemoveHTMLElement = T extends HTMLElement ? never : T; + +type ArrayToObj = UnionToIntersection>; + + +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + +type HTMLElementsByTagName = { + div: HTMLDivElement; + span: HTMLSpanElement; + a: HTMLAnchorElement; +}; + +type TagToElement = T extends `${infer TStart}.${string}` + ? TStart extends keyof HTMLElementsByTagName + ? HTMLElementsByTagName[TStart] + : HTMLElement + : T extends keyof HTMLElementsByTagName + ? HTMLElementsByTagName[T] + : HTMLElement; + +export function setStyle( + element: HTMLElement, + style: { + width?: number | string; + height?: number | string; + left?: number | string; + top?: number | string; + } +): void { + Object.entries(style).forEach(([key, value]) => { + element.style.setProperty(key, toSize(value)); + }); +} + +function toSize(value: number | string): string { + return typeof value === 'number' ? `${value}px` : value; +} + +export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable): IDisposable { + const d = new DisposableStore(); + let decorationIds: string[] = []; + d.add(autorun(reader => { + const d = decorations.read(reader); + editor.changeDecorations(a => { + decorationIds = a.deltaDecorations(decorationIds, d); + }); + }, 'Update Decorations')); + d.add({ + dispose: () => { + editor.changeDecorations(a => { + decorationIds = a.deltaDecorations(decorationIds, []); + }); + } + }); + return d; +} + +export function* leftJoin( + left: Iterable, + right: readonly TRight[], + compare: (left: TLeft, right: TRight) => CompareResult, +): IterableIterator<{ left: TLeft; rights: TRight[] }> { + const rightQueue = new ArrayQueue(right); + for (const leftElement of left) { + rightQueue.takeWhile(rightElement => CompareResult.isGreaterThan(compare(leftElement, rightElement))); + const equals = rightQueue.takeWhile(rightElement => CompareResult.isNeitherLessOrGreaterThan(compare(leftElement, rightElement))); + yield { left: leftElement, rights: equals || [] }; + } +} From fbbcdc94114509f0268db654d85405a3a39fa972 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 18 May 2022 17:10:28 -0700 Subject: [PATCH 145/350] Add WebviewInitInfo type (#149877) * Add WebviewInitInfo type This cleanup PR adds a new `WebviewInitInfo` type that brings together the core information needed to create a webview I've then updated the various webview APIs to use this new type * Revert frameId -> origin change * frameId -> iframeId --- .../api/browser/mainThreadCodeInsets.ts | 11 ++++-- .../api/browser/mainThreadWebviewPanels.ts | 8 +++- .../customEditor/browser/customEditorInput.ts | 7 +++- .../browser/customEditorInputFactory.ts | 15 ++++--- .../extensions/browser/extensionEditor.ts | 13 +++++-- .../view/renderers/backLayerWebView.ts | 23 ++++++----- .../update/browser/releaseNotesEditor.ts | 22 ++++++----- .../contrib/webview/browser/overlayWebview.ts | 19 ++++----- .../contrib/webview/browser/webview.ts | 15 ++----- .../contrib/webview/browser/webviewElement.ts | 39 ++++++++++++++----- .../contrib/webview/browser/webviewService.ts | 22 +++-------- .../electron-sandbox/webviewElement.ts | 12 ++---- .../electron-sandbox/webviewService.ts | 12 ++---- .../browser/webviewEditorInputSerializer.ts | 10 +++-- .../browser/webviewWorkbenchService.ts | 31 +++++---------- .../webviewView/browser/webviewViewPane.ts | 12 +++--- .../browser/gettingStarted.ts | 4 +- 17 files changed, 147 insertions(+), 128 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 9036d7a754a..7bb7cee0895 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -89,9 +89,14 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const disposables = new DisposableStore(); - const webview = this._webviewService.createWebviewElement('mainThreadCodeInsets_' + handle, { - enableFindWidget: false, - }, reviveWebviewContentOptions(options), { id: extensionId, location: URI.revive(extensionLocation) }); + const webview = this._webviewService.createWebviewElement({ + id: 'mainThreadCodeInsets_' + handle, + options: { + enableFindWidget: false, + }, + contentOptions: reviveWebviewContentOptions(options), + extension: { id: extensionId, location: URI.revive(extensionLocation) } + }); const webviewZone = new EditorWebviewZone(editor, line, height, webview); diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 6659298ef14..5737029a6a4 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -165,7 +165,13 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc const extension = reviveWebviewExtension(extensionData); - const webview = this._webviewWorkbenchService.createWebview(handle, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions, reviveWebviewOptions(initData.panelOptions), reviveWebviewContentOptions(initData.webviewOptions), extension); + const webview = this._webviewWorkbenchService.createWebview({ + id: handle, + options: reviveWebviewOptions(initData.panelOptions), + contentOptions: reviveWebviewContentOptions(initData.webviewOptions), + extension + }, this.webviewPanelViewType.fromExternal(viewType), initData.title, mainThreadShowOptions); + this.addWebviewInput(handle, webview, { serializeBuffersForPostMessage: initData.serializeBuffersForPostMessage }); const payload = { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 81dfae861af..e275174e00a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -38,7 +38,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { const untitledString = accessor.get(IUntitledTextEditorService).getValue(resource); let untitledDocumentData = untitledString ? VSBuffer.fromString(untitledString) : undefined; const id = generateUuid(); - const webview = accessor.get(IWebviewService).createWebviewOverlay(id, { customClasses: options?.customClasses }, {}, undefined); + const webview = accessor.get(IWebviewService).createWebviewOverlay({ + id, + options: { customClasses: options?.customClasses }, + contentOptions: {}, + extension: undefined, + }); const input = instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, { untitledDocumentData: untitledDocumentData, oldResource: options?.oldResource }); if (typeof group !== 'undefined') { input.updateGroup(group); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 3304e1334a0..a4a7f7eea73 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -108,11 +108,16 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } function reviveWebview(webviewService: IWebviewService, data: { id: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { - const webview = webviewService.createWebviewOverlay(data.id, { - purpose: WebviewContentPurpose.CustomEditor, - enableFindWidget: data.webviewOptions.enableFindWidget, - retainContextWhenHidden: data.webviewOptions.retainContextWhenHidden - }, data.contentOptions, data.extension); + const webview = webviewService.createWebviewOverlay({ + id: data.id, + options: { + purpose: WebviewContentPurpose.CustomEditor, + enableFindWidget: data.webviewOptions.enableFindWidget, + retainContextWhenHidden: data.webviewOptions.retainContextWhenHidden + }, + contentOptions: data.contentOptions, + extension: data.extension, + }); webview.state = data.state; return webview; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 2f1244e6ebd..6ad78716620 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -744,10 +744,15 @@ export class ExtensionEditor extends EditorPane { return Promise.resolve(null); } - const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay(generateUuid(), { - enableFindWidget: true, - tryRestoreScrollPosition: true, - }, {}, undefined)); + const webview = this.contentDisposables.add(this.webviewService.createWebviewOverlay({ + id: generateUuid(), + options: { + enableFindWidget: true, + tryRestoreScrollPosition: true, + }, + contentOptions: {}, + extension: undefined, + })); webview.initialScrollProgress = this.initialScrollProgress.get(webviewIndex) || 0; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index cc2eb8bf93a..af4606ffee6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -884,15 +884,20 @@ var requirejs = (function() { ...workspaceFolders, ...this.getBuiltinLocalResourceRoots(), ]; - const webview = webviewService.createWebviewElement(this.id, { - purpose: WebviewContentPurpose.NotebookRenderer, - enableFindWidget: false, - transformCssVariables: transformWebviewThemeVars, - }, { - allowMultipleAPIAcquire: true, - allowScripts: true, - localResourceRoots: this.localResourceRootsCache, - }, undefined); + const webview = webviewService.createWebviewElement({ + id: this.id, + options: { + purpose: WebviewContentPurpose.NotebookRenderer, + enableFindWidget: false, + transformCssVariables: transformWebviewThemeVars, + }, + contentOptions: { + allowMultipleAPIAcquire: true, + allowScripts: true, + localResourceRoots: this.localResourceRootsCache, + }, + extension: undefined + }); webview.html = content; return webview; diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index df20df258da..9517be6e2e0 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -79,18 +79,20 @@ export class ReleaseNotesManager { this._webviewWorkbenchService.revealWebview(this._currentReleaseNotes, activeEditorPane ? activeEditorPane.group : this._editorGroupService.activeGroup, false); } else { this._currentReleaseNotes = this._webviewWorkbenchService.createWebview( - generateUuid(), + { + id: generateUuid(), + options: { + tryRestoreScrollPosition: true, + enableFindWidget: true, + }, + contentOptions: { + localResourceRoots: [] + }, + extension: undefined + }, 'releaseNotes', title, - { group: ACTIVE_GROUP, preserveFocus: false }, - { - tryRestoreScrollPosition: true, - enableFindWidget: true, - }, - { - localResourceRoots: [] - }, - undefined); + { group: ACTIVE_GROUP, preserveFocus: false }); this._currentReleaseNotes.webview.onDidClickLink(uri => this.onDidClickLink(URI.parse(uri))); this._currentReleaseNotes.onWillDispose(() => { this._currentReleaseNotes = undefined; }); diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index 057e5e10d4d..b1a228fc65c 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -12,6 +12,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, IWebview, WebviewContentOptions, IWebviewElement, WebviewExtensionDescription, WebviewMessageReceivedEvent, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webviewElement'; /** * Webview that is absolutely positioned over another element and that can creates and destroys an underlying webview as needed. @@ -41,20 +42,20 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _findWidgetVisible: IContextKey | undefined; private _findWidgetEnabled: IContextKey | undefined; + public readonly id: string; + public constructor( - public readonly id: string, - initialOptions: WebviewOptions, - initialContentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, + initInfo: WebviewInitInfo, @ILayoutService private readonly _layoutService: ILayoutService, @IWebviewService private readonly _webviewService: IWebviewService, @IContextKeyService private readonly _baseContextKeyService: IContextKeyService ) { super(); - this._extension = extension; - this._options = initialOptions; - this._contentOptions = initialContentOptions; + this.id = initInfo.id; + this._extension = initInfo.extension; + this._options = initInfo.options; + this._contentOptions = initInfo.contentOptions; } public get isFocused() { @@ -99,8 +100,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { // Webviews cannot be reparented in the dom as it will destroy their contents. // Mount them to a high level node to avoid this. this._layoutService.container.appendChild(this._container); - } + return this._container; } @@ -185,7 +186,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } if (!this._webview.value) { - const webview = this._webviewService.createWebviewElement(this.id, this._options, this._contentOptions, this.extension); + const webview = this._webviewService.createWebviewElement({ id: this.id, options: this._options, contentOptions: this._contentOptions, extension: this.extension }); this._webview.value = webview; webview.state = this._state; diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 3054fdda9fb..3d50fee82a0 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -14,6 +14,7 @@ import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMapping'; +import { WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webviewElement'; /** * Set when the find widget in a webview in a webview is visible. @@ -53,12 +54,7 @@ export interface IWebviewService { /** * Create a basic webview dom element. */ - createWebviewElement( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, - ): IWebviewElement; + createWebviewElement(initInfo: WebviewInitInfo): IWebviewElement; /** * Create a lazily created webview element that is overlaid on top of another element. @@ -66,12 +62,7 @@ export interface IWebviewService { * Allows us to avoid re-parenting the webview (which destroys its contents) when * moving webview around the workbench. */ - createWebviewOverlay( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, - ): IOverlayWebview; + createWebviewOverlay(initInfo: WebviewInitInfo): IOverlayWebview; } export const enum WebviewContentPurpose { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 6cd87f63f28..fe286d9a832 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -100,11 +100,28 @@ namespace WebviewState { export type State = typeof Ready | Initializing; } +export interface WebviewInitInfo { + readonly id: string; + + readonly options: WebviewOptions; + readonly contentOptions: WebviewContentOptions; + + readonly extension: WebviewExtensionDescription | undefined; +} + + export class WebviewElement extends Disposable implements IWebview, WebviewFindDelegate { + /** + * External identifier of this webview. + */ public readonly id: string; + /** + * Unique identifier of this webview iframe element. + */ private readonly iframeId: string; + private readonly encodedWebviewOriginPromise: Promise; private encodedWebviewOrigin: string | undefined; @@ -153,11 +170,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD private _disposed = false; + + public extension: WebviewExtensionDescription | undefined; + private readonly options: WebviewOptions; + constructor( - id: string, - private readonly options: WebviewOptions, - contentOptions: WebviewContentOptions, - public extension: WebviewExtensionDescription | undefined, + initInfo: WebviewInitInfo, protected readonly webviewThemeDataProvider: WebviewThemeDataProvider, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -174,13 +192,16 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD ) { super(); - this.id = id; + this.id = initInfo.id; this.iframeId = generateUuid(); this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.iframeId).then(id => this.encodedWebviewOrigin = id); + this.options = initInfo.options; + this.extension = initInfo.extension; + this.content = { html: '', - options: contentOptions, + options: initInfo.contentOptions, state: undefined }; @@ -190,7 +211,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this._tunnelService )); - this._element = this.createElement(options, contentOptions); + this._element = this.createElement(initInfo.options, initInfo.contentOptions); const subscription = this._register(addDisposableListener(window, 'message', (e: MessageEvent) => { @@ -355,14 +376,14 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.startBlockingIframeDragEvents(); })); - if (options.enableFindWidget) { + if (initInfo.options.enableFindWidget) { this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); this.styledFindWidget(); } this.encodedWebviewOriginPromise.then(encodedWebviewOrigin => { if (!this._disposed) { - this.initElement(encodedWebviewOrigin, extension, options); + this.initElement(encodedWebviewOrigin, this.extension, this.options); } }); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index ef902f5d5e8..1ca0b71be52 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -7,8 +7,8 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; -import { IWebviewService, IWebview, WebviewContentOptions, IWebviewElement, WebviewExtensionDescription, WebviewOptions, IOverlayWebview } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewElement } from 'vs/workbench/contrib/webview/browser/webviewElement'; +import { IOverlayWebview, IWebview, IWebviewElement, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewElement, WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { OverlayWebview } from './overlayWebview'; export class WebviewService extends Disposable implements IWebviewService { @@ -43,24 +43,14 @@ export class WebviewService extends Disposable implements IWebviewService { private readonly _onDidChangeActiveWebview = this._register(new Emitter()); public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event; - createWebviewElement( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, - ): IWebviewElement { - const webview = this._instantiationService.createInstance(WebviewElement, id, options, contentOptions, extension, this._webviewThemeDataProvider); + createWebviewElement(initInfo: WebviewInitInfo): IWebviewElement { + const webview = this._instantiationService.createInstance(WebviewElement, initInfo, this._webviewThemeDataProvider); this.registerNewWebview(webview); return webview; } - createWebviewOverlay( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, - ): IOverlayWebview { - const webview = this._instantiationService.createInstance(OverlayWebview, id, options, contentOptions, extension); + createWebviewOverlay(initInfo: WebviewInitInfo): IOverlayWebview { + const webview = this._instantiationService.createInstance(OverlayWebview, initInfo); this.registerNewWebview(webview); return webview; } diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts index 5d77ddbc1d9..692eeb591ba 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts @@ -23,8 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; import { FindInFrameOptions, IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; -import { WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewElement, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/webviewElement'; +import { WebviewElement, WebviewInitInfo, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WindowIgnoreMenuShortcutsManager } from 'vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -44,10 +43,7 @@ export class ElectronWebviewElement extends WebviewElement { protected override get platform() { return 'electron'; } constructor( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, + initInfo: WebviewInitInfo, webviewThemeDataProvider: WebviewThemeDataProvider, @IContextMenuService contextMenuService: IContextMenuService, @ITunnelService tunnelService: ITunnelService, @@ -64,7 +60,7 @@ export class ElectronWebviewElement extends WebviewElement { @IInstantiationService instantiationService: IInstantiationService, @IAccessibilityService accessibilityService: IAccessibilityService, ) { - super(id, options, contentOptions, extension, webviewThemeDataProvider, + super(initInfo, webviewThemeDataProvider, configurationService, contextMenuService, menuService, notificationService, environmentService, fileService, logService, remoteAuthorityResolverService, telemetryService, tunnelService, instantiationService, accessibilityService); @@ -80,7 +76,7 @@ export class ElectronWebviewElement extends WebviewElement { this._webviewKeyboardHandler.didBlur(); })); - if (options.enableFindWidget) { + if (initInfo.options.enableFindWidget) { this._register(this.onDidHtmlChange((newContent) => { if (this._findStarted && this._cachedHtmlContent !== newContent) { this.stopFind(false); diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts index c63c3a65675..20ec351f827 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewService.ts @@ -3,19 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWebviewElement, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; import { ElectronWebviewElement } from 'vs/workbench/contrib/webview/electron-sandbox/webviewElement'; export class ElectronWebviewService extends WebviewService { - override createWebviewElement( - id: string, - options: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, - ): IWebviewElement { - const webview = this._instantiationService.createInstance(ElectronWebviewElement, id, options, contentOptions, extension, this._webviewThemeDataProvider); + override createWebviewElement(initInfo: WebviewInitInfo): IWebviewElement { + const webview = this._instantiationService.createInstance(ElectronWebviewElement, initInfo, this._webviewThemeDataProvider); this.registerNewWebview(webview); return webview; } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index 2e49d30d1f4..abed8dcdb16 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -74,14 +74,16 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { ): WebviewInput { const data = this.fromJson(JSON.parse(serializedEditorInput)); return this._webviewWorkbenchService.reviveWebview({ - id: data.id, + webviewInitInfo: { + id: data.id, + options: data.webviewOptions, + contentOptions: data.contentOptions, + extension: data.extension, + }, viewType: data.viewType, title: data.title, iconPath: data.iconPath, state: data.state, - webviewOptions: data.webviewOptions, - contentOptions: data.contentOptions, - extension: data.extension, group: data.group }); } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 45d5a74cdf6..94ede834b02 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -15,7 +15,8 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiatio import { GroupIdentifier } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IOverlayWebview, IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; +import { IOverlayWebview, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewIconManager, WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; @@ -34,24 +35,18 @@ export interface IWebviewWorkbenchService { readonly iconManager: WebviewIconManager; createWebview( - id: string, + webviewInitInfo: WebviewInitInfo, viewType: string, title: string, showOptions: ICreateWebViewShowOptions, - webviewOptions: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, ): WebviewInput; reviveWebview(options: { - id: string; + webviewInitInfo: WebviewInitInfo; viewType: string; title: string; iconPath: WebviewIcons | undefined; state: any; - webviewOptions: WebviewOptions; - contentOptions: WebviewContentOptions; - extension: WebviewExtensionDescription | undefined; group: number | undefined; }): WebviewInput; @@ -218,16 +213,13 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc } public createWebview( - id: string, + webviewInitInfo: WebviewInitInfo, viewType: string, title: string, showOptions: ICreateWebViewShowOptions, - webviewOptions: WebviewOptions, - contentOptions: WebviewContentOptions, - extension: WebviewExtensionDescription | undefined, ): WebviewInput { - const webview = this._webviewService.createWebviewOverlay(id, webviewOptions, contentOptions, extension); - const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, webview, this.iconManager); + const webview = this._webviewService.createWebviewOverlay(webviewInitInfo); + const webviewInput = this._instantiationService.createInstance(WebviewInput, webviewInitInfo.id, viewType, title, webview, this.iconManager); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -268,20 +260,17 @@ export class WebviewEditorService extends Disposable implements IWebviewWorkbenc } public reviveWebview(options: { - id: string; + webviewInitInfo: WebviewInitInfo; viewType: string; title: string; iconPath: WebviewIcons | undefined; state: any; - webviewOptions: WebviewOptions; - contentOptions: WebviewContentOptions; - extension: WebviewExtensionDescription | undefined; group: number | undefined; }): WebviewInput { - const webview = this._webviewService.createWebviewOverlay(options.id, options.webviewOptions, options.contentOptions, options.extension); + const webview = this._webviewService.createWebviewOverlay(options.webviewInitInfo); webview.state = options.state; - const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, options.id, options.viewType, options.title, webview); + const webviewInput = this._instantiationService.createInstance(LazilyResolvedWebviewEditorInput, options.webviewInitInfo.id, options.viewType, options.title, webview); webviewInput.iconPath = options.iconPath; if (typeof options.group === 'number') { diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index b09e7f519cf..124df428f3f 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -167,12 +167,12 @@ export class WebviewViewPane extends ViewPane { this._activated = true; const webviewId = generateUuid(); - const webview = this.webviewService.createWebviewOverlay( - webviewId, - { purpose: WebviewContentPurpose.WebviewView }, - {}, - this.extensionId ? { id: this.extensionId } : undefined - ); + const webview = this.webviewService.createWebviewOverlay({ + id: webviewId, + options: { purpose: WebviewContentPurpose.WebviewView }, + contentOptions: {}, + extension: this.extensionId ? { id: this.extensionId } : undefined + }); webview.state = this.viewState[storageKeys.webviewState]; this._webview.value = webview; diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 977b500b88d..5fac78668bf 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -529,7 +529,7 @@ export class GettingStartedPage extends EditorPane { this.stepsContent.classList.remove('markdown'); const media = stepToExpand.media; - const webview = this.stepDisposables.add(this.webviewService.createWebviewElement(this.webviewID, {}, {}, undefined)); + const webview = this.stepDisposables.add(this.webviewService.createWebviewElement({ id: this.webviewID, options: {}, contentOptions: {}, extension: undefined })); webview.mountTo(this.stepMediaComponent); webview.html = await this.detailsRenderer.renderSVG(media.path); @@ -570,7 +570,7 @@ export class GettingStartedPage extends EditorPane { const media = stepToExpand.media; - const webview = this.stepDisposables.add(this.webviewService.createWebviewElement(this.webviewID, {}, { localResourceRoots: [media.root], allowScripts: true }, undefined)); + const webview = this.stepDisposables.add(this.webviewService.createWebviewElement({ id: this.webviewID, options: {}, contentOptions: { localResourceRoots: [media.root], allowScripts: true }, extension: undefined })); webview.mountTo(this.stepMediaComponent); const rawHTML = await this.detailsRenderer.renderMarkdown(media.path, media.base); From ba2dd825803b5f95d5dcce927afc3a4882ed0f84 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 18 May 2022 17:14:29 -0700 Subject: [PATCH 146/350] update loader (#149878) --- src/vs/css.js | 4 ---- src/vs/loader.js | 12 +++++++++--- src/vs/nls.js | 9 ++++++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/vs/css.js b/src/vs/css.js index a719c310ec3..8a0f9912902 100644 --- a/src/vs/css.js +++ b/src/vs/css.js @@ -16,10 +16,6 @@ 'use strict'; var CSSLoaderPlugin; (function (CSSLoaderPlugin) { - /** - * Known issue: - * - In IE there is no way to know if the CSS file loaded successfully or not. - */ var BrowserCSSLoader = /** @class */ (function () { function BrowserCSSLoader() { this._pendingLoads = 0; diff --git a/src/vs/loader.js b/src/vs/loader.js index d66392e23a0..69554c5fac4 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -277,8 +277,10 @@ var AMDLoader; return; } if (err.phase === 'factory') { - console.error('The factory method of "' + err.moduleId + '" has thrown an exception'); + console.error('The factory function of "' + err.moduleId + '" has thrown an exception'); console.error(err); + console.error('Here are the modules that depend on it:'); + console.error(err.neededBy); return; } } @@ -1183,7 +1185,7 @@ var AMDLoader; producedError: null }; }; - Module.prototype.complete = function (recorder, config, dependenciesValues) { + Module.prototype.complete = function (recorder, config, dependenciesValues, inversedependenciesProvider) { this._isComplete = true; var producedError = null; if (this._callback) { @@ -1204,6 +1206,7 @@ var AMDLoader; var err = AMDLoader.ensureError(producedError); err.phase = 'factory'; err.moduleId = this.strId; + err.neededBy = inversedependenciesProvider(this.id); this.error = err; config.onError(err); } @@ -1815,7 +1818,10 @@ var AMDLoader; dependenciesValues[i] = null; } } - module.complete(recorder, this._config, dependenciesValues); + var inversedependenciesProvider = function (moduleId) { + return (_this._inverseDependencies2[moduleId] || []).map(function (intModuleId) { return _this._moduleIdProvider.getStrModuleId(intModuleId); }); + }; + module.complete(recorder, this._config, dependenciesValues, inversedependenciesProvider); // Fetch and clear inverse dependencies var inverseDeps = this._inverseDependencies2[module.id]; this._inverseDependencies2[module.id] = null; diff --git a/src/vs/nls.js b/src/vs/nls.js index f969d159f4a..793f0d6eecb 100644 --- a/src/vs/nls.js +++ b/src/vs/nls.js @@ -114,6 +114,7 @@ var NLSLoaderPlugin; }; NLSPlugin.prototype.load = function (name, req, load, config) { var _this = this; + var _a; config = config || {}; if (!name || name.length === 0) { load({ @@ -148,7 +149,13 @@ var NLSLoaderPlugin; }); } else { - req([name + suffix], messagesLoaded_1); + var base = (_a = pluginConfig.baseUrl) !== null && _a !== void 0 ? _a : ''; + req([base + name + suffix], messagesLoaded_1, function (err) { + var _a; + // We have an error. Load the English default strings instead. + console.warn("Falling back to default strings. Unable to load translations because of: " + ((_a = err.message) !== null && _a !== void 0 ? _a : err)); + req([name + '.nls'], messagesLoaded_1); + }); } } }; From 60e199cda4988e09fd78b1eeb9dce5e9e87e8a1d Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 19 May 2022 14:34:26 +0900 Subject: [PATCH 147/350] chore: bump electron@17.4.4 (#149896) --- .yarnrc | 2 +- .../linux/product-build-linux-client.yml | 10 ---------- build/azure-pipelines/win32/product-build-win32.yml | 8 -------- cgmanifest.json | 4 ++-- package.json | 2 +- yarn.lock | 8 ++++---- 6 files changed, 8 insertions(+), 26 deletions(-) diff --git a/.yarnrc b/.yarnrc index 95494af5201..f2811eb170a 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,4 +1,4 @@ disturl "https://electronjs.org/headers" -target "17.4.3" +target "17.4.4" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index 4e6f8f13d0e..7dba0eef378 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -122,16 +122,6 @@ steps: - script: | set -e export npm_config_arch=$(NPM_ARCH) - # node-gyp@9.0.0 shipped with node@16.15.0 starts using config.gypi - # from the custom headers path if dist-url option was set instead of - # using the config value from the process. Electron builds with pointer compression - # enabled for x64 and arm64, but incorrectly ships a single copy of config.gypi - # with v8_enable_pointer_compression option always set for all target architectures. - # We use the force_process_config option to use the config.gypi from the - # nodejs process executing npm for 32-bit architectures. - if [ "$NPM_ARCH" = "armv7l" ]; then - export npm_config_force_process_config="true" - fi if [ -z "$CC" ] || [ -z "$CXX" ]; then # Download clang based on chromium revision used by vscode diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 286283a31dc..4b746afc2a1 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -103,14 +103,6 @@ steps: . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" $env:npm_config_arch="$(VSCODE_ARCH)" - # node-gyp@9.0.0 shipped with node@16.15.0 starts using config.gypi - # from the custom headers path if dist-url option was set instead of - # using the config value from the process. Electron builds with pointer compression - # enabled for x64 and arm64, but incorrectly ships a single copy of config.gypi - # with v8_enable_pointer_compression option always set for all target architectures. - # We use the force_process_config option to use the config.gypi from the - # nodejs process executing npm for 32-bit architectures. - if ('$(VSCODE_ARCH)' -eq 'ia32') { $env:npm_config_force_process_config="true" } $env:CHILD_CONCURRENCY="1" retry { exec { yarn --frozen-lockfile --check-files } } env: diff --git a/cgmanifest.json b/cgmanifest.json index a4a8dc1be73..d153e97eb0f 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "322f1c3f8907f2592eef5b5e03a97045e30df9e3" + "commitHash": "085a15fd95969f3c61a52b39d64a7048d306dabe" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "17.4.3" + "version": "17.4.4" }, { "component": { diff --git a/package.json b/package.json index 035efff2f33..c9215abb13a 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "17.4.3", + "electron": "17.4.4", "eslint": "8.7.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^19.1.0", diff --git a/yarn.lock b/yarn.lock index cee415886c1..21813ce9737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4296,10 +4296,10 @@ electron-to-chromium@^1.4.17: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.45.tgz#cf1144091d6683cbd45a231954a745f02fb24598" integrity sha512-czF9eYVuOmlY/vxyMQz2rGlNSjZpxNQYBe1gmQv7al171qOIhgyO9k7D5AKlgeTCSPKk+LHhj5ZyIdmEub9oNg== -electron@17.4.3: - version "17.4.3" - resolved "https://registry.yarnpkg.com/electron/-/electron-17.4.3.tgz#5f3c26cb211f9267d2becee717f34e3ce564a6bf" - integrity sha512-WQggyCgNUOzoOn+wJKe+xFhYy56gyrn/jIa/l7dyD3TxPb8lddSc86OAqPnP5EugcNXQ0yIu8b+SIE8duKozSw== +electron@17.4.4: + version "17.4.4" + resolved "https://registry.yarnpkg.com/electron/-/electron-17.4.4.tgz#a289fa5cff6a59ef83647517a295eca780d64a86" + integrity sha512-/CqXJwm1VLfhF7+QhCrPEoePcpGMdRh09A+sVHX+kgT1twrmNH8S+ZeMPYxX8EU0O0Eki3UfA5zA2ADWaCDq2Q== dependencies: "@electron/get" "^1.13.0" "@types/node" "^14.6.2" From 14925e336de15a41da04deacea2150dcff77ae16 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 19 May 2022 11:18:22 +0200 Subject: [PATCH 148/350] Add a basic check (#149832) * Add a basic check * Fix yaml error * Another attempt to fix the yaml * update actions versions * let -> const * Add hygiene and layering check * update name --- .github/workflows/basic.yml | 142 ++++++++++++++++++ .../test/browser/outlineModel.test.ts | 4 +- 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/basic.yml diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml new file mode 100644 index 00000000000..270e9523938 --- /dev/null +++ b/.github/workflows/basic.yml @@ -0,0 +1,142 @@ +name: Basic checks + +on: + # push: + # branches: + # - main + # - release/* + pull_request: + branches: + - main + - release/* + +jobs: + main: + name: Compilation, Unit and Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + - name: Setup Build Environment + run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 + + - name: Compile and Download + run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + + - name: Run Unit Tests + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh + + - name: Run Integration Tests (Electron) + id: electron-integration-tests + run: DISPLAY=:10 ./scripts/test-integration.sh + + hygiene: + name: Hygiene and Layering + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 + + - name: Download Playwright + run: yarn playwright-install + + - name: Run Hygiene Checks + run: yarn gulp hygiene + + - name: Run Valid Layers Checks + run: yarn valid-layers-check + + - name: Compile /build/ + run: yarn --cwd build compile + + - name: Check clean git state + run: ./.github/workflows/check-clean-git-state.sh + + - name: Run eslint + run: yarn eslint + + - name: Run vscode-dts Compile Checks + run: yarn vscode-dts-compile-check + + - name: Run Trusted Types Checks + run: yarn tsec-compile-check diff --git a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts index d801c2c7722..096713ed3b0 100644 --- a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts @@ -77,9 +77,9 @@ suite('OutlineModel', function () { }); assert.strictEqual(isCancelled, false); - let s1 = new CancellationTokenSource(); + const s1 = new CancellationTokenSource(); service.getOrCreate(model, s1.token); - let s2 = new CancellationTokenSource(); + const s2 = new CancellationTokenSource(); service.getOrCreate(model, s2.token); s1.cancel(); From 6ccc8509acb20e6c7b630acf86d9bb153564a9c3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 May 2022 11:28:57 +0200 Subject: [PATCH 149/350] reduce roundtrps - send policies data to workbench and shared process --- .../sharedProcess/sharedProcessMain.ts | 4 +- .../policy/common/filePolicyService.ts | 35 ++--------- src/vs/platform/policy/common/policy.ts | 41 +++++++++++- src/vs/platform/policy/common/policyIpc.ts | 35 +++++------ .../policy/node/nativePolicyService.ts | 62 +++++++------------ .../electron-main/sharedProcess.ts | 5 +- .../sharedProcess/node/sharedProcess.ts | 4 ++ src/vs/platform/window/common/window.ts | 3 + .../platform/windows/electron-main/window.ts | 3 + .../electron-main/windowsMainService.ts | 5 +- .../electron-sandbox/desktop.main.ts | 4 +- 11 files changed, 102 insertions(+), 99 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 8a2cfbc36dc..243729c4e37 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -101,7 +101,7 @@ import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profili import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; class SharedProcessMain extends Disposable { @@ -183,7 +183,7 @@ class SharedProcessMain extends Disposable { services.set(IMainProcessService, mainProcessService); // Policies - const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + const policyService = this.configuration.policiesData ? new PolicyChannelClient(this.configuration.policiesData, mainProcessService.getChannel('policy')) : new NullPolicyService(); services.set(IPolicyService, policyService); // Environment diff --git a/src/vs/platform/policy/common/filePolicyService.ts b/src/vs/platform/policy/common/filePolicyService.ts index 4dadb9a1716..71d2ed5fd87 100644 --- a/src/vs/platform/policy/common/filePolicyService.ts +++ b/src/vs/platform/policy/common/filePolicyService.ts @@ -5,14 +5,13 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable } from 'vs/base/common/lifecycle'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; function keysDiff(a: Map, b: Map): string[] { const result: string[] = []; @@ -26,15 +25,7 @@ function keysDiff(a: Map, b: Map): string[] { return result; } -export class FilePolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private readonly policyNames: Set = new Set(); - private policies = new Map(); - - private readonly _onDidChange = new Emitter(); - readonly onDidChange = this._onDidChange.event; +export class FilePolicyService extends AbstractPolicyService implements IPolicyService { private readonly throttledDelayer = this._register(new ThrottledDelayer(500)); @@ -50,18 +41,8 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._register(onDidChangePolicyFile(() => this.throttledDelayer.trigger(() => this.refresh()))); } - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - let hasNewPolicies = false; - for (const key of Object.keys(policies)) { - if (!this.policyNames.has(key)) { - hasNewPolicies = true; - this.policyNames.add(key); - } - } - if (hasNewPolicies) { - await this.refresh(); - } - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + await this.refresh(); } private async read(): Promise> { @@ -76,7 +57,7 @@ export class FilePolicyService extends Disposable implements IPolicyService { } for (const key of Object.keys(raw)) { - if (this.policyNames.has(key)) { + if (this.policyDefinitions[key]) { policies.set(key, raw[key]); } } @@ -98,8 +79,4 @@ export class FilePolicyService extends Disposable implements IPolicyService { this._onDidChange.fire(diff); } } - - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/policy/common/policy.ts b/src/vs/platform/policy/common/policy.ts index 21ef3302bb0..d02ddfb77ef 100644 --- a/src/vs/platform/policy/common/policy.ts +++ b/src/vs/platform/policy/common/policy.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IStringDictionary } from 'vs/base/common/collections'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; +import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export type PolicyName = string; -export type PolicyValue = string | boolean; +export type PolicyValue = string | number; export type PolicyDefinition = { type: 'string' | 'number' }; export const IPolicyService = createDecorator('policy'); @@ -17,8 +19,40 @@ export interface IPolicyService { readonly _serviceBrand: undefined; readonly onDidChange: Event; - registerPolicyDefinitions(policies: IStringDictionary): Promise>; + registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise>; getPolicyValue(name: PolicyName): PolicyValue | undefined; + serialize(): IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }> | undefined; +} + +export abstract class AbstractPolicyService extends Disposable implements IPolicyService { + readonly _serviceBrand: undefined; + + protected policyDefinitions: IStringDictionary = {}; + protected policies = new Map(); + + protected readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { + const size = Object.keys(this.policyDefinitions).length; + this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + + if (size !== Object.keys(this.policyDefinitions).length) { + await this.initializePolicies(policyDefinitions); + } + + return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + } + + getPolicyValue(name: PolicyName): PolicyValue | undefined { + return this.policies.get(name); + } + + serialize(): IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }> { + return Iterable.reduce<[PolicyName, PolicyDefinition], IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>>(Object.entries(this.policyDefinitions), (r, [name, definition]) => ({ ...r, [name]: { definition, value: this.policies.get(name)! } }), {}); + } + + protected abstract initializePolicies(policyDefinitions: IStringDictionary): Promise; } export class NullPolicyService implements IPolicyService { @@ -26,4 +60,5 @@ export class NullPolicyService implements IPolicyService { readonly onDidChange = Event.None; async registerPolicyDefinitions() { return {}; } getPolicyValue() { return undefined; } + serialize() { return undefined; } } diff --git a/src/vs/platform/policy/common/policyIpc.ts b/src/vs/platform/policy/common/policyIpc.ts index 177b1d51ae6..cdca636e999 100644 --- a/src/vs/platform/policy/common/policyIpc.ts +++ b/src/vs/platform/policy/common/policyIpc.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; export class PolicyChannel implements IServerChannel { @@ -29,7 +29,7 @@ export class PolicyChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'initialize': return this.service.registerPolicyDefinitions(arg as IStringDictionary); + case 'registerPolicyDefinitions': return this.service.registerPolicyDefinitions(arg as IStringDictionary); } throw new Error(`Call not found: ${command}`); @@ -40,16 +40,17 @@ export class PolicyChannel implements IServerChannel { } } -export class PolicyChannelClient implements IPolicyService { +export class PolicyChannelClient extends AbstractPolicyService implements IPolicyService { - declare readonly _serviceBrand: undefined; - - private policies = new Map(); - - private readonly _onDidChange = new Emitter(); - readonly onDidChange: Event = this._onDidChange.event; - - constructor(private readonly channel: IChannel) { + constructor(policiesData: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>, private readonly channel: IChannel) { + super(); + for (const name in policiesData) { + const { definition, value } = policiesData[name]; + this.policyDefinitions[name] = definition; + if (value !== undefined) { + this.policies.set(name, value); + } + } this.channel.listen('onDidChange')(policies => { for (const name in policies) { const value = policies[name as keyof typeof policies]; @@ -65,17 +66,11 @@ export class PolicyChannelClient implements IPolicyService { }); } - async registerPolicyDefinitions(policies: IStringDictionary): Promise> { - const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('initialize', policies); - + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + const result = await this.channel.call<{ [name: PolicyName]: PolicyValue }>('registerPolicyDefinitions', policyDefinitions); for (const name in result) { this.policies.set(name, result[name]); } - - return result; } - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/policy/node/nativePolicyService.ts b/src/vs/platform/policy/node/nativePolicyService.ts index 9011bbf1534..4d0e46d9d06 100644 --- a/src/vs/platform/policy/node/nativePolicyService.ts +++ b/src/vs/platform/policy/node/nativePolicyService.ts @@ -3,23 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; -import { IPolicyService, PolicyDefinition, PolicyName, PolicyValue } from 'vs/platform/policy/common/policy'; +import { AbstractPolicyService, IPolicyService, PolicyDefinition } from 'vs/platform/policy/common/policy'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Iterable } from 'vs/base/common/iterator'; import { Throttler } from 'vs/base/common/async'; import { createWatcher, Watcher } from 'vscode-policy-watcher'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; -export class NativePolicyService extends Disposable implements IPolicyService { - - readonly _serviceBrand: undefined; - - private policyDefinitions: IStringDictionary = {}; - private readonly policies = new Map(); - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; +export class NativePolicyService extends AbstractPolicyService implements IPolicyService { private throttler = new Throttler(); private watcher = this._register(new MutableDisposable()); @@ -28,37 +18,27 @@ export class NativePolicyService extends Disposable implements IPolicyService { super(); } - async registerPolicyDefinitions(policyDefinitions: IStringDictionary): Promise> { - const size = Object.keys(this.policyDefinitions).length; - this.policyDefinitions = { ...policyDefinitions, ...this.policyDefinitions }; + protected async initializePolicies(policyDefinitions: IStringDictionary): Promise { + await this.throttler.queue(() => new Promise((c, e) => { + try { + this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { + for (const key in update) { + const value = update[key] as any; - if (size !== Object.keys(this.policyDefinitions).length) { - await this.throttler.queue(() => new Promise((c, e) => { - try { - this.watcher.value = createWatcher(this.productName, policyDefinitions, update => { - for (const key in update) { - const value = update[key] as any; - - if (value === undefined) { - this.policies.delete(key); - } else { - this.policies.set(key, value); - } + if (value === undefined) { + this.policies.delete(key); + } else { + this.policies.set(key, value); } + } - this._onDidChange.fire(Object.keys(update)); - c(); - }); - } catch (err) { - e(err); - } - })); - } - - return Iterable.reduce(this.policies.entries(), (r, [name, value]) => ({ ...r, [name]: value }), {}); + this._onDidChange.fire(Object.keys(update)); + c(); + }); + } catch (err) { + e(err); + } + })); } - getPolicyValue(name: PolicyName): PolicyValue | undefined { - return this.policies.get(name); - } } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index f7046a4d756..0ccd16e61e5 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -22,6 +22,7 @@ import { ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/com import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { WindowError } from 'vs/platform/window/electron-main/window'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export class SharedProcess extends Disposable implements ISharedProcess { @@ -39,6 +40,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, + @IPolicyService private readonly policyService: IPolicyService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService ) { @@ -242,7 +244,8 @@ export class SharedProcess extends Disposable implements ISharedProcess { userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.logService.getLevel(), - product + product, + policiesData: this.policyService.serialize() }); // Load with config diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 70c028b7a11..5c0265c6c4f 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { LogLevel } from 'vs/platform/log/common/log'; +import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; export interface ISharedProcess { @@ -24,4 +26,6 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly logLevel: LogLevel; readonly backupWorkspacesPath: string; + + readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 2c9591a2bec..afac7f93ce7 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { PerformanceMark } from 'vs/base/common/performance'; import { isLinux, isMacintosh, isNative, isWeb } from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -12,6 +13,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { FileType } from 'vs/platform/files/common/files'; import { LogLevel } from 'vs/platform/log/common/log'; +import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -289,6 +291,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native filesToWait?: IPathsToWaitFor; os: IOSConfiguration; + policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } /** diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index dd3fb88731f..3b8ba525827 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -39,6 +39,7 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderW import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState } from 'vs/platform/window/electron-main/window'; import { Color } from 'vs/base/common/color'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; export interface IWindowCreationOptions { state: IWindowState; @@ -149,6 +150,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, + @IPolicyService private readonly policyService: IPolicyService, @IFileService private readonly fileService: IFileService, @IGlobalStorageMainService private readonly globalStorageMainService: IGlobalStorageMainService, @IStorageMainService private readonly storageMainService: IStorageMainService, @@ -870,6 +872,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } configuration.isInitialStartup = false; // since this is a reload + configuration.policiesData = this.policyService.serialize(); // set policies data again // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ec6d1848956..81ecd35e990 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -51,6 +51,7 @@ import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electro import { ICodeWindow, UnloadReason } from 'vs/platform/window/electron-main/window'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IPolicyService } from 'vs/platform/policy/common/policy'; //#region Helper Interfaces @@ -192,6 +193,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @IStateMainService private readonly stateMainService: IStateMainService, + @IPolicyService private readonly policyService: IPolicyService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @@ -1320,7 +1322,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false, accessibilitySupport: app.accessibilitySupportEnabled, - colorScheme: this.themeMainService.getColorScheme() + colorScheme: this.themeMainService.getColorScheme(), + policiesData: this.policyService.serialize(), }; let window: ICodeWindow | undefined; diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 42ada97cea0..88955896dce 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -51,7 +51,7 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; export class DesktopMain extends Disposable { @@ -158,7 +158,7 @@ export class DesktopMain extends Disposable { serviceCollection.set(IMainProcessService, mainProcessService); // Policies - const policyService = new PolicyChannelClient(mainProcessService.getChannel('policy')); + const policyService = this.configuration.policiesData ? new PolicyChannelClient(this.configuration.policiesData, mainProcessService.getChannel('policy')) : new NullPolicyService(); serviceCollection.set(IPolicyService, policyService); // Product From 7ce1b0f88502c32a6024b56494832f6bf255c1fb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 May 2022 11:43:33 +0200 Subject: [PATCH 150/350] perf improvements --- .../configuration/common/configurationService.ts | 8 ++++---- .../configuration/common/configurations.ts | 16 ++++++++++++++-- .../browser/configurationService.ts | 8 ++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index a1bd9906ee0..99da4050566 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -10,10 +10,10 @@ import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel, UserSettings } from 'vs/platform/configuration/common/configurationModels'; -import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { DefaultConfiguration, IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { @@ -21,7 +21,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private configuration: Configuration; private readonly defaultConfiguration: DefaultConfiguration; - private readonly policyConfiguration: PolicyConfiguration; + private readonly policyConfiguration: IPolicyConfiguration; private readonly userConfiguration: UserSettings; private readonly reloadConfigurationScheduler: RunOnceScheduler; @@ -36,7 +36,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe ) { super(); this.defaultConfiguration = this._register(new DefaultConfiguration()); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); + this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, extUriBiasedIgnorePathCase, fileService)); this.configuration = new Configuration(this.defaultConfiguration.configurationModel, this.policyConfiguration.configurationModel, new ConfigurationModel()); diff --git a/src/vs/platform/configuration/common/configurations.ts b/src/vs/platform/configuration/common/configurations.ts index 6099234ef8e..81133870a7a 100644 --- a/src/vs/platform/configuration/common/configurations.ts +++ b/src/vs/platform/configuration/common/configurations.ts @@ -5,7 +5,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; @@ -78,7 +78,19 @@ export class DefaultConfigurationModel extends ConfigurationModel { } } -export class PolicyConfiguration extends Disposable { +export interface IPolicyConfiguration { + readonly onDidChangeConfiguration: Event; + readonly configurationModel: ConfigurationModel; + initialize(): Promise; +} + +export class NullPolicyConfiguration implements IPolicyConfiguration { + readonly onDidChangeConfiguration = Event.None; + readonly configurationModel = new ConfigurationModel(); + async initialize() { return this.configurationModel; } +} + +export class PolicyConfiguration extends Disposable implements IPolicyConfiguration { private readonly _onDidChangeConfiguration = this._register(new Emitter()); readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 26f97a95502..d32d7e978bb 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -13,7 +13,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkspaceContextService, Workspace as BaseWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder, isWorkspaceFolder, IWorkspaceFoldersWillChangeEvent, IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier, IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { ConfigurationModel, ConfigurationChangeEvent, AllKeysConfigurationChangeEvent, mergeChanges } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, isConfigurationOverrides, IConfigurationData, IConfigurationValue, IConfigurationChange, ConfigurationTargetToString, IConfigurationUpdateOverrides, isConfigurationUpdateOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; +import { IPolicyConfiguration, NullPolicyConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; import { Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, RestrictedSettings } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -40,7 +40,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; -import { IPolicyService } from 'vs/platform/policy/common/policy'; +import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; class Workspace extends BaseWorkspace { initialized: boolean = false; @@ -57,7 +57,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat private _configuration: Configuration; private initialized: boolean = false; private readonly defaultConfiguration: DefaultConfiguration; - private readonly policyConfiguration: PolicyConfiguration; + private readonly policyConfiguration: IPolicyConfiguration; private localUserConfiguration: UserConfiguration; private remoteUserConfiguration: RemoteUserConfiguration | null = null; private workspaceConfiguration: WorkspaceConfiguration; @@ -114,7 +114,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat this.initRemoteUserConfigurationBarrier = new Barrier(); this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = this._register(new DefaultConfiguration(configurationCache, environmentService)); - this.policyConfiguration = this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); + this.policyConfiguration = policyService instanceof NullPolicyService ? new NullPolicyConfiguration() : this._register(new PolicyConfiguration(this.defaultConfiguration, policyService, logService)); this.configurationCache = configurationCache; this.fileService = fileService; this.uriIdentityService = uriIdentityService; From e783fdc25e8edee0aed71f05f3bacafe962359ab Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 May 2022 12:48:53 +0200 Subject: [PATCH 151/350] css/html/json update to lsp 8 (#148715) --- .../client/src/browser/cssClientMain.ts | 16 +- .../client/src/cssClient.ts | 45 +-- .../client/src/node/cssClientMain.ts | 16 +- .../client/src/requests.ts | 4 +- extensions/css-language-features/package.json | 17 +- .../css-language-features/server/package.json | 4 +- .../server/src/cssServer.ts | 23 +- .../css-language-features/server/yarn.lock | 58 +-- extensions/css-language-features/yarn.lock | 48 +-- .../client/src/browser/htmlClientMain.ts | 16 +- .../client/src/htmlClient.ts | 129 ++++--- .../client/src/node/htmlClientMain.ts | 14 +- .../client/src/requests.ts | 4 +- .../html-language-features/package.json | 6 +- .../server/package.json | 10 +- .../server/src/htmlServer.ts | 14 +- .../server/src/modes/cssMode.ts | 4 +- .../server/src/modes/javascriptMode.ts | 29 +- .../server/src/modes/languageModes.ts | 10 + .../html-language-features/server/yarn.lock | 75 ++-- extensions/html-language-features/yarn.lock | 64 ++-- .../client/src/browser/jsonClientMain.ts | 15 +- .../client/src/jsonClient.ts | 348 +++++++++--------- .../client/src/node/jsonClientMain.ts | 13 +- .../json-language-features/package.json | 4 +- .../server/package.json | 4 +- .../server/src/jsonServer.ts | 20 +- .../json-language-features/server/yarn.lock | 68 ++-- extensions/json-language-features/yarn.lock | 56 +-- .../test/colorize-fixtures/test.html | 2 +- 30 files changed, 603 insertions(+), 533 deletions(-) diff --git a/extensions/css-language-features/client/src/browser/cssClientMain.ts b/extensions/css-language-features/client/src/browser/cssClientMain.ts index 0cfde9025f3..8fa2d81bd03 100644 --- a/extensions/css-language-features/client/src/browser/cssClientMain.ts +++ b/extensions/css-language-features/client/src/browser/cssClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../cssClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -15,8 +15,10 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/cssServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -24,9 +26,17 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, clientOptions, worker); }; - startClient(context, newLanguageClient, { TextDecoder }); + client = await startClient(context, newLanguageClient, { TextDecoder }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} + diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 282c347bdd8..6f6238465d8 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList, FormattingOptions, workspace } from 'vscode'; -import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, CommonLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; +import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, BaseLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { getCustomDataSource } from './customData'; import { RequestService, serveFileSystemRequests } from './requests'; @@ -15,7 +15,7 @@ namespace CustomDataChangedNotification { const localize = nls.loadMessageBundle(); -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; @@ -39,7 +39,7 @@ interface CSSFormatSettings { const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['newlineBetweenSelectors', 'newlineBetweenRules', 'spaceAroundSelectorSeparator', 'braceStyle', 'preserveNewLines', 'maxPreserveNewLines']; -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const customDataSource = getCustomDataSource(context.subscriptions); @@ -100,31 +100,25 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua // Create the language client and start the client. let client = newLanguageClient('css', localize('cssserver.name', 'CSS Language Server'), clientOptions); client.registerProposedFeatures(); - client.onReady().then(() => { + await client.start(); + + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - customDataSource.onDidChange(() => { - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - - // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. - for (const registration of formatterRegistrations) { - updateFormatterRegistration(registration); - context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); - context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); - } - - serveFileSystemRequests(client, runtime); }); - let disposable = client.start(); - // Push the disposable to the context's subscriptions so that the - // client can be deactivated on extension deactivation - context.subscriptions.push(disposable); + // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. + for (const registration of formatterRegistrations) { + updateFormatterRegistration(registration); + context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); + context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); + } - client.onReady().then(() => { - context.subscriptions.push(initCompletionProvider()); - }); + serveFileSystemRequests(client, runtime); + + + context.subscriptions.push(initCompletionProvider()); function initCompletionProvider(): Disposable { const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/; @@ -204,11 +198,10 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } } } - console.log(JSON.stringify(params.options)); return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( client.protocol2CodeConverter.asTextEdits, (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); return Promise.resolve([]); } ); @@ -216,4 +209,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua }); } } + + return client; } diff --git a/extensions/css-language-features/client/src/node/cssClientMain.ts b/extensions/css-language-features/client/src/node/cssClientMain.ts index b88838d8912..dfbe121f822 100644 --- a/extensions/css-language-features/client/src/node/cssClientMain.ts +++ b/extensions/css-language-features/client/src/node/cssClientMain.ts @@ -6,11 +6,14 @@ import { getNodeFSRequestService } from './nodeFs'; import { ExtensionContext, extensions } from 'vscode'; import { startClient, LanguageClientConstructor } from '../cssClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; + +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || ''; const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`; @@ -30,5 +33,12 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, serverOptions, clientOptions); }; - startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); + client = await startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); +} + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } } diff --git a/extensions/css-language-features/client/src/requests.ts b/extensions/css-language-features/client/src/requests.ts index 6aea3ab8fad..f19918e57ef 100644 --- a/extensions/css-language-features/client/src/requests.ts +++ b/extensions/css-language-features/client/src/requests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri, workspace } from 'vscode'; -import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; +import { RequestType, BaseLanguageClient } from 'vscode-languageclient'; import { Runtime } from './cssClient'; export namespace FsContentRequest { @@ -18,7 +18,7 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) { +export function serveFileSystemRequests(client: BaseLanguageClient, runtime: Runtime) { client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string }) => { const uri = Uri.parse(param.uri); if (uri.scheme === 'file' && runtime.fs) { diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index e9ea587d4db..c4955243c8f 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -336,7 +336,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%css.format.braceStyle.desc%" }, "css.format.preserveNewLines": { @@ -638,7 +641,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%scss.format.braceStyle.desc%" }, "scss.format.preserveNewLines": { @@ -941,7 +947,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%less.format.braceStyle.desc%" }, "less.format.preserveNewLines": { @@ -985,7 +994,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^7.0.0", + "vscode-languageclient": "^8.0.1", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.3" }, diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 18c39540516..d2f0443e65b 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,8 +10,8 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^5.4.2", - "vscode-languageserver": "^7.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-languageserver": "^8.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 40e05b13ab1..3eed3962879 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { - Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit + Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit, Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position, CSSFormatConfiguration } from 'vscode-css-languageservice'; @@ -64,6 +64,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + + const initializationOptions = params.initializationOptions as any || {}; + workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { workspaceFolders = []; @@ -72,7 +75,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } } - requestService = getRequestService(params.initializationOptions?.handledSchemas || ['file'], connection, runtime); + requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); function getClientCapability(name: string, def: T) { const keys = name.split('.'); @@ -88,7 +91,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false); scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); @@ -110,8 +113,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, + documentFormattingProvider: initializationOptions?.provideFormatter === true, }; return { capabilities }; }); @@ -135,7 +138,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let promise = documentSettings[textDocument.uri]; if (!promise) { const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] }; - promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0]); + promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0] as LanguageSettings | undefined); documentSettings[textDocument.uri] = promise; } return promise; @@ -145,12 +148,12 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { - updateConfiguration(change.settings); + updateConfiguration(change.settings as any); }); - function updateConfiguration(settings: Settings) { + function updateConfiguration(settings: any) { for (const languageId in languageServices) { - languageServices[languageId].configure((settings as any)[languageId]); + languageServices[languageId].configure(settings[languageId]); } // reset all document settings documentSettings = {}; @@ -193,7 +196,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const settingsPromise = getDocumentSettings(textDocument); Promise.all([settingsPromise, dataProvidersReady]).then(async ([settings]) => { const stylesheet = stylesheets.get(textDocument); - const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); + const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings) as Diagnostic[]; // Send the computed diagnostics to VSCode. connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); }, e => { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index b7a44bc52c4..8749dcdebad 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -12,50 +12,50 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-css-languageservice@^5.4.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz#69ea74c000bd653dfc8e458a1720d28b9ffa5cfb" - integrity sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg== +vscode-css-languageservice@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz#ccf94944e094dcc5833d1b4ac276994b698e9283" + integrity sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index 60ba7c2dcc3..76af92973f2 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -39,39 +39,39 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts index b69cca75854..ab23520fe79 100644 --- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -15,8 +15,10 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/htmlServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -31,9 +33,17 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { TextDecoder, timer }); + client = await startClient(context, newLanguageClient, { TextDecoder, timer }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} + diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 7baceece6fb..6c44ceb72a8 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -13,7 +13,7 @@ import { } from 'vscode'; import { LanguageClientOptions, RequestType, DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, Position as LspPosition, NotificationType, CommonLanguageClient + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, Position as LspPosition, NotificationType, BaseLanguageClient } from 'vscode-languageclient'; import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; @@ -72,7 +72,7 @@ export interface TelemetryReporter { }): void; } -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; @@ -83,18 +83,17 @@ export interface Runtime { }; } -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - let toDispose = context.subscriptions; + const toDispose = context.subscriptions; - - let documentSelector = ['html', 'handlebars']; - let embeddedLanguages = { css: true, javascript: true }; + const documentSelector = ['html', 'handlebars']; + const embeddedLanguages = { css: true, javascript: true }; let rangeFormatting: Disposable | undefined = undefined; // Options to control the language client - let clientOptions: LanguageClientOptions = { + const clientOptions: LanguageClientOptions = { documentSelector, synchronize: { configurationSection: ['html', 'css', 'javascript'], // the settings to synchronize @@ -135,67 +134,65 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions); client.registerProposedFeatures(); - let disposable = client.start(); - toDispose.push(disposable); - client.onReady().then(() => { + await client.start(); - toDispose.push(serveFileSystemRequests(client, runtime)); + toDispose.push(serveFileSystemRequests(client, runtime)); - const customDataSource = getCustomDataSource(runtime, context.subscriptions); + const customDataSource = getCustomDataSource(runtime, context.subscriptions); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - customDataSource.onDidChange(() => { - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - client.onRequest(CustomDataContent.type, customDataSource.getContent); - - - const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { - let param: AutoInsertParams = { - kind, - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: client.code2ProtocolConverter.asPosition(position) - }; - return client.sendRequest(AutoInsertRequest.type, param); - }; - let disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); - toDispose.push(disposable); - - disposable = client.onTelemetry(e => { - runtime.telemetry?.sendTelemetryEvent(e.key, e.data); - }); - toDispose.push(disposable); - - // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. - updateFormatterRegistration(); - toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); - toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(SettingIds.formatEnable) && updateFormatterRegistration())); - - client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { - if (legend) { - const provider: DocumentSemanticTokensProvider & DocumentRangeSemanticTokensProvider = { - provideDocumentSemanticTokens(doc) { - const params: SemanticTokenParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), - }; - return client.sendRequest(SemanticTokenRequest.type, params).then(data => { - return data && new SemanticTokens(new Uint32Array(data)); - }); - }, - provideDocumentRangeSemanticTokens(doc, range) { - const params: SemanticTokenParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), - ranges: [client.code2ProtocolConverter.asRange(range)] - }; - return client.sendRequest(SemanticTokenRequest.type, params).then(data => { - return data && new SemanticTokens(new Uint32Array(data)); - }); - } - }; - toDispose.push(languages.registerDocumentSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers))); - } - }); }); + client.onRequest(CustomDataContent.type, customDataSource.getContent); + + + const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { + const param: AutoInsertParams = { + kind, + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: client.code2ProtocolConverter.asPosition(position) + }; + return client.sendRequest(AutoInsertRequest.type, param); + }; + const disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); + toDispose.push(disposable); + + const disposable2 = client.onTelemetry(e => { + runtime.telemetry?.sendTelemetryEvent(e.key, e.data); + }); + toDispose.push(disposable2); + + // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. + updateFormatterRegistration(); + toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); + toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(SettingIds.formatEnable) && updateFormatterRegistration())); + + client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { + if (legend) { + const provider: DocumentSemanticTokensProvider & DocumentRangeSemanticTokensProvider = { + provideDocumentSemanticTokens(doc) { + const params: SemanticTokenParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), + }; + return client.sendRequest(SemanticTokenRequest.type, params).then(data => { + return data && new SemanticTokens(new Uint32Array(data)); + }); + }, + provideDocumentRangeSemanticTokens(doc, range) { + const params: SemanticTokenParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), + ranges: [client.code2ProtocolConverter.asRange(range)] + }; + return client.sendRequest(SemanticTokenRequest.type, params).then(data => { + return data && new SemanticTokens(new Uint32Array(data)); + }); + } + }; + toDispose.push(languages.registerDocumentSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers))); + } + }); + function updateFormatterRegistration() { const formatEnabled = workspace.getConfiguration().get(SettingIds.formatEnable); @@ -219,7 +216,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( client.protocol2CodeConverter.asTextEdits, (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); return Promise.resolve([]); } ); @@ -300,4 +297,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } } + return client; + } diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index 4c7d24e397c..f460d0c1524 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -6,16 +6,17 @@ import { getNodeFileFS } from './nodeFs'; import { Disposable, ExtensionContext } from 'vscode'; import { startClient, LanguageClientConstructor } from '../htmlClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; import * as fs from 'fs'; import TelemetryReporter from '@vscode/extension-telemetry'; let telemetry: TelemetryReporter | undefined; +let client: BaseLanguageClient | undefined; // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { let clientPackageJSON = getPackageInfo(context); telemetry = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey); @@ -44,7 +45,14 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { fileFs: getNodeFileFS(), TextDecoder, telemetry, timer }); + client = await startClient(context, newLanguageClient, { fileFs: getNodeFileFS(), TextDecoder, telemetry, timer }); +} + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } } interface IPackageInfo { diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index ba124e28cd7..8106f044228 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri, workspace, Disposable } from 'vscode'; -import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; +import { RequestType, BaseLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; export namespace FsStatRequest { @@ -15,7 +15,7 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable { +export function serveFileSystemRequests(client: BaseLanguageClient, runtime: Runtime): Disposable { const disposables = []; disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index e7244d251c0..2f2dc4d1067 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -261,9 +261,9 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "0.4.10", - "vscode-languageclient": "^7.0.0", - "vscode-nls": "^5.0.0", + "@vscode/extension-telemetry": "0.5.1", + "vscode-languageclient": "^8.0.1", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 423a395ba96..affcf98e566 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,11 +9,11 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^5.4.2", - "vscode-html-languageservice": "^4.2.5", - "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.3", - "vscode-nls": "^5.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-html-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index b3df5fc8e51..319399a038c 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -11,7 +11,7 @@ import { } from 'vscode-languageserver'; import { getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation, - Range, DocumentLink, SymbolInformation, TextDocumentIdentifier + Range, DocumentLink, SymbolInformation, TextDocumentIdentifier, isCompletionItemData } from './modes/languageModes'; import { format } from './modes/formatting'; @@ -129,7 +129,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities connection.onInitialize((params: InitializeParams): InitializeResult => { - const initializationOptions = params.initializationOptions; + const initializationOptions = params.initializationOptions as any || {}; workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { @@ -179,14 +179,14 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) scopedSettingsSupport = getClientCapability('workspace.configuration', false); workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined, hoverProvider: true, documentHighlightProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, + documentFormattingProvider: initializationOptions?.provideFormatter === true, documentLinkProvider: { resolveProvider: false }, documentSymbolProvider: true, definitionProvider: true, @@ -226,7 +226,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration((change) => { - globalSettings = change.settings; + globalSettings = change.settings as Settings; documentSettings = {}; // reset all document settings documents.all().forEach(triggerValidation); @@ -331,7 +331,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onCompletionResolve((item, token) => { return runSafe(runtime, async () => { const data = item.data; - if (data && data.languageId && data.uri) { + if (isCompletionItemData(data)) { const mode = languageModes.getMode(data.languageId); const document = documents.get(data.uri); if (mode && mode.doResolve && document) { diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 9757d99cd88..6bc02acb510 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -5,7 +5,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; -import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes'; +import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext, Diagnostic } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { @@ -18,7 +18,7 @@ export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegio }, async doValidation(document: TextDocument, settings = workspace.settings) { let embedded = embeddedCSSDocuments.get(document); - return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css); + return (cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css) as Diagnostic[]); }, async doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, _settings = workspace.settings) { let embedded = embeddedCSSDocuments.get(document); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index bbcba5e0f86..7f9e3f1d85a 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -8,7 +8,7 @@ import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange, - LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext + LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext, CompletionItemData, isCompletionItemData } from './languageModes'; import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; @@ -122,6 +122,11 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { + const data: CompletionItemData = { // data used for resolving item details (see 'doResolve') + languageId, + uri: document.uri, + offset: offset + }; return { uri: document.uri, position: position, @@ -129,23 +134,21 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { - const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); - if (details) { - item.detail = ts.displayPartsToString(details.displayParts); - item.documentation = ts.displayPartsToString(details.documentation); - delete item.data; + if (isCompletionItemData(item.data)) { + const jsDocument = jsDocuments.get(document); + const jsLanguageService = await host.getLanguageService(jsDocument); + let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); + if (details) { + item.detail = ts.displayPartsToString(details.displayParts); + item.documentation = ts.displayPartsToString(details.documentation); + delete item.data; + } } return item; }, diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index 3a8b9b797cc..59cc742b2b8 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -54,6 +54,16 @@ export interface SemanticTokenData { modifierSet: number; } +export type CompletionItemData = { + languageId: string; + uri: string; + offset: number; +}; + +export function isCompletionItemData(value: any): value is CompletionItemData { + return value && typeof value.languageId === 'string' && typeof value.uri === 'string' && typeof value.offset === 'number'; +} + export interface LanguageMode { getId(): string; getSelectionRange?: (document: TextDocument, position: Position) => Promise; diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index a5bfebda600..f086cbf6898 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -12,65 +12,60 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-css-languageservice@^5.4.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz#69ea74c000bd653dfc8e458a1720d28b9ffa5cfb" - integrity sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg== +vscode-css-languageservice@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz#ccf94944e094dcc5833d1b4ac276994b698e9283" + integrity sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-html-languageservice@^4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.5.tgz#c0cc8ff3d824d16388bbac187e1828749eccf006" - integrity sha512-dbr10KHabB9EaK8lI0XZW7SqOsTfrNyT3Nuj0GoPi4LjGKUmMiLtsqzfedIzRTzqY+w0FiLdh0/kQrnQ0tLxrw== +vscode-html-languageservice@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.0.tgz#c68613f836d7fcff125183d78e6f1f0ff326fa55" + integrity sha512-KJG13z54aLszskp3ETf8b1EKDypr2Sf5RUsfR6OXmKqEl2ZUfyIxsWz4gbJWjPzoJZx/bGH0ZXVwxJ1rg8OKRQ== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" - -vscode-languageserver-textdocument@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff" - integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A== + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index d77421b701c..5157b15d877 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.1.tgz#20150976629663b3d33799a4ad25944a1535f7db" + integrity sha512-cvFq8drxdLRF8KN72WcV4lTEa9GqDiRwy9EbnYuoSCD9Jdk8zHFF49MmACC1qs4R9Ko/C1uMOmeLJmVi8EA0rQ== balanced-match@^1.0.0: version "1.0.0" @@ -44,44 +44,44 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index 9eb390b545c..e1fae6ffeb4 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -14,8 +14,10 @@ declare const Worker: { declare function fetch(uri: string, options: any): any; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/jsonServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -32,9 +34,16 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { schemaRequests }); + client = await startClient(context, newLanguageClient, { schemaRequests }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 5246257adc8..60225af8cef 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -16,9 +16,10 @@ import { import { LanguageClientOptions, RequestType, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, CommonLanguageClient + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient } from 'vscode-languageclient'; + import { hash } from './utils/hash'; import { createLanguageStatusItem } from './languageStatus'; @@ -56,7 +57,7 @@ namespace ResultLimitReachedNotification { export const type: NotificationType = new NotificationType('json/resultLimitReached'); } -interface Settings { +type Settings = { json?: { schemas?: JSONSchemaSettings[]; format?: { enable?: boolean }; @@ -67,13 +68,13 @@ interface Settings { proxy?: string; proxyStrictSSL?: boolean; }; -} +}; -export interface JSONSchemaSettings { +export type JSONSchemaSettings = { fileMatch?: string[]; url?: string; schema?: any; -} +}; namespace SettingIds { export const enableFormatter = 'json.format.enable'; @@ -94,7 +95,7 @@ export interface TelemetryReporter { }): void; } -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { schemaRequests: SchemaRequestService; @@ -108,7 +109,7 @@ export interface SchemaRequestService { export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const toDispose = context.subscriptions; @@ -218,178 +219,177 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const client = newLanguageClient('json', languageServerDescription, clientOptions); client.registerProposedFeatures(); - const disposable = client.start(); - toDispose.push(disposable); - client.onReady().then(() => { - isClientReady = true; + const schemaDocuments: { [uri: string]: boolean } = {}; - const schemaDocuments: { [uri: string]: boolean } = {}; - - // handle content request - client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { - const uri = Uri.parse(uriPath); - if (uri.scheme === 'untitled') { - return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); - } - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return workspace.openTextDocument(uri).then(doc => { - schemaDocuments[uri.toString()] = true; - return doc.getText(); - }, error => { - return Promise.reject(new ResponseError(2, error.toString())); - }); - } else if (schemaDownloadEnabled) { - if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { - /* __GDPR__ - "json.schema" : { - "owner": "aeschli", - "comment": "Measure the use of the Azure resource manager schemas", - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } - } - */ - runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); - } - return runtime.schemaRequests.getContent(uriPath).catch(e => { - return Promise.reject(new ResponseError(4, e.toString())); - }); - } else { - return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); - } - }); - - const handleContentChange = (uriString: string) => { - if (schemaDocuments[uriString]) { - client.sendNotification(SchemaContentChangeNotification.type, uriString); - return true; - } - return false; - }; - const handleActiveEditorChange = (activeEditor?: TextEditor) => { - if (!activeEditor) { - return; - } - - const activeDocUri = activeEditor.document.uri.toString(); - - if (activeDocUri && fileSchemaErrors.has(activeDocUri)) { - schemaResolutionErrorStatusBarItem.show(); - } else { - schemaResolutionErrorStatusBarItem.hide(); - } - }; - - toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); - toDispose.push(workspace.onDidCloseTextDocument(d => { - const uriString = d.uri.toString(); - if (handleContentChange(uriString)) { - delete schemaDocuments[uriString]; - } - fileSchemaErrors.delete(uriString); - })); - toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); - - const handleRetryResolveSchemaCommand = () => { - if (window.activeTextEditor) { - schemaResolutionErrorStatusBarItem.text = '$(watch)'; - const activeDocUri = window.activeTextEditor.document.uri.toString(); - client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => { - const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError); - if (schemaErrorIndex !== -1) { - // Show schema resolution errors in status bar only; ref: #51032 - const schemaResolveDiagnostic = diagnostics[schemaErrorIndex]; - fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message); - } else { - schemaResolutionErrorStatusBarItem.hide(); - } - schemaResolutionErrorStatusBarItem.text = '$(alert)'; - }); - } - }; - - toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); - - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); - - toDispose.push(extensions.onDidChange(_ => { - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); - })); - - // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652. - updateFormatterRegistration(); - toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); - - updateSchemaDownloadSetting(); - - toDispose.push(workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(SettingIds.enableFormatter)) { - updateFormatterRegistration(); - } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { - updateSchemaDownloadSetting(); - } - })); - - client.onNotification(ResultLimitReachedNotification.type, async message => { - const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; - if (shouldPrompt) { - const ok = localize('ok', "OK"); - const openSettings = localize('goToSetting', 'Open Settings'); - const neverAgain = localize('yes never again', "Don't Show Again"); - const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); - if (pick === neverAgain) { - await context.globalState.update(StorageIds.maxItemsExceededInformation, false); - } else if (pick === openSettings) { - await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); - } - } - }); - - toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); - - function updateFormatterRegistration() { - const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter); - if (!formatEnabled && rangeFormatting) { - rangeFormatting.dispose(); - rangeFormatting = undefined; - } else if (formatEnabled && !rangeFormatting) { - rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { - provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { - const filesConfig = workspace.getConfiguration('files', document); - const fileFormattingOptions = { - trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), - trimFinalNewlines: filesConfig.get('trimFinalNewlines'), - insertFinalNewline: filesConfig.get('insertFinalNewline'), - }; - const params: DocumentRangeFormattingParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - range: client.code2ProtocolConverter.asRange(range), - options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) - }; - - return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( - client.protocol2CodeConverter.asTextEdits, - (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); - return Promise.resolve([]); - } - ); - } - }); - } + // handle content request + client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { + const uri = Uri.parse(uriPath); + if (uri.scheme === 'untitled') { + return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); } - - function updateSchemaDownloadSetting() { - schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; - if (schemaDownloadEnabled) { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.'); - schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema'; - handleRetryResolveSchemaCommand(); - } else { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.'); - schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' }; + if (uri.scheme !== 'http' && uri.scheme !== 'https') { + return workspace.openTextDocument(uri).then(doc => { + schemaDocuments[uri.toString()] = true; + return doc.getText(); + }, error => { + return Promise.reject(new ResponseError(2, error.toString())); + }); + } else if (schemaDownloadEnabled) { + if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { + /* __GDPR__ + "json.schema" : { + "owner": "aeschli", + "comment": "Measure the use of the Azure resource manager schemas", + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } + } + */ + runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); } + return runtime.schemaRequests.getContent(uriPath).catch(e => { + return Promise.reject(new ResponseError(4, e.toString())); + }); + } else { + return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); } - }); + + await client.start(); + + isClientReady = true; + + const handleContentChange = (uriString: string) => { + if (schemaDocuments[uriString]) { + client.sendNotification(SchemaContentChangeNotification.type, uriString); + return true; + } + return false; + }; + const handleActiveEditorChange = (activeEditor?: TextEditor) => { + if (!activeEditor) { + return; + } + + const activeDocUri = activeEditor.document.uri.toString(); + + if (activeDocUri && fileSchemaErrors.has(activeDocUri)) { + schemaResolutionErrorStatusBarItem.show(); + } else { + schemaResolutionErrorStatusBarItem.hide(); + } + }; + + toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); + toDispose.push(workspace.onDidCloseTextDocument(d => { + const uriString = d.uri.toString(); + if (handleContentChange(uriString)) { + delete schemaDocuments[uriString]; + } + fileSchemaErrors.delete(uriString); + })); + toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); + + const handleRetryResolveSchemaCommand = () => { + if (window.activeTextEditor) { + schemaResolutionErrorStatusBarItem.text = '$(watch)'; + const activeDocUri = window.activeTextEditor.document.uri.toString(); + client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => { + const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError); + if (schemaErrorIndex !== -1) { + // Show schema resolution errors in status bar only; ref: #51032 + const schemaResolveDiagnostic = diagnostics[schemaErrorIndex]; + fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message); + } else { + schemaResolutionErrorStatusBarItem.hide(); + } + schemaResolutionErrorStatusBarItem.text = '$(alert)'; + }); + } + }; + + toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); + + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); + + toDispose.push(extensions.onDidChange(_ => { + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); + })); + + // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652. + updateFormatterRegistration(); + toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); + + updateSchemaDownloadSetting(); + + toDispose.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SettingIds.enableFormatter)) { + updateFormatterRegistration(); + } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { + updateSchemaDownloadSetting(); + } + })); + + client.onNotification(ResultLimitReachedNotification.type, async message => { + const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; + if (shouldPrompt) { + const ok = localize('ok', "OK"); + const openSettings = localize('goToSetting', 'Open Settings'); + const neverAgain = localize('yes never again', "Don't Show Again"); + const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); + if (pick === neverAgain) { + await context.globalState.update(StorageIds.maxItemsExceededInformation, false); + } else if (pick === openSettings) { + await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); + } + } + }); + + toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); + + function updateFormatterRegistration() { + const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter); + if (!formatEnabled && rangeFormatting) { + rangeFormatting.dispose(); + rangeFormatting = undefined; + } else if (formatEnabled && !rangeFormatting) { + rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { + provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { + const filesConfig = workspace.getConfiguration('files', document); + const fileFormattingOptions = { + trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), + trimFinalNewlines: filesConfig.get('trimFinalNewlines'), + insertFinalNewline: filesConfig.get('insertFinalNewline'), + }; + const params: DocumentRangeFormattingParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + range: client.code2ProtocolConverter.asRange(range), + options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) + }; + + return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( + client.protocol2CodeConverter.asTextEdits, + (error) => { + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); + return Promise.resolve([]); + } + ); + } + }); + } + } + + function updateSchemaDownloadSetting() { + schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; + if (schemaDownloadEnabled) { + schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.'); + schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema'; + handleRetryResolveSchemaCommand(); + } else { + schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.'); + schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' }; + } + } + + return client; } function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 9809074a5a9..752abe59a9f 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -5,7 +5,7 @@ import { ExtensionContext, OutputChannel, window, workspace } from 'vscode'; import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { promises as fs } from 'fs'; import * as path from 'path'; @@ -15,6 +15,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { JSONSchemaCache } from './schemaCache'; let telemetry: TelemetryReporter | undefined; +let client: BaseLanguageClient | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -45,11 +46,15 @@ export async function activate(context: ExtensionContext) { const schemaRequests = await getSchemaRequestService(context, log); - startClient(context, newLanguageClient, { schemaRequests, telemetry }); + client = await startClient(context, newLanguageClient, { schemaRequests, telemetry }); } -export function deactivate(): Promise { - return telemetry ? telemetry.dispose() : Promise.resolve(null); +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } + telemetry?.dispose(); } interface IPackageInfo { diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 4941fced265..1fc6753efbb 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -149,8 +149,8 @@ "dependencies": { "@vscode/extension-telemetry": "0.5.1", "request-light": "^0.5.8", - "vscode-languageclient": "^7.0.0", - "vscode-nls": "^5.0.0" + "vscode-languageclient": "^8.0.1", + "vscode-nls": "^5.0.1" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index cd04f24c3ab..a60bc177dc1 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,8 +14,8 @@ "dependencies": { "jsonc-parser": "^3.0.0", "request-light": "^0.5.8", - "vscode-json-languageservice": "^4.2.1", - "vscode-languageserver": "^7.0.0", + "vscode-json-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index e105859371f..b818bcefbf2 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -6,11 +6,11 @@ import { Connection, TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions + DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions, Diagnostic } from 'vscode-languageserver'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; -import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Diagnostic, Range, Position } from 'vscode-json-languageservice'; +import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { Utils, URI } from 'vscode-uri'; @@ -117,7 +117,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - const handledProtocols = params.initializationOptions?.handledSchemaProtocols; + const initializationOptions = params.initializationOptions as any || {}; + + const handledProtocols = initializationOptions?.handledSchemaProtocols; languageService = getLanguageService({ schemaRequestService: getSchemaRequestService(handledProtocols), @@ -138,11 +140,13 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return c; } + + clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); - dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions?.provideFormatter !== 'boolean'); + dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof initializationOptions.provideFormatter !== 'boolean'); foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { @@ -151,8 +155,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } : undefined, hoverProvider: true, documentSymbolProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions.provideFormatter === true, + documentFormattingProvider: initializationOptions.provideFormatter === true, colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, @@ -403,7 +407,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) runtime.timer.setImmediate(() => { const currDocument = documents.get(textDocument.uri); if (currDocument && currDocument.version === version) { - respond(diagnostics); // Send the computed diagnostics to VSCode. + respond(diagnostics as Diagnostic[]); // Send the computed diagnostics to VSCode. } }); }, error => { diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 5cd1cf5f480..2e74a77e9d3 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -22,51 +22,51 @@ request-light@^0.5.8: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== -vscode-json-languageservice@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz#94b6f471ece193bf4a1ef37f6ab5cce86d50a8b4" - integrity sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA== +vscode-json-languageservice@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.0.0.tgz#465d76cfe5dfeed4c3d5a2123b50e3f115bb7f78" + integrity sha512-1/+1TJBRFrfCNizmrW0fbIvguKzzO+4ehlqWCCnF7ioSACUGHrYop4ANb+eRnFaCP6fi3+i+llJC5Y5yAvmL6w== dependencies: jsonc-parser "^3.0.0" - vscode-languageserver-textdocument "^1.0.3" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-textdocument "^1.0.4" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-textdocument@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff" - integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A== +vscode-languageserver-textdocument@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" + integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 7e56398f062..3b6ad62624a 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -49,44 +49,44 @@ request-light@^0.5.8: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== yallist@^4.0.0: version "4.0.0" diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.html b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.html index 13fd84fbd06..be1437797f8 100644 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.html +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.html @@ -39,4 +39,4 @@ You signed out in another tab or window. Reload to refresh your session. - \ No newline at end of file + From 32e6d2c113f750cf0ed591b8c38a02c5a82a80a7 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 19 May 2022 12:54:28 +0200 Subject: [PATCH 152/350] extract logic for title suffix and prefix computations and use it for title menu --- .../parts/titlebar/titleMenuControl.ts | 14 ++++++- .../browser/parts/titlebar/windowTitle.ts | 41 ++++++++++++------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts index a68616a47d8..77e1ebb32eb 100644 --- a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts @@ -95,7 +95,19 @@ export class TitleMenuControl { } private _updateFromWindowTitle() { - this.workspaceTitle.innerText = windowTitle.workspaceName; + + // label: just workspace name and optional decorations + const { prefix, suffix } = windowTitle.getTitleDecorations(); + let label = windowTitle.workspaceName; + if (prefix) { + label = localize('label1', "{0} {1}", prefix, label); + } + if (suffix) { + label = localize('label2', "{0} {1}", label, suffix); + } + this.workspaceTitle.innerText = label; + + // tooltip: full windowTitle const kb = keybindingService.lookupKeybinding(action.id)?.getLabel(); const title = kb ? localize('title', "Search {0} ({1}) \u2014 {2}", windowTitle.workspaceName, kb, windowTitle.value) diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index cdae94ad277..1920790f520 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -108,28 +108,41 @@ export class WindowTitle extends Disposable { } private getWindowTitle(): string { - let title = this.doGetWindowTitle(); + let title = this.doGetWindowTitle() || this.productService.nameLong; + let { prefix, suffix } = this.getTitleDecorations(); + if (prefix) { + title = `${prefix} ${title}`; + } + if (suffix) { + title = `${title} ${suffix}`; + } + // Replace non-space whitespace + title = title.replace(/[^\S ]/g, ' '); + return title; + } + + getTitleDecorations() { + let prefix: string | undefined; + let suffix: string | undefined; if (this.properties.prefix) { - title = `${this.properties.prefix} ${title || this.productService.nameLong}`; + prefix = this.properties.prefix; + } + if (this.environmentService.isExtensionDevelopment) { + prefix = !prefix + ? WindowTitle.NLS_EXTENSION_HOST + : `${WindowTitle.NLS_EXTENSION_HOST} - ${prefix}`; } if (this.properties.isAdmin) { - title = `${title || this.productService.nameLong} ${WindowTitle.NLS_USER_IS_ADMIN}`; + suffix = WindowTitle.NLS_USER_IS_ADMIN; } - if (!this.properties.isPure) { - title = `${title || this.productService.nameLong} ${WindowTitle.NLS_UNSUPPORTED}`; + suffix = !suffix + ? WindowTitle.NLS_UNSUPPORTED + : `${suffix} ${WindowTitle.NLS_UNSUPPORTED}`; } - - if (this.environmentService.isExtensionDevelopment) { - title = `${WindowTitle.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`; - } - - // Replace non-space whitespace - title = title.replace(/[^\S ]/g, ' '); - - return title; + return { prefix, suffix }; } updateProperties(properties: ITitleProperties): void { From 04e34f25549ca272802d8532a5616b48ef262b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 19 May 2022 13:56:04 +0200 Subject: [PATCH 153/350] dispose native policy service on shutdown --- src/vs/code/electron-main/main.ts | 10 ++++++---- src/vs/code/node/cliProcessMain.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index cb41f40555e..cfa944e76c8 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -65,6 +65,7 @@ import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron- import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; /** * The main VS Code entry point. @@ -142,6 +143,8 @@ class CodeMain { private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, IPolicyService, ConfigurationService, StateMainService, BufferLogService, IProductService] { const services = new ServiceCollection(); + const disposables = new DisposableStore(); + process.once('exit', () => disposables.dispose()); // Product const productService = { _serviceBrand: undefined, ...product }; @@ -156,8 +159,7 @@ class CodeMain { // we are the only instance running, otherwise we'll have concurrent // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) const bufferLogService = new BufferLogService(); - const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService]); - process.once('exit', () => logService.dispose()); + const logService = disposables.add(new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService])); services.set(ILogService, logService); // Files @@ -170,8 +172,8 @@ class CodeMain { services.set(ILoggerService, new LoggerService(logService, fileService)); // Policy - const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) - : environmentMainService.policyFile ? new FilePolicyService(environmentMainService.policyFile, fileService, logService) + const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(productService.win32RegValueName)) + : environmentMainService.policyFile ? disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index bc630b7cae8..6c197b17661 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -133,8 +133,8 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // Policy - const policyService = isWindows && productService.win32RegValueName ? new NativePolicyService(productService.win32RegValueName) - : environmentService.policyFile ? new FilePolicyService(environmentService.policyFile, fileService, logService) + const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(productService.win32RegValueName)) + : environmentService.policyFile ? this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); From a63bebc2967e04ea6cb13fb4721977cff77bb685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 19 May 2022 16:02:56 +0200 Subject: [PATCH 154/350] build: :arrow_up: bump deemon (#149938) fixes deemon issue https://github.com/joaomoreno/deemon/issues/6 --- yarn.lock | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 21813ce9737..9aab56681f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2563,16 +2563,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bl@^4.0.3: +bl@^4.0.2, bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -3957,9 +3948,9 @@ decompress@^4.2.1: strip-dirs "^2.0.0" deemon@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/deemon/-/deemon-1.4.0.tgz#01c09cc23eec41e5d7ddac082eb52c3611d38dff" - integrity sha512-S0zK5tNTdVFsJZVUeKi/CYJn4zzhW0Y55lwXzv2hVxb7ajzAHf91BhE5y2xvx1X7czIZ6PHLPDj00TVAmylVXw== + version "1.7.1" + resolved "https://registry.yarnpkg.com/deemon/-/deemon-1.7.1.tgz#46cf8313fd320fac6ee944bbb1bea3e8c2a978ee" + integrity sha512-UcBiu6+4sZgkDrs7GT78FDkqmt9ZVN412XXT4Bm9sn5Hcrwv/M9HmCay3108Yvxl4Ti5n1UjkkuSVA1y3+FIvg== dependencies: bl "^4.0.2" tree-kill "^1.2.2" From 0db3cee2a42d8c180ff429edacf82de3b7dcedba Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 19 May 2022 11:20:05 +0200 Subject: [PATCH 155/350] let -> const --- .../folding/test/browser/foldingModel.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index 6b54319a21a..9db0f7f572d 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -347,20 +347,20 @@ suite('Folding Model', () => { let textModel = createTextModel(lines.join('\n')); try { - let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); + const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); - let ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ }); + const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ }); foldingModel.update(ranges); - let r1 = r(1, 2, false); - let r2 = r(3, 11, false); - let r3 = r(4, 10, false); - let r4 = r(5, 6, false); - let r5 = r(8, 9, false); + const r1 = r(1, 2, false); + const r2 = r(3, 11, false); + const r3 = r(4, 10, false); + const r4 = r(5, 6, false); + const r5 = r(8, 9, false); - let region1 = foldingModel.getRegionAtLine(r1.startLineNumber); - let region2 = foldingModel.getRegionAtLine(r2.startLineNumber); - let region3 = foldingModel.getRegionAtLine(r3.startLineNumber); + const region1 = foldingModel.getRegionAtLine(r1.startLineNumber); + const region2 = foldingModel.getRegionAtLine(r2.startLineNumber); + const region3 = foldingModel.getRegionAtLine(r3.startLineNumber); assertRanges(foldingModel, [r1, r2, r3, r4, r5]); From a3243b282c0a882ad45064f4555ee5a7fb985d2d Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 19 May 2022 08:12:29 -0700 Subject: [PATCH 156/350] Add indicator, fixes #148672 (#149294) * Add indicator, fixes #148672 This PR adds a default override indicator to extension-contributed language-specific default setting overrides. * Remove indicator from language tag settings --- .../preferences/browser/settingsTreeModels.ts | 9 ++++++++- .../preferences/common/preferencesModels.ts | 14 ++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6c4169e6b7c..e41f7298554 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -18,8 +18,9 @@ import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_S import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { ConfigurationScope, EditPresentationTypes } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { Registry } from 'vs/platform/registry/common/platform'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -221,6 +222,12 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.value = displayValue; this.scopeValue = isConfigured && overrideValues[targetSelector]; this.defaultValue = overrideValues.defaultValue ?? inspected.defaultValue; + + const registryValues = Registry.as(Extensions.Configuration).getConfigurationDefaultsOverrides(); + const overrideValueSource = registryValues.get(`[${languageSelector}]`)?.valuesSources?.get(this.setting.key); + if (overrideValueSource) { + this.setting.defaultValueSource = overrideValueSource; + } } else { this.value = displayValue; this.scopeValue = isConfigured && inspected[targetSelector]; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 000a74b77ca..d93923deb81 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -707,17 +707,19 @@ export class DefaultSettings extends Disposable { }); } - const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; - let defaultValueSource: string | IExtensionInfo | undefined; - if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { - defaultValueSource = registeredConfigurationProp.defaultValueSource; - } - let isLanguageTagSetting = false; if (OVERRIDE_PROPERTY_REGEX.test(key)) { isLanguageTagSetting = true; } + let defaultValueSource: string | IExtensionInfo | undefined; + if (!isLanguageTagSetting) { + const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; + if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { + defaultValueSource = registeredConfigurationProp.defaultValueSource; + } + } + result.push({ key, value, From 6e36be6f339e9b60a63ff34f7dd79075397f2c25 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Thu, 19 May 2022 10:49:29 -0700 Subject: [PATCH 157/350] Align menubar hover background with rest of titlebar items (#149956) --- src/vs/workbench/common/theme.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index bf7c818fb94..7258b951aff 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground, listActiveSelectionBackground, listActiveSelectionForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground, listActiveSelectionBackground, listActiveSelectionForeground, editorForeground, toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -763,8 +763,8 @@ export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionFore }, localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); export const MENUBAR_SELECTION_BACKGROUND = registerColor('menubar.selectionBackground', { - dark: transparent(Color.white, 0.1), - light: transparent(Color.black, 0.1), + dark: toolbarHoverBackground, + light: toolbarHoverBackground, hcDark: null, hcLight: null, }, localize('menubarSelectionBackground', "Background color of the selected menu item in the menubar.")); From 3f27a6103b6c61c7a552140e92e19de54cd03780 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 19 May 2022 11:01:45 -0700 Subject: [PATCH 158/350] Pick up latest TS nightly for building VS Code (#149959) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c9215abb13a..a8ffe4891ff 100644 --- a/package.json +++ b/package.json @@ -199,7 +199,7 @@ "style-loader": "^1.0.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220511", + "typescript": "^4.8.0-dev.20220518", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/yarn.lock b/yarn.lock index 9aab56681f1..382b89816cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11385,10 +11385,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.8.0-dev.20220511: - version "4.8.0-dev.20220511" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220511.tgz#42dd5ad99dcd345277c784949cc36a7b5d43fa18" - integrity sha512-MDo0tI/TRHCJ1sxochoU4LhT41C6jhEIkWneehqsxNYV84+d+C4HvD7Nq4DQrI03cIGvPJbdW2s3ZladROXE+A== +typescript@^4.8.0-dev.20220518: + version "4.8.0-dev.20220518" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220518.tgz#3082c89c764daece904480552b9f2c3f5ce79c56" + integrity sha512-yczRLiowXD4THxpe2DrClYXsmIRt9VPDft1dat4Le50mQwuUcmvdqD43o4hmlbNP7HpyeEYX51KXozGq1s7zlw== typical@^4.0.0: version "4.0.0" From 85bf8af5b9661b0c7f9587d33b3cb61499f6e4b8 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 19 May 2022 11:20:48 -0700 Subject: [PATCH 159/350] Improve Counter-Zoom Handling for Context Views (#149958) * fixes #149957 * fixes #149741 * use const * use const --- src/vs/base/browser/dom.ts | 18 ++++++++++++++++++ .../base/browser/ui/contextview/contextview.ts | 13 +++++++++---- .../electron-sandbox/contextmenuService.ts | 10 +--------- .../services/hover/browser/hoverWidget.ts | 14 +++++++++++++- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d30acf1fff9..d2fa1bd3ba8 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -577,6 +577,24 @@ export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePositi }; } +/** + * Returns the effective zoom on a given element before window zoom level is applied + */ +export function getDomNodeZoomLevel(domNode: HTMLElement): number { + let testElement: HTMLElement | null = domNode; + let zoom = 1.0; + do { + const elementZoomLevel = (getComputedStyle(testElement) as any).zoom; + if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { + zoom *= elementZoomLevel; + } + + testElement = testElement.parentElement; + } while (testElement !== null && testElement !== document.documentElement); + + return zoom; +} + export interface IStandardWindow { readonly scrollX: number; readonly scrollY: number; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index c34f711ae9f..1aa24bde272 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -262,11 +262,16 @@ export class ContextView extends Disposable { if (DOM.isHTMLElement(anchor)) { let elementPosition = DOM.getDomNodePagePosition(anchor); + // In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element + // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level. + // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5 + const zoom = DOM.getDomNodeZoomLevel(anchor); + around = { - top: elementPosition.top, - left: elementPosition.left, - width: elementPosition.width, - height: elementPosition.height + top: elementPosition.top * zoom, + left: elementPosition.left * zoom, + width: elementPosition.width * zoom, + height: elementPosition.height * zoom }; } else { around = { diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b491ce7079e..b4c0e11cbeb 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -105,15 +105,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // In areas where zoom is applied to the element or its ancestors, we need to adjust accordingly // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level. // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Coordinate Multiplier: 1.5 * 1.0 / 1.5 = 1.0 - let testElement: HTMLElement | null = anchor; - do { - const elementZoomLevel = (dom.getComputedStyle(testElement) as any).zoom; - if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { - zoom *= elementZoomLevel; - } - - testElement = testElement.parentElement; - } while (testElement !== null && testElement !== document.documentElement); + zoom *= dom.getDomNodeZoomLevel(anchor); x = elementPosition.left; y = elementPosition.top + elementPosition.height; diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index 837a4bdfd52..24f4173e3f8 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -230,7 +230,19 @@ export class HoverWidget extends Widget { this._hover.containerDomNode.classList.remove('right-aligned'); this._hover.contentsDomNode.style.maxHeight = ''; - const targetBounds = this._target.targetElements.map(e => e.getBoundingClientRect()); + const getZoomAccountedBoundingClientRect = (e: HTMLElement) => { + const zoom = dom.getDomNodeZoomLevel(e); + + const boundingRect = e.getBoundingClientRect(); + return { + top: boundingRect.top * zoom, + bottom: boundingRect.bottom * zoom, + right: boundingRect.right * zoom, + left: boundingRect.left * zoom, + }; + }; + + const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e)); const top = Math.min(...targetBounds.map(e => e.top)); const right = Math.max(...targetBounds.map(e => e.right)); const bottom = Math.max(...targetBounds.map(e => e.bottom)); From a00e1380401cb64023272c189772563f2885e8ab Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 19 May 2022 11:25:45 -0700 Subject: [PATCH 160/350] Changing dependency syntax for markdown-it-katex (#149962) Fixes #149291 --- extensions/markdown-math/package.json | 2 +- extensions/markdown-math/yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 8267989daee..d3bb21a553a 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -90,7 +90,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git" + "@iktakahiro/markdown-it-katex": "mjbvz/markdown-it-katex" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index 645b0080707..202cceb35b9 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -2,9 +2,9 @@ # yarn lockfile v1 -"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git": +"@iktakahiro/markdown-it-katex@mjbvz/markdown-it-katex": version "4.0.1" - resolved "https://github.com/mjbvz/markdown-it-katex.git#2e3736e4b916ee64ed92ebfabeaa94643612665a" + resolved "https://codeload.github.com/mjbvz/markdown-it-katex/tar.gz/2e3736e4b916ee64ed92ebfabeaa94643612665a" dependencies: katex "^0.13.0" From 2c9c8e6770df84d39c837f875818763c5f4cf49f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 19 May 2022 11:43:09 -0700 Subject: [PATCH 161/350] Fix lazy button layout and cursor (#149963) Fix #149875 --- .../contrib/debug/browser/media/debug.contribution.css | 3 +-- src/vs/workbench/contrib/debug/browser/media/repl.css | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index c061b736c55..36390d28f6a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -71,8 +71,7 @@ /* Expressions */ -.monaco-workbench .debug-pane .monaco-list-row .expression, -.monaco-workbench .debug-hover-widget .monaco-list-row .expression { +.monaco-workbench .monaco-list-row .expression { display: flex; } diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index d28e388e474..38c3431d3b3 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -23,6 +23,10 @@ line-height: var(--vscode-repl-line-height); } +.monaco-workbench .repl .repl-tree .monaco-tl-contents .expression .lazy-button { + cursor: pointer; +} + .monaco-workbench .repl .repl-tree .monaco-tl-twistie { background-position-y: calc(100% - (var(--vscode-repl-font-size-for-twistie))); } From 92b6c1be5462c276f82bb39244b9e155fc7a653a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 19 May 2022 11:50:28 -0700 Subject: [PATCH 162/350] Allow cancelling webview's readFileStream (#149964) This passes a cancellation token to `readFileStream`. This ensures we stop reading the file if the webview is disposed of --- src/vs/workbench/contrib/webview/browser/resourceLoading.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts index 9d717dd0c1c..cdff87cccd0 100644 --- a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts @@ -65,7 +65,7 @@ export async function loadLocalResource( const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime try { - const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }); + const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }, token); return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, result.mtime, mime); } catch (err) { if (err instanceof FileOperationError) { From 3b3a3e3e362dcd1a2390955cd2cca1bfe3318f87 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 19 May 2022 14:08:15 -0700 Subject: [PATCH 163/350] remote: fix wrong event name used for latency (#149973) --- .../workbench/contrib/remote/common/remote.contribution.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index a2d8b9671ae..7da92791ee6 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -243,20 +243,20 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio await timeout(EXT_HOST_LATENCY_DELAY); } - type RemoteConnectionFailureClassification = { + type RemoteConnectionLatencyClassification = { owner: 'connor4312'; comment: 'The latency to the remote extension host'; web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; }; - type RemoteConnectionFailureEvent = { + type RemoteConnectionLatencyEvent = { web: boolean; remoteName: string | undefined; latencyMs: number; }; - this._telemetryService.publicLog2('remoteConnectionFailure', { + this._telemetryService.publicLog2('remoteConnectionLatency', { web: isWeb, remoteName: getRemoteName(this._environmentService.remoteAuthority), latencyMs: bestLatency From 0ee05bb617c5585d9a329e7b34cc6ec9edca10dc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 19 May 2022 14:54:08 -0700 Subject: [PATCH 164/350] Pick up latest markdown-it-katex (#149978) Fixes #149682 --- extensions/markdown-math/yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index 202cceb35b9..2b979935781 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -4,7 +4,7 @@ "@iktakahiro/markdown-it-katex@mjbvz/markdown-it-katex": version "4.0.1" - resolved "https://codeload.github.com/mjbvz/markdown-it-katex/tar.gz/2e3736e4b916ee64ed92ebfabeaa94643612665a" + resolved "https://codeload.github.com/mjbvz/markdown-it-katex/tar.gz/1e0d09f9174b3ee1537de2586ce8d8a460284ce4" dependencies: katex "^0.13.0" From 55abdaee3e4f3a5ea5f7f95007708e4d50543339 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 19 May 2022 15:12:43 -0700 Subject: [PATCH 165/350] Render outputs a viewport above as well as below (#149879) * Also render outputs/markdowns a viewport above the current viewport, as well as below Towards #148164 * Update docs and add background color to svg --- build/hygiene.js | 2 +- .../viewportCustomMarkdown.ts | 2 +- .../cell-resize-above-viewport.drawio.svg | 406 +++++++++--------- .../notebook/browser/docs/notebook.layout.md | 5 +- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 4 +- .../notebook/browser/view/notebookCellList.ts | 11 +- .../browser/view/notebookRenderingCommon.ts | 2 +- 8 files changed, 217 insertions(+), 217 deletions(-) diff --git a/build/hygiene.js b/build/hygiene.js index a5d2b080172..1668e508ec9 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -54,7 +54,7 @@ function hygiene(some, linting = true) { const m = /([^\t\n\r\x20-\x7E⊃⊇✔︎✓🎯⚠️🛑🔴🚗🚙🚕🎉✨❗⇧⌥⌘×÷¦⋯…↑↓→→←↔⟷·•●◆▼⟪⟫┌└├⏎↩√φ]+)/g.exec(line); if (m) { console.error( - file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}". To suppress, use // allow-any-unicode-next-line` + file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}" (charCode: ${m[0].charCodeAt(0)}). To suppress, use // allow-any-unicode-next-line` ); errorCount++; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts index 81fd6185e59..212b41dbdb5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts @@ -70,7 +70,7 @@ class NotebookViewportContribution extends Disposable implements INotebookEditor return; } - const visibleRanges = this._notebookEditor.getVisibleRangesPlusViewportBelow(); + const visibleRanges = this._notebookEditor.getVisibleRangesPlusViewportAboveAndBelow(); cellRangesToIndexes(visibleRanges).forEach(index => { const cell = this._notebookEditor.cellAt(index); diff --git a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg index 909b8665213..6a2f80fc98d 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg +++ b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg @@ -1,42 +1,42 @@ - + - - - - - - - - + + + + + + + + - - + + - - + + - - + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View @@ -44,86 +44,86 @@ - +
    -
    -
    +
    +
    Webview top -1000
    - + Webview top -1000 - - + + - +
    -
    -
    +
    +
    Viewport
    - + Viewport - - - - + + + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View - - + + - - + + - + - + - +
    -
    -
    +
    +
    Grow Height by 50
    - + Grow Height by 50 @@ -131,16 +131,16 @@ - +
    -
    -
    +
    +
    scrollTop 1000
    - + scrollTop 1000 @@ -148,172 +148,172 @@ - +
    -
    -
    +
    +
    scrollTop 1000
    - + scrollTop 1000 - - + + - - - - + + + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View - - + + - - + + - + - + - +
    -
    -
    +
    +
    scrollTop 1050
    - + scrollTop 1050 - - + + - - + + - - + + - - + + - - - - + + + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View - - + + - - + + - + - + - +
    -
    -
    +
    +
    scrollTop 1050
    - + scrollTop 1050 - - + + - +
    -
    -
    +
    +
    Adjust top
    - + Adjust top - - + + - +
    -
    -
    +
    +
    UpdateScrollTop
    - + UpdateScrollTop @@ -323,16 +323,16 @@ - +
    -
    -
    +
    +
    Webview top -1000
    - + Webview top -1000 @@ -340,16 +340,16 @@ - +
    -
    -
    +
    +
    Webview top -1000
    - + Webview top -1000 @@ -357,16 +357,16 @@ - +
    -
    -
    +
    +
    Webview top -1000
    - + Webview top -1000 @@ -374,55 +374,55 @@ - - + + - - - - + + + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View - - + + - - + + - + - + - +
    -
    -
    +
    +
    scrollTop 1050
    - + scrollTop 1050 @@ -430,16 +430,16 @@ - +
    -
    -
    +
    +
    Webview top -950
    - + Webview top -950 @@ -447,55 +447,55 @@ - - + + - - - - + + + + - +
    -
    -
    +
    +
    Notebook List View
    - + Notebook List View - - + + - - + + - + - + - +
    -
    -
    +
    +
    scrollTop 1050
    - + scrollTop 1050 @@ -503,35 +503,35 @@ - +
    -
    -
    +
    +
    Webview top -950
    - + Webview top -950 - - + + - +
    -
    -
    +
    +
    Adjust top
    - + Adjust top @@ -543,16 +543,16 @@ - +
    -
    -
    +
    +
    1
    - + 1 @@ -560,16 +560,16 @@ - +
    -
    -
    +
    +
    2
    - + 2 @@ -577,16 +577,16 @@ - +
    -
    -
    +
    +
    3
    - + 3 @@ -594,16 +594,16 @@ - +
    -
    -
    +
    +
    4
    - + 4 @@ -611,16 +611,16 @@ - +
    -
    -
    +
    +
    4'
    - + 4' @@ -628,16 +628,16 @@ - +
    -
    -
    +
    +
    5
    - + 5 diff --git a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md index b289f68fd34..eb757a5a366 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md @@ -8,7 +8,6 @@ The notebook editor is a virtualized list view rendered in two contexts (mainfra - [Optimizations](#optimizations) - [Avoid flickering on resize of cells above current viewport](#avoid-flickering-on-resize-of-cells-above-current-viewport) - [Executing code cell followed by markdown cells](#executing-code-cell-followed-by-markdown-cells) - - [What's the catch](#whats-the-catch) - [Re-executing code cell followed by markdown cells](#re-executing-code-cell-followed-by-markdown-cells) - [Scrolling](#scrolling) @@ -172,6 +171,6 @@ If the new output is rendered within 200ms, users won't see the UI movement. ## Scrolling -Code cell outputs and markdown cells are rendered in the webview, which are async in nature. In order to have the cell outputs and markdown previews rendered when users scroll to them, we send rendering requests of cells in the next viewport when it's idle. Thus scrolling downwards is smoother. +Code cell outputs and markdown cells are rendered in the webview, which are async in nature. In order to have the cell outputs and markdown previews rendered when users scroll to them, we send rendering requests of cells in the next and previous viewport when it's idle. Thus scrolling is smoother. -However, we **don't** warmup the previous viewport as the cell height change of previous viewport might trigger the flickering of markdown cells in current viewport. Before we optimize this, do not do any warmup of cells before current viewport. +We also warm up all rendered markdown in the document, from top to bottom, when the browser is idle. We don't warm up outputs like this, since they can be more expensive to render than markdown. But outputs do get rendered when the user starts a search in outputs. diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 56012a1a041..75452af0bde 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -442,7 +442,7 @@ export interface INotebookEditor { */ getLayoutInfo(): NotebookLayoutInfo; - getVisibleRangesPlusViewportBelow(): ICellRange[]; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; /** * Focus the container of a cell (the monaco editor inside is not focused). diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 535e00b2aa6..8567bdded40 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2088,8 +2088,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._listViewInfoAccessor.setHiddenAreas(_ranges); } - getVisibleRangesPlusViewportBelow(): ICellRange[] { - return this._listViewInfoAccessor.getVisibleRangesPlusViewportBelow(); + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] { + return this._listViewInfoAccessor.getVisibleRangesPlusViewportAboveAndBelow(); } setScrollTop(scrollTop: number) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 859e007d6c9..0318aa6a288 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -600,15 +600,16 @@ export class NotebookCellList extends WorkbenchList implements ID return reduceCellRanges(ranges); } - getVisibleRangesPlusViewportBelow() { + getVisibleRangesPlusViewportAboveAndBelow() { if (this.view.length <= 0) { return []; } - const bottom = clamp(this.getViewScrollBottom() + this.renderHeight, 0, this.scrollHeight); - const topViewIndex = this.firstVisibleIndex; + const top = Math.max(this.getViewScrollTop() - this.renderHeight, 0); + const topViewIndex = this.view.indexAt(top); const topElement = this.view.element(topViewIndex); const topModelIndex = this._viewModel!.getCellIndex(topElement); + const bottom = clamp(this.getViewScrollBottom() + this.renderHeight, 0, this.scrollHeight); const bottomViewIndex = clamp(this.view.indexAt(bottom), 0, this.view.length - 1); const bottomElement = this.view.element(bottomViewIndex); const bottomModelIndex = this._viewModel!.getCellIndex(bottomElement); @@ -1584,8 +1585,8 @@ export class ListViewInfoAccessor extends Disposable { return this.list.setHiddenAreas(_ranges, true); } - getVisibleRangesPlusViewportBelow(): ICellRange[] { - return this.list?.getVisibleRangesPlusViewportBelow() ?? []; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] { + return this.list?.getVisibleRangesPlusViewportAboveAndBelow() ?? []; } triggerScroll(event: IMouseWheelEvent) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 0eb419e6d32..0bdb4ff330f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -53,7 +53,7 @@ export interface INotebookCellList { getViewIndex2(modelIndex: number): number | undefined; getModelIndex(cell: CellViewModel): number | undefined; getModelIndex2(viewIndex: number): number | undefined; - getVisibleRangesPlusViewportBelow(): ICellRange[]; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; focusElement(element: ICellViewModel): void; selectElements(elements: ICellViewModel[]): void; getFocusedElements(): ICellViewModel[]; From 08f247b5526de7a73aa9a743166bbb95fbb9146e Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 19 May 2022 15:50:55 -0700 Subject: [PATCH 166/350] debug: bump js profile visualizer (#149981) --- product.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product.json b/product.json index e80c93598d5..ae841799a09 100644 --- a/product.json +++ b/product.json @@ -61,7 +61,7 @@ }, { "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.1", + "version": "1.0.2", "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", "metadata": { "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", From 17c75e11805103026b943c8dc4d50a35f245f76f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 19 May 2022 16:07:14 -0700 Subject: [PATCH 167/350] Allow reusing webview origins across reloads (#149950) * Allow reusing webview origins across reloads Currently webviews are always loaded into a unique origin. This keeps them isolated but also means that we can't benefit from caching across window reloads This change adds a new `origin` option that you can pass to webviews which controls the origin they use. If this origin is not provided, we use a random one instead We then save off this origin for webview panels and restore it on window reloads. Webviews restore a little faster on window reload * Update webview fallback version --- product.json | 2 +- .../api/browser/mainThreadCustomEditors.ts | 1 + .../browser/customEditorInputFactory.ts | 5 ++++- .../contrib/webview/browser/overlayWebview.ts | 12 +++++++++++- .../workbench/contrib/webview/browser/pre/main.js | 4 ++-- src/vs/workbench/contrib/webview/browser/webview.ts | 8 ++++++++ .../contrib/webview/browser/webviewElement.ts | 13 +++++++++++-- .../browser/webviewEditorInputSerializer.ts | 4 ++++ .../environment/browser/environmentService.ts | 2 +- test/integration/browser/src/index.ts | 2 +- 10 files changed, 44 insertions(+), 9 deletions(-) diff --git a/product.json b/product.json index ae841799a09..ca0f29df1da 100644 --- a/product.json +++ b/product.json @@ -27,7 +27,7 @@ "licenseFileName": "LICENSE.txt", "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", "urlProtocol": "code-oss", - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/181b43c0e2949e36ecb623d8cc6de29d4fa2bae8/out/vs/workbench/contrib/webview/browser/pre/", + "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/3c8520fab514b9f56070214496b26ff68d1b1cb5/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ { "name": "ms-vscode.js-debug-companion", diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 6f247115e34..3be47943f1a 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -662,6 +662,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom } : undefined, webview: { id: primaryEditor.id, + origin: primaryEditor.webview.origin, options: primaryEditor.webview.options, state: primaryEditor.webview.state, } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index a4a7f7eea73..a1670e79b80 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -32,6 +32,7 @@ export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta { readonly webview: { readonly id: string; + readonly origin: string | undefined; readonly options: SerializedWebviewOptions; readonly state: any; }; @@ -107,9 +108,10 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } } -function reviveWebview(webviewService: IWebviewService, data: { id: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { +function reviveWebview(webviewService: IWebviewService, data: { id: string; origin: string | undefined; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { const webview = webviewService.createWebviewOverlay({ id: data.id, + origin: data.origin, options: { purpose: WebviewContentPurpose.CustomEditor, enableFindWidget: data.webviewOptions.enableFindWidget, @@ -185,6 +187,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location); const webview = reviveWebview(this._webviewService, { id, + origin: backupData.webview.origin, webviewOptions: restoreWebviewOptions(backupData.webview.options), contentOptions: restoreWebviewContentOptions(backupData.webview.options), state: backupData.webview.state, diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index b1a228fc65c..8c6cc98b2f4 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -8,6 +8,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -43,6 +44,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _findWidgetEnabled: IContextKey | undefined; public readonly id: string; + public readonly origin: string; public constructor( initInfo: WebviewInitInfo, @@ -53,6 +55,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { super(); this.id = initInfo.id; + this.origin = initInfo.origin ?? generateUuid(); + this._extension = initInfo.extension; this._options = initInfo.options; this._contentOptions = initInfo.contentOptions; @@ -186,7 +190,13 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } if (!this._webview.value) { - const webview = this._webviewService.createWebviewElement({ id: this.id, options: this._options, contentOptions: this._contentOptions, extension: this.extension }); + const webview = this._webviewService.createWebviewElement({ + id: this.id, + origin: this.origin, + options: this._options, + contentOptions: this._contentOptions, + extension: this.extension, + }); this._webview.value = webview; webview.state = this._state; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index d2c57f4aa10..74384c1550f 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -20,6 +20,7 @@ const isFirefox = ( const searchParams = new URL(location.toString()).searchParams; const ID = searchParams.get('id'); +const webviewOrigin = searchParams.get('origin'); const onElectron = searchParams.get('platform') === 'electron'; const expectedWorkerVersion = parseInt(searchParams.get('swVersion')); @@ -314,7 +315,6 @@ const hostMessaging = new class HostMessaging { }; const parentOrigin = searchParams.get('parentOrigin'); - const id = searchParams.get('id'); const hostname = location.hostname; @@ -327,7 +327,7 @@ const hostMessaging = new class HostMessaging { // compute a sha-256 composed of `parentOrigin` and `salt` converted to base 32 let parentOriginHash; try { - const strData = JSON.stringify({ parentOrigin, salt: id }); + const strData = JSON.stringify({ parentOrigin, salt: webviewOrigin }); const encoder = new TextEncoder(); const arrData = encoder.encode(strData); const hash = await crypto.subtle.digest('sha-256', arrData); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 3d50fee82a0..e9e90223d4e 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -151,8 +151,16 @@ export interface WebviewMessageReceivedEvent { export interface IWebview extends IDisposable { + /** + * External identifier of this webview. + */ readonly id: string; + /** + * The origin this webview itself is loaded from. May not be unique + */ + readonly origin: string; + html: string; contentOptions: WebviewContentOptions; localResourcesRoot: readonly URI[]; diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index fe286d9a832..77aaf6683f9 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -102,6 +102,7 @@ namespace WebviewState { export interface WebviewInitInfo { readonly id: string; + readonly origin?: string; readonly options: WebviewOptions; readonly contentOptions: WebviewContentOptions; @@ -118,7 +119,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD public readonly id: string; /** - * Unique identifier of this webview iframe element. + * The origin this webview itself is loaded from. May not be unique + */ + public readonly origin: string; + + /** + * Unique internal identifier of this webview's iframe element. */ private readonly iframeId: string; @@ -194,7 +200,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.id = initInfo.id; this.iframeId = generateUuid(); - this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.iframeId).then(id => this.encodedWebviewOrigin = id); + this.origin = initInfo.origin ?? this.iframeId; + + this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.origin).then(id => this.encodedWebviewOrigin = id); this.options = initInfo.options; this.extension = initInfo.extension; @@ -484,6 +492,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD // The extensionId and purpose in the URL are used for filtering in js-debug: const params: { [key: string]: string } = { id: this.iframeId, + origin: this.origin, swVersion: String(this._expectedServiceWorkerVersion), extensionId: extension?.id.value ?? '', platform: this.platform, diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index abed8dcdb16..ec4f4c9ef91 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -21,6 +21,7 @@ interface SerializedIconPath { export interface SerializedWebview { readonly id: string; + readonly origin: string | undefined; readonly viewType: string; readonly title: string; readonly options: SerializedWebviewOptions; @@ -33,6 +34,7 @@ export interface SerializedWebview { export interface DeserializedWebview { readonly id: string; + readonly origin: string | undefined; readonly viewType: string; readonly title: string; readonly webviewOptions: WebviewOptions; @@ -76,6 +78,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { return this._webviewWorkbenchService.reviveWebview({ webviewInitInfo: { id: data.id, + origin: data.origin, options: data.webviewOptions, contentOptions: data.contentOptions, extension: data.extension, @@ -102,6 +105,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { protected toJson(input: WebviewInput): SerializedWebview { return { id: input.id, + origin: input.webview.origin, viewType: input.viewType, title: input.getName(), options: { ...input.webview.options, ...input.webview.contentOptions }, diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index d41839b87ba..f205de47189 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -189,7 +189,7 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi const webviewExternalEndpointCommit = this.payload?.get('webviewExternalEndpointCommit'); return endpoint - .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '181b43c0e2949e36ecb623d8cc6de29d4fa2bae8') + .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '3c8520fab514b9f56070214496b26ff68d1b1cb5') .replace('{{quality}}', (webviewExternalEndpointCommit ? 'insider' : this.productService.quality) ?? 'insider'); } diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 18350685d5e..39a996b6ff4 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -65,7 +65,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); - const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","181b43c0e2949e36ecb623d8cc6de29d4fa2bae8"],["skipWelcome","true"]]`; + const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","3c8520fab514b9f56070214496b26ff68d1b1cb5"],["skipWelcome","true"]]`; if (path.extname(testWorkspacePath) === '.code-workspace') { await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`); From e9393b096d90a5ac49b1528d1ec60483fc6b993e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 May 2022 00:07:20 -0700 Subject: [PATCH 168/350] Inline webview main.js (#149983) For #145567 This inlines the `main.js` script that webview use. This saves us an extra request at the cost of currently having to maintain two copies of the source from `main.js` --- .../electron-main/webviewProtocolProvider.ts | 1 - .../webview/browser/pre/index-no-csp.html | 1131 +++++++++++++++- .../contrib/webview/browser/pre/index.html | 1134 ++++++++++++++++- .../contrib/webview/browser/pre/main.js | 1133 ---------------- 4 files changed, 2262 insertions(+), 1137 deletions(-) delete mode 100644 src/vs/workbench/contrib/webview/browser/pre/main.js diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index a0c161312fb..3da845d011a 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -14,7 +14,6 @@ export class WebviewProtocolProvider extends Disposable { private static validWebviewFilePaths = new Map([ ['/index.html', 'index.html'], ['/fake.html', 'fake.html'], - ['/main.js', 'main.js'], ['/service-worker.js', 'service-worker.js'], ]); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 6885c2488cd..625c46f3115 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -12,7 +12,1136 @@ - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 3f2ade8478f..457447af38e 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -4,7 +4,8 @@ - + - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js deleted file mode 100644 index 74384c1550f..00000000000 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ /dev/null @@ -1,1133 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check - -/// - -const isSafari = ( - navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && - navigator.userAgent && - navigator.userAgent.indexOf('CriOS') === -1 && - navigator.userAgent.indexOf('FxiOS') === -1 -); - -const isFirefox = ( - navigator.userAgent && - navigator.userAgent.indexOf('Firefox') >= 0 -); - -const searchParams = new URL(location.toString()).searchParams; -const ID = searchParams.get('id'); -const webviewOrigin = searchParams.get('origin'); -const onElectron = searchParams.get('platform') === 'electron'; -const expectedWorkerVersion = parseInt(searchParams.get('swVersion')); - -/** - * Use polling to track focus of main webview and iframes within the webview - * - * @param {Object} handlers - * @param {() => void} handlers.onFocus - * @param {() => void} handlers.onBlur - */ -const trackFocus = ({ onFocus, onBlur }) => { - const interval = 250; - let isFocused = document.hasFocus(); - setInterval(() => { - const isCurrentlyFocused = document.hasFocus(); - if (isCurrentlyFocused === isFocused) { - return; - } - isFocused = isCurrentlyFocused; - if (isCurrentlyFocused) { - onFocus(); - } else { - onBlur(); - } - }, interval); -}; - -const getActiveFrame = () => { - return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('active-frame')); -}; - -const getPendingFrame = () => { - return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('pending-frame')); -}; - -/** - * @template T - * @param {T | undefined | null} obj - * @return {T} - */ -function assertIsDefined(obj) { - if (typeof obj === 'undefined' || obj === null) { - throw new Error('Found unexpected null'); - } - return obj; -} - -const vscodePostMessageFuncName = '__vscode_post_message__'; - -const defaultStyles = document.createElement('style'); -defaultStyles.id = '_defaultStyles'; -defaultStyles.textContent = ` - html { - scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background); - } - - body { - background-color: transparent; - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); - font-weight: var(--vscode-font-weight); - font-size: var(--vscode-font-size); - margin: 0; - padding: 0 20px; - } - - img { - max-width: 100%; - max-height: 100%; - } - - a, a code { - color: var(--vscode-textLink-foreground); - } - - a:hover { - color: var(--vscode-textLink-activeForeground); - } - - a:focus, - input:focus, - select:focus, - textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; - } - - code { - color: var(--vscode-textPreformat-foreground); - } - - blockquote { - background: var(--vscode-textBlockQuote-background); - border-color: var(--vscode-textBlockQuote-border); - } - - kbd { - color: var(--vscode-editor-foreground); - border-radius: 3px; - vertical-align: middle; - padding: 1px 3px; - - background-color: hsla(0,0%,50%,.17); - border: 1px solid rgba(71,71,71,.4); - border-bottom-color: rgba(88,88,88,.4); - box-shadow: inset 0 -1px 0 rgba(88,88,88,.4); - } - .vscode-light kbd { - background-color: hsla(0,0%,87%,.5); - border: 1px solid hsla(0,0%,80%,.7); - border-bottom-color: hsla(0,0%,73%,.7); - box-shadow: inset 0 -1px 0 hsla(0,0%,73%,.7); - } - - ::-webkit-scrollbar { - width: 10px; - height: 10px; - } - - ::-webkit-scrollbar-corner { - background-color: var(--vscode-editor-background); - } - - ::-webkit-scrollbar-thumb { - background-color: var(--vscode-scrollbarSlider-background); - } - ::-webkit-scrollbar-thumb:hover { - background-color: var(--vscode-scrollbarSlider-hoverBackground); - } - ::-webkit-scrollbar-thumb:active { - background-color: var(--vscode-scrollbarSlider-activeBackground); - } - ::highlight(find-highlight) { - background-color: var(--vscode-editor-findMatchHighlightBackground); - } - ::highlight(current-find-highlight) { - background-color: var(--vscode-editor-findMatchBackground); - }`; - -/** - * @param {boolean} allowMultipleAPIAcquire - * @param {*} [state] - * @return {string} - */ -function getVsCodeApiScript(allowMultipleAPIAcquire, state) { - const encodedState = state ? encodeURIComponent(state) : undefined; - return /* js */` - globalThis.acquireVsCodeApi = (function() { - const originalPostMessage = window.parent['${vscodePostMessageFuncName}'].bind(window.parent); - const doPostMessage = (channel, data, transfer) => { - originalPostMessage(channel, data, transfer); - }; - - let acquired = false; - - let state = ${state ? `JSON.parse(decodeURIComponent("${encodedState}"))` : undefined}; - - return () => { - if (acquired && !${allowMultipleAPIAcquire}) { - throw new Error('An instance of the VS Code API has already been acquired'); - } - acquired = true; - return Object.freeze({ - postMessage: function(message, transfer) { - doPostMessage('onmessage', { message, transfer }, transfer); - }, - setState: function(newState) { - state = newState; - doPostMessage('do-update-state', JSON.stringify(newState)); - return newState; - }, - getState: function() { - return state; - } - }); - }; - })(); - delete window.parent; - delete window.top; - delete window.frameElement; - `; -} - -/** @type {Promise} */ -const workerReady = new Promise((resolve, reject) => { - if (!areServiceWorkersEnabled()) { - return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.')); - } - - const swPath = `service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${searchParams.get('remoteAuthority') ?? ''}`; - navigator.serviceWorker.register(swPath) - .then(() => navigator.serviceWorker.ready) - .then(async registration => { - /** - * @param {MessageEvent} event - */ - const versionHandler = async (event) => { - if (event.data.channel !== 'version') { - return; - } - - navigator.serviceWorker.removeEventListener('message', versionHandler); - if (event.data.version === expectedWorkerVersion) { - return resolve(); - } else { - console.log(`Found unexpected service worker version. Found: ${event.data.version}. Expected: ${expectedWorkerVersion}`); - console.log(`Attempting to reload service worker`); - - // If we have the wrong version, try once (and only once) to unregister and re-register - // Note that `.update` doesn't seem to work desktop electron at the moment so we use - // `unregister` and `register` here. - return registration.unregister() - .then(() => navigator.serviceWorker.register(swPath)) - .then(() => navigator.serviceWorker.ready) - .finally(() => { resolve(); }); - } - }; - navigator.serviceWorker.addEventListener('message', versionHandler); - - const postVersionMessage = (/** @type {ServiceWorker} */ controller) => { - controller.postMessage({ channel: 'version' }); - }; - - // At this point, either the service worker is ready and - // became our controller, or we need to wait for it. - // Note that navigator.serviceWorker.controller could be a - // controller from a previously loaded service worker. - const currentController = navigator.serviceWorker.controller; - if (currentController?.scriptURL.endsWith(swPath)) { - // service worker already loaded & ready to receive messages - postVersionMessage(currentController); - } else { - // either there's no controlling service worker, or it's an old one: - // wait for it to change before posting the message - const onControllerChange = () => { - navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); - postVersionMessage(navigator.serviceWorker.controller); - }; - navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); - } - }).catch(error => { - reject(new Error(`Could not register service workers: ${error}.`)); - }); -}); - -const hostMessaging = new class HostMessaging { - - constructor() { - this.channel = new MessageChannel(); - - /** @type {Map void>>} */ - this.handlers = new Map(); - - this.channel.port1.onmessage = (e) => { - const channel = e.data.channel; - const handlers = this.handlers.get(channel); - if (handlers) { - for (const handler of handlers) { - handler(e, e.data.args); - } - } else { - console.log('no handler for ', e); - } - }; - } - - /** - * @param {string} channel - * @param {any} data - * @param {any} [transfer] - */ - postMessage(channel, data, transfer) { - this.channel.port1.postMessage({ channel, data }, transfer); - } - - /** - * @param {string} channel - * @param {(event: MessageEvent, data: any) => void} handler - */ - onMessage(channel, handler) { - let handlers = this.handlers.get(channel); - if (!handlers) { - handlers = []; - this.handlers.set(channel, handlers); - } - handlers.push(handler); - } - - async signalReady() { - const start = (/** @type {string} */ parentOrigin) => { - window.parent.postMessage({ target: ID, channel: 'webview-ready', data: {} }, parentOrigin, [this.channel.port2]); - }; - - const parentOrigin = searchParams.get('parentOrigin'); - - const hostname = location.hostname; - - if (!crypto.subtle) { - // cannot validate, not running in a secure context - throw new Error(`Cannot validate in current context!`); - } - - // Here the `parentOriginHash()` function from `src/vs/workbench/common/webview.ts` is inlined - // compute a sha-256 composed of `parentOrigin` and `salt` converted to base 32 - let parentOriginHash; - try { - const strData = JSON.stringify({ parentOrigin, salt: webviewOrigin }); - const encoder = new TextEncoder(); - const arrData = encoder.encode(strData); - const hash = await crypto.subtle.digest('sha-256', arrData); - const hashArray = Array.from(new Uint8Array(hash)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 - parentOriginHash = BigInt(`0x${hashHex}`).toString(32).padStart(52, '0'); - } catch (err) { - throw err instanceof Error ? err : new Error(String(err)); - } - - if (hostname === parentOriginHash || hostname.startsWith(parentOriginHash + '.')) { - // validation succeeded! - return start(parentOrigin); - } - - throw new Error(`Expected '${parentOriginHash}' as hostname or subdomain!`); - } -}(); - -const unloadMonitor = new class { - - constructor() { - this.confirmBeforeClose = 'keyboardOnly'; - this.isModifierKeyDown = false; - - hostMessaging.onMessage('set-confirm-before-close', (_e, /** @type {string} */ data) => { - this.confirmBeforeClose = data; - }); - - hostMessaging.onMessage('content', (_e, /** @type {any} */ data) => { - this.confirmBeforeClose = data.confirmBeforeClose; - }); - - window.addEventListener('beforeunload', (event) => { - if (onElectron) { - return; - } - - switch (this.confirmBeforeClose) { - case 'always': { - event.preventDefault(); - event.returnValue = ''; - return ''; - } - case 'never': { - break; - } - case 'keyboardOnly': - default: { - if (this.isModifierKeyDown) { - event.preventDefault(); - event.returnValue = ''; - return ''; - } - break; - } - } - }); - } - - onIframeLoaded(/** @type {HTMLIFrameElement} */ frame) { - frame.contentWindow.addEventListener('keydown', e => { - this.isModifierKeyDown = e.metaKey || e.ctrlKey || e.altKey; - }); - - frame.contentWindow.addEventListener('keyup', () => { - this.isModifierKeyDown = false; - }); - } -}; - -// state -let firstLoad = true; -/** @type {any} */ -let loadTimeout; -let styleVersion = 0; - -/** @type {Array<{ readonly message: any, transfer?: ArrayBuffer[] }>} */ -let pendingMessages = []; - -const initData = { - /** @type {number | undefined} */ - initialScrollProgress: undefined, - - /** @type {{ [key: string]: string } | undefined} */ - styles: undefined, - - /** @type {string | undefined} */ - activeTheme: undefined, - - /** @type {string | undefined} */ - themeName: undefined, - - /** @type {boolean} */ - screenReader: false, - - /** @type {boolean} */ - reduceMotion: false, -}; - -hostMessaging.onMessage('did-load-resource', (_event, data) => { - navigator.serviceWorker.ready.then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); - }); -}); - -hostMessaging.onMessage('did-load-localhost', (_event, data) => { - navigator.serviceWorker.ready.then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data }); - }); -}); - -navigator.serviceWorker.addEventListener('message', event => { - switch (event.data.channel) { - case 'load-resource': - case 'load-localhost': - hostMessaging.postMessage(event.data.channel, event.data); - return; - } -}); -/** - * @param {HTMLDocument?} document - * @param {HTMLElement?} body - */ -const applyStyles = (document, body) => { - if (!document) { - return; - } - - if (body) { - body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast', 'vscode-reduce-motion', 'vscode-using-screen-reader'); - if (initData.activeTheme) { - body.classList.add(initData.activeTheme); - } - - if (initData.reduceMotion) { - body.classList.add('vscode-reduce-motion'); - } - - if (initData.screenReader) { - body.classList.add('vscode-using-screen-reader'); - } - - body.dataset.vscodeThemeKind = initData.activeTheme; - body.dataset.vscodeThemeName = initData.themeName || ''; - } - - if (initData.styles) { - const documentStyle = document.documentElement.style; - - // Remove stale properties - for (let i = documentStyle.length - 1; i >= 0; i--) { - const property = documentStyle[i]; - - // Don't remove properties that the webview might have added separately - if (property && property.startsWith('--vscode-')) { - documentStyle.removeProperty(property); - } - } - - // Re-add new properties - for (const variable of Object.keys(initData.styles)) { - documentStyle.setProperty(`--${variable}`, initData.styles[variable]); - } - } -}; - -/** - * @param {MouseEvent} event - */ -const handleInnerClick = (event) => { - if (!event?.view?.document) { - return; - } - - const baseElement = event.view.document.querySelector('base'); - - for (const pathElement of event.composedPath()) { - /** @type {any} */ - const node = pathElement; - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - if (node.getAttribute('href') === '#') { - event.view.scrollTo(0, 0); - } else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href === baseElement.href + node.hash))) { - const fragment = node.hash.slice(1); - const scrollTarget = event.view.document.getElementById(fragment) ?? event.view.document.getElementById(decodeURIComponent(fragment)); - scrollTarget?.scrollIntoView(); - } else { - hostMessaging.postMessage('did-click-link', node.href.baseVal || node.href); - } - event.preventDefault(); - return; - } - } -}; - -/** - * @param {MouseEvent} event - */ -const handleAuxClick = (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event?.view?.document) { - return; - } - - if (event.button === 1) { - for (const pathElement of event.composedPath()) { - /** @type {any} */ - const node = pathElement; - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - return; - } - } - } -}; - -/** - * @param {KeyboardEvent} e - */ -const handleInnerKeydown = (e) => { - // If the keypress would trigger a browser event, such as copy or paste, - // make sure we block the browser from dispatching it. Instead VS Code - // handles these events and will dispatch a copy/paste back to the webview - // if needed - if (isUndoRedo(e) || isPrint(e) || isFindEvent(e)) { - e.preventDefault(); - } else if (isCopyPasteOrCut(e)) { - if (onElectron) { - e.preventDefault(); - } else { - return; // let the browser handle this - } - } - - hostMessaging.postMessage('did-keydown', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); -}; -/** - * @param {KeyboardEvent} e - */ -const handleInnerUp = (e) => { - hostMessaging.postMessage('did-keyup', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); -}; - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isCopyPasteOrCut(e) { - const hasMeta = e.ctrlKey || e.metaKey; - const shiftInsert = e.shiftKey && e.key.toLowerCase() === 'insert'; - return (hasMeta && ['c', 'v', 'x'].includes(e.key.toLowerCase())) || shiftInsert; -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isUndoRedo(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && ['z', 'y'].includes(e.key.toLowerCase()); -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isPrint(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && e.key.toLowerCase() === 'p'; -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isFindEvent(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && e.key.toLowerCase() === 'f'; -} - -let isHandlingScroll = false; - -/** - * @param {WheelEvent} event - */ -const handleWheel = (event) => { - if (isHandlingScroll) { - return; - } - - hostMessaging.postMessage('did-scroll-wheel', { - deltaMode: event.deltaMode, - deltaX: event.deltaX, - deltaY: event.deltaY, - deltaZ: event.deltaZ, - detail: event.detail, - type: event.type - }); -}; - -/** - * @param {Event} event - */ -const handleInnerScroll = (event) => { - if (isHandlingScroll) { - return; - } - - const target = /** @type {HTMLDocument | null} */ (event.target); - const currentTarget = /** @type {Window | null} */ (event.currentTarget); - if (!currentTarget || !target?.body) { - return; - } - - const progress = currentTarget.scrollY / target.body.clientHeight; - if (isNaN(progress)) { - return; - } - - isHandlingScroll = true; - window.requestAnimationFrame(() => { - try { - hostMessaging.postMessage('did-scroll', progress); - } catch (e) { - // noop - } - isHandlingScroll = false; - }); -}; - -function handleInnerDragStartEvent(/** @type {DragEvent} */ e) { - if (e.defaultPrevented) { - // Extension code has already handled this event - return; - } - - if (!e.dataTransfer || e.shiftKey) { - return; - } - - // Only handle drags from outside editor for now - if (e.dataTransfer.items.length && Array.prototype.every.call(e.dataTransfer.items, item => item.kind === 'file')) { - hostMessaging.postMessage('drag-start'); - } -} - -/** - * @param {() => void} callback - */ -function onDomReady(callback) { - if (document.readyState === 'interactive' || document.readyState === 'complete') { - callback(); - } else { - document.addEventListener('DOMContentLoaded', callback); - } -} - -function areServiceWorkersEnabled() { - try { - return !!navigator.serviceWorker; - } catch (e) { - return false; - } -} - -/** - * @typedef {{ - * contents: string; - * options: { - * readonly allowScripts: boolean; - * readonly allowForms: boolean; - * readonly allowMultipleAPIAcquire: boolean; - * } - * state: any; - * cspSource: string; - * }} ContentUpdateData - */ - -/** - * @param {ContentUpdateData} data - * @return {string} - */ -function toContentHtml(data) { - const options = data.options; - const text = data.contents; - const newDocument = new DOMParser().parseFromString(text, 'text/html'); - - newDocument.querySelectorAll('a').forEach(a => { - if (!a.title) { - const href = a.getAttribute('href'); - if (typeof href === 'string') { - a.title = href; - } - } - }); - - // Set default aria role - if (!newDocument.body.hasAttribute('role')) { - newDocument.body.setAttribute('role', 'document'); - } - - // Inject default script - if (options.allowScripts) { - const defaultScript = newDocument.createElement('script'); - defaultScript.id = '_vscodeApiScript'; - defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, data.state); - newDocument.head.prepend(defaultScript); - } - - // Inject default styles - newDocument.head.prepend(defaultStyles.cloneNode(true)); - - applyStyles(newDocument, newDocument.body); - - // Strip out unsupported http-equiv tags - for (const metaElement of Array.from(newDocument.querySelectorAll('meta'))) { - const httpEquiv = metaElement.getAttribute('http-equiv'); - if (httpEquiv && !/^(content-security-policy|default-style|content-type)$/i.test(httpEquiv)) { - console.warn(`Removing unsupported meta http-equiv: ${httpEquiv}`); - metaElement.remove(); - } - } - - // Check for CSP - const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); - if (!csp) { - hostMessaging.postMessage('no-csp-found'); - } else { - try { - // Attempt to rewrite CSPs that hardcode old-style resource endpoint - const cspContent = csp.getAttribute('content'); - if (cspContent) { - const newCsp = cspContent.replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, data.cspSource); - csp.setAttribute('content', newCsp); - } - } catch (e) { - console.error(`Could not rewrite csp: ${e}`); - } - } - - // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off - // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden - return '\n' + newDocument.documentElement.outerHTML; -} - -onDomReady(() => { - if (!document.body) { - return; - } - - hostMessaging.onMessage('styles', (_event, data) => { - ++styleVersion; - - initData.styles = data.styles; - initData.activeTheme = data.activeTheme; - initData.themeName = data.themeName; - initData.reduceMotion = data.reduceMotion; - initData.screenReader = data.screenReader; - - const target = getActiveFrame(); - if (!target) { - return; - } - - if (target.contentDocument) { - applyStyles(target.contentDocument, target.contentDocument.body); - } - }); - - // propagate focus - hostMessaging.onMessage('focus', () => { - const activeFrame = getActiveFrame(); - if (!activeFrame || !activeFrame.contentWindow) { - // Focus the top level webview instead - window.focus(); - return; - } - - if (document.activeElement === activeFrame) { - // We are already focused on the iframe (or one of its children) so no need - // to refocus. - return; - } - - activeFrame.contentWindow.focus(); - }); - - // update iframe-contents - let updateId = 0; - hostMessaging.onMessage('content', async (_event, /** @type {ContentUpdateData} */ data) => { - const currentUpdateId = ++updateId; - try { - await workerReady; - } catch (e) { - console.error(`Webview fatal error: ${e}`); - hostMessaging.postMessage('fatal-error', { message: e + '' }); - return; - } - - if (currentUpdateId !== updateId) { - return; - } - - const options = data.options; - const newDocument = toContentHtml(data); - - const initialStyleVersion = styleVersion; - - const frame = getActiveFrame(); - const wasFirstLoad = firstLoad; - // keep current scrollY around and use later - /** @type {(body: HTMLElement, window: Window) => void} */ - let setInitialScrollPosition; - if (firstLoad) { - firstLoad = false; - setInitialScrollPosition = (body, window) => { - if (typeof initData.initialScrollProgress === 'number' && !isNaN(initData.initialScrollProgress)) { - if (window.scrollY === 0) { - window.scroll(0, body.clientHeight * initData.initialScrollProgress); - } - } - }; - } else { - const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? assertIsDefined(frame.contentWindow).scrollY : 0; - setInitialScrollPosition = (body, window) => { - if (window.scrollY === 0) { - window.scroll(0, scrollY); - } - }; - } - - // Clean up old pending frames and set current one as new one - const previousPendingFrame = getPendingFrame(); - if (previousPendingFrame) { - previousPendingFrame.setAttribute('id', ''); - document.body.removeChild(previousPendingFrame); - } - if (!wasFirstLoad) { - pendingMessages = []; - } - - const newFrame = document.createElement('iframe'); - newFrame.setAttribute('id', 'pending-frame'); - newFrame.setAttribute('frameborder', '0'); - - const sandboxRules = new Set(['allow-same-origin', 'allow-pointer-lock']); - if (options.allowScripts) { - sandboxRules.add('allow-scripts'); - sandboxRules.add('allow-downloads'); - } - if (options.allowForms) { - sandboxRules.add('allow-forms'); - } - newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' ')); - if (!isFirefox) { - newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); - } - // We should just be able to use srcdoc, but I wasn't - // seeing the service worker applying properly. - // Fake load an empty on the correct origin and then write real html - // into it to get around this. - newFrame.src = `./fake.html?id=${ID}`; - - newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; - document.body.appendChild(newFrame); - - /** - * @param {Document} contentDocument - */ - function onFrameLoaded(contentDocument) { - // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 - setTimeout(() => { - contentDocument.open(); - contentDocument.write(newDocument); - contentDocument.close(); - hookupOnLoadHandlers(newFrame); - - if (initialStyleVersion !== styleVersion) { - applyStyles(contentDocument, contentDocument.body); - } - }, 0); - } - - if (!options.allowScripts && isSafari) { - // On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired: https://bugs.webkit.org/show_bug.cgi?id=33604 - // Use polling instead. - const interval = setInterval(() => { - // If the frame is no longer mounted, loading has stopped - if (!newFrame.parentElement) { - clearInterval(interval); - return; - } - - const contentDocument = assertIsDefined(newFrame.contentDocument); - if (contentDocument.location.pathname.endsWith('/fake.html') && contentDocument.readyState !== 'loading') { - clearInterval(interval); - onFrameLoaded(contentDocument); - } - }, 10); - } else { - assertIsDefined(newFrame.contentWindow).addEventListener('DOMContentLoaded', e => { - const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; - onFrameLoaded(assertIsDefined(contentDocument)); - }); - } - - /** - * @param {Document} contentDocument - * @param {Window} contentWindow - */ - const onLoad = (contentDocument, contentWindow) => { - if (contentDocument && contentDocument.body) { - // Workaround for https://github.com/microsoft/vscode/issues/12865 - // check new scrollY and reset if necessary - setInitialScrollPosition(contentDocument.body, contentWindow); - } - - const newFrame = getPendingFrame(); - if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) { - const wasFocused = document.hasFocus(); - const oldActiveFrame = getActiveFrame(); - if (oldActiveFrame) { - document.body.removeChild(oldActiveFrame); - } - // Styles may have changed since we created the element. Make sure we re-style - if (initialStyleVersion !== styleVersion) { - applyStyles(newFrame.contentDocument, newFrame.contentDocument.body); - } - newFrame.setAttribute('id', 'active-frame'); - newFrame.style.visibility = 'visible'; - - contentWindow.addEventListener('scroll', handleInnerScroll); - contentWindow.addEventListener('wheel', handleWheel); - - if (wasFocused) { - contentWindow.focus(); - } - - pendingMessages.forEach((message) => { - contentWindow.postMessage(message.message, window.origin, message.transfer); - }); - pendingMessages = []; - } - }; - - /** - * @param {HTMLIFrameElement} newFrame - */ - function hookupOnLoadHandlers(newFrame) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - loadTimeout = setTimeout(() => { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(assertIsDefined(newFrame.contentDocument), assertIsDefined(newFrame.contentWindow)); - }, 200); - - const contentWindow = assertIsDefined(newFrame.contentWindow); - - contentWindow.addEventListener('load', function (e) { - const contentDocument = /** @type {Document} */ (e.target); - - if (loadTimeout) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(contentDocument, this); - } - }); - - // Bubble out various events - contentWindow.addEventListener('click', handleInnerClick); - contentWindow.addEventListener('auxclick', handleAuxClick); - contentWindow.addEventListener('keydown', handleInnerKeydown); - contentWindow.addEventListener('keyup', handleInnerUp); - contentWindow.addEventListener('contextmenu', e => { - if (e.defaultPrevented) { - // Extension code has already handled this event - return; - } - - e.preventDefault(); - hostMessaging.postMessage('did-context-menu', { - clientX: e.clientX, - clientY: e.clientY, - }); - }); - - contentWindow.addEventListener('dragenter', handleInnerDragStartEvent); - contentWindow.addEventListener('dragover', handleInnerDragStartEvent); - - unloadMonitor.onIframeLoaded(newFrame); - } - }); - - // Forward message to the embedded iframe - hostMessaging.onMessage('message', (_event, /** @type {{message: any, transfer?: ArrayBuffer[] }} */ data) => { - const pending = getPendingFrame(); - if (!pending) { - const target = getActiveFrame(); - if (target) { - assertIsDefined(target.contentWindow).postMessage(data.message, window.origin, data.transfer); - return; - } - } - pendingMessages.push(data); - }); - - hostMessaging.onMessage('initial-scroll-position', (_event, progress) => { - initData.initialScrollProgress = progress; - }); - - hostMessaging.onMessage('execCommand', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - assertIsDefined(target.contentDocument).execCommand(data); - }); - - /** @type {string | undefined} */ - let lastFindValue = undefined; - - hostMessaging.onMessage('find', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - - if (!data.previous && lastFindValue !== data.value) { - // Reset selection so we start search at the head of the last search - const selection = target.contentWindow.getSelection(); - selection.collapse(selection.anchorNode); - } - lastFindValue = data.value; - - const didFind = (/** @type {any} */ (target.contentWindow)).find( - data.value, - /* caseSensitive*/ false, - /* backwards*/ data.previous, - /* wrapAround*/ true, - /* wholeWord */ false, - /* searchInFrames*/ false, - false); - hostMessaging.postMessage('did-find', didFind); - }); - - hostMessaging.onMessage('find-stop', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - - lastFindValue = undefined; - - if (!data.clearSelection) { - const selection = target.contentWindow.getSelection(); - for (let i = 0; i < selection.rangeCount; i++) { - selection.removeRange(selection.getRangeAt(i)); - } - } - }); - - trackFocus({ - onFocus: () => hostMessaging.postMessage('did-focus'), - onBlur: () => hostMessaging.postMessage('did-blur') - }); - - (/** @type {any} */ (window))[vscodePostMessageFuncName] = (/** @type {string} */ command, /** @type {any} */ data) => { - switch (command) { - case 'onmessage': - case 'do-update-state': - hostMessaging.postMessage(command, data); - break; - } - }; - - // Also forward events before the contents of the webview have loaded - window.addEventListener('keydown', handleInnerKeydown); - window.addEventListener('dragenter', handleInnerDragStartEvent); - window.addEventListener('dragover', handleInnerDragStartEvent); - - hostMessaging.signalReady(); -}); From a952653a56b90ddada33960cac8e895c22eb7672 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 May 2022 09:54:10 +0200 Subject: [PATCH 169/350] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8ffe4891ff..ca6cec1a6bd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.68.0", - "distro": "dec8abea6c1191c66ab4a551172264be30a76e26", + "distro": "19bbe01e3aa85625f35f1fb2df6818a8e491670f", "author": { "name": "Microsoft Corporation" }, From fc9732a6833ad9b1ed9e4886dae1fc1e5f8db45e Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 20 May 2022 10:10:39 +0200 Subject: [PATCH 170/350] Spawn the extension host directly from the main process because fork() is now fast again (#150002) --- build/gulpfile.vscode.js | 1 - src/buildfile.js | 4 - src/vs/code/electron-main/app.ts | 4 +- .../extensionHostStarter.ts} | 288 +++++++++--------- .../workerMainProcessExtensionHostStarter.ts | 173 ----------- .../node/extensionHostStarterWorkerMain.ts | 66 ---- 6 files changed, 151 insertions(+), 385 deletions(-) rename src/vs/platform/extensions/{node/extensionHostStarterWorker.ts => electron-main/extensionHostStarter.ts} (78%) delete mode 100644 src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts delete mode 100644 src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 7b3a5043154..94b22026a07 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -65,7 +65,6 @@ const vscodeResources = [ 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', 'out-build/vs/platform/environment/node/userDataPath.js', - 'out-build/vs/platform/extensions/node/extensionHostStarterWorkerMain.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/src/buildfile.js b/src/buildfile.js index 8c30339da6e..6b49aa30083 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -38,10 +38,6 @@ exports.base = [ }, { name: 'vs/base/common/worker/simpleWorker', - }, - { - name: 'vs/platform/extensions/node/extensionHostStarterWorker', - exclude: ['vs/base/common/worker/simpleWorker'] } ]; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4bc4a432ceb..651c9ea09e2 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -46,7 +46,7 @@ import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -import { WorkerMainProcessExtensionHostStarter } from 'vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter'; +import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient'; @@ -641,7 +641,7 @@ export class CodeApplication extends Disposable { services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); // Extension Host Starter - services.set(IExtensionHostStarter, new SyncDescriptor(WorkerMainProcessExtensionHostStarter)); + services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts similarity index 78% rename from src/vs/platform/extensions/node/extensionHostStarterWorker.ts rename to src/vs/platform/extensions/electron-main/extensionHostStarter.ts index e0f8d8b03e4..493168507e8 100644 --- a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -3,141 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { canceled, SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { ChildProcess, fork } from 'child_process'; import { StringDecoder } from 'string_decoder'; import { Promises, timeout } from 'vs/base/common/async'; -import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; - -export interface IExtensionHostStarterWorkerHost { - logInfo(message: string): Promise; -} - -class ExtensionHostProcess extends Disposable { - - readonly _onStdout = this._register(new Emitter()); - readonly onStdout = this._onStdout.event; - - readonly _onStderr = this._register(new Emitter()); - readonly onStderr = this._onStderr.event; - - readonly _onMessage = this._register(new Emitter()); - readonly onMessage = this._onMessage.event; - - readonly _onError = this._register(new Emitter<{ error: SerializedError }>()); - readonly onError = this._onError.event; - - readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); - readonly onExit = this._onExit.event; - - private _process: ChildProcess | null = null; - private _hasExited: boolean = false; - - constructor( - public readonly id: string, - private readonly _host: IExtensionHostStarterWorkerHost - ) { - super(); - } - - start(opts: IExtensionHostProcessOptions): { pid: number } { - if (platform.isCI) { - this._host.logInfo(`Calling fork to start extension host...`); - } - const sw = StopWatch.create(false); - this._process = fork( - FileAccess.asFileUri('bootstrap-fork', require).fsPath, - ['--type=extensionHost', '--skipWorkspaceStorageLock'], - mixin({ cwd: cwd() }, opts), - ); - const forkTime = sw.elapsed(); - const pid = this._process.pid!; - - this._host.logInfo(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); - - const stdoutDecoder = new StringDecoder('utf-8'); - this._process.stdout?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); - this._onStdout.fire(strChunk); - }); - - const stderrDecoder = new StringDecoder('utf-8'); - this._process.stderr?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); - this._onStderr.fire(strChunk); - }); - - this._process.on('message', msg => { - this._onMessage.fire(msg); - }); - - this._process.on('error', (err) => { - this._onError.fire({ error: transformErrorForSerialization(err) }); - }); - - this._process.on('exit', (code: number, signal: string) => { - this._hasExited = true; - this._onExit.fire({ pid, code, signal }); - }); - - return { pid }; - } - - enableInspectPort(): boolean { - if (!this._process) { - return false; - } - - this._host.logInfo(`Enabling inspect port on extension host with pid ${this._process.pid}.`); - - interface ProcessExt { - _debugProcess?(n: number): any; - } - - if (typeof (process)._debugProcess === 'function') { - // use (undocumented) _debugProcess feature of node - (process)._debugProcess!(this._process.pid!); - return true; - } else if (!platform.isWindows) { - // use KILL USR1 on non-windows platforms (fallback) - this._process.kill('SIGUSR1'); - return true; - } else { - // not supported... - return false; - } - } - - kill(): void { - if (!this._process) { - return; - } - this._host.logInfo(`Killing extension host with pid ${this._process.pid}.`); - this._process.kill(); - } - - async waitForExit(maxWaitTimeMs: number): Promise { - if (!this._process) { - return; - } - const pid = this._process.pid; - this._host.logInfo(`Waiting for extension host with pid ${pid} to exit.`); - await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); - - if (!this._hasExited) { - // looks like we timed out - this._host.logInfo(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); - this._process.kill(); - } - } -} export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { _serviceBrand: undefined; @@ -145,11 +24,19 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter private static _lastId: number = 0; protected readonly _extHosts: Map; + private _shutdown = false; constructor( - private readonly _host: IExtensionHostStarterWorkerHost + @ILogService private readonly _logService: ILogService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { this._extHosts = new Map(); + + // On shutdown: gracefully await extension host shutdowns + lifecycleMainService.onWillShutdown((e) => { + this._shutdown = true; + e.join(this._waitForAllExit(6000)); + }); } dispose(): void { @@ -185,11 +72,14 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async createExtensionHost(): Promise<{ id: string }> { + if (this._shutdown) { + throw canceled(); + } const id = String(++ExtensionHostStarter._lastId); - const extHost = new ExtensionHostProcess(id, this._host); + const extHost = new ExtensionHostProcess(id, this._logService); this._extHosts.set(id, extHost); extHost.onExit(({ pid, code, signal }) => { - this._host.logInfo(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); + this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); setTimeout(() => { extHost.dispose(); this._extHosts.delete(id); @@ -199,10 +89,16 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { + if (this._shutdown) { + throw canceled(); + } return this._getExtHost(id).start(opts); } async enableInspectPort(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { return false; @@ -211,6 +107,9 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async kill(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { // already gone! @@ -219,13 +118,13 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter extHostProcess.kill(); } - async killAllNow(): Promise { + async _killAllNow(): Promise { for (const [, extHost] of this._extHosts) { extHost.kill(); } } - async waitForAllExit(maxWaitTimeMs: number): Promise { + async _waitForAllExit(maxWaitTimeMs: number): Promise { const exitPromises: Promise[] = []; for (const [, extHost] of this._extHosts) { exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); @@ -234,10 +133,121 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } } -/** - * The `create` function needs to be there by convention because - * we are loaded via the `vs/base/common/worker/simpleWorker` utility. - */ -export function create(host: IExtensionHostStarterWorkerHost) { - return new ExtensionHostStarter(host); +class ExtensionHostProcess extends Disposable { + + readonly _onStdout = this._register(new Emitter()); + readonly onStdout = this._onStdout.event; + + readonly _onStderr = this._register(new Emitter()); + readonly onStderr = this._onStderr.event; + + readonly _onMessage = this._register(new Emitter()); + readonly onMessage = this._onMessage.event; + + readonly _onError = this._register(new Emitter<{ error: SerializedError }>()); + readonly onError = this._onError.event; + + readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); + readonly onExit = this._onExit.event; + + private _process: ChildProcess | null = null; + private _hasExited: boolean = false; + + constructor( + public readonly id: string, + @ILogService private readonly _logService: ILogService, + ) { + super(); + } + + start(opts: IExtensionHostProcessOptions): { pid: number } { + if (platform.isCI) { + this._logService.info(`Calling fork to start extension host...`); + } + const sw = StopWatch.create(false); + this._process = fork( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + ['--type=extensionHost', '--skipWorkspaceStorageLock'], + mixin({ cwd: cwd() }, opts), + ); + const forkTime = sw.elapsed(); + const pid = this._process.pid!; + + this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); + + const stdoutDecoder = new StringDecoder('utf-8'); + this._process.stdout?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); + this._onStdout.fire(strChunk); + }); + + const stderrDecoder = new StringDecoder('utf-8'); + this._process.stderr?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); + this._onStderr.fire(strChunk); + }); + + this._process.on('message', msg => { + this._onMessage.fire(msg); + }); + + this._process.on('error', (err) => { + this._onError.fire({ error: transformErrorForSerialization(err) }); + }); + + this._process.on('exit', (code: number, signal: string) => { + this._hasExited = true; + this._onExit.fire({ pid, code, signal }); + }); + + return { pid }; + } + + enableInspectPort(): boolean { + if (!this._process) { + return false; + } + + this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`); + + interface ProcessExt { + _debugProcess?(n: number): any; + } + + if (typeof (process)._debugProcess === 'function') { + // use (undocumented) _debugProcess feature of node + (process)._debugProcess!(this._process.pid!); + return true; + } else if (!platform.isWindows) { + // use KILL USR1 on non-windows platforms (fallback) + this._process.kill('SIGUSR1'); + return true; + } else { + // not supported... + return false; + } + } + + kill(): void { + if (!this._process) { + return; + } + this._logService.info(`Killing extension host with pid ${this._process.pid}.`); + this._process.kill(); + } + + async waitForExit(maxWaitTimeMs: number): Promise { + if (!this._process) { + return; + } + const pid = this._process.pid; + this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); + await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); + + if (!this._hasExited) { + // looks like we timed out + this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._process.kill(); + } + } } diff --git a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts deleted file mode 100644 index f78d3e2db1f..00000000000 --- a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts +++ /dev/null @@ -1,173 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { canceled, SerializedError } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { Event } from 'vs/base/common/event'; -import { FileAccess } from 'vs/base/common/network'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Worker } from 'worker_threads'; -import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import type { ExtensionHostStarter, IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { StopWatch } from 'vs/base/common/stopwatch'; - -class NodeWorker implements IWorker { - - private readonly _worker: Worker; - - public readonly onError: Event; - public readonly onExit: Event; - public readonly onMessageError: Event; - - constructor(callback: IWorkerCallback, onErrorCallback: (err: any) => void) { - this._worker = new Worker( - FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath, - ); - this._worker.on('message', callback); - this._worker.on('error', onErrorCallback); - this.onError = Event.fromNodeEventEmitter(this._worker, 'error'); - this.onExit = Event.fromNodeEventEmitter(this._worker, 'exit'); - this.onMessageError = Event.fromNodeEventEmitter(this._worker, 'messageerror'); - } - - getId(): number { - return 1; - } - - postMessage(message: any, transfer: ArrayBuffer[]): void { - this._worker.postMessage(message, transfer); - } - - dispose(): void { - this._worker.terminate(); - } -} - -class ExtensionHostStarterWorkerHost implements IExtensionHostStarterWorkerHost { - constructor( - @ILogService private readonly _logService: ILogService - ) { } - - public async logInfo(message: string): Promise { - this._logService.info(message); - } -} - -export class WorkerMainProcessExtensionHostStarter implements IDisposable, IExtensionHostStarter { - _serviceBrand: undefined; - - private _proxy: ExtensionHostStarter | null; - private readonly _worker: SimpleWorkerClient; - private _shutdown = false; - - constructor( - @ILogService private readonly _logService: ILogService, - @ILifecycleMainService lifecycleMainService: ILifecycleMainService - ) { - this._proxy = null; - - const workerFactory: IWorkerFactory = { - create: (moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker => { - const worker = new NodeWorker(callback, onErrorCallback); - worker.onError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered an error:`); - this._logService.error(err); - }); - worker.onMessageError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered a message error:`); - this._logService.error(err); - }); - worker.onExit((exitCode) => this._logService.info(`ExtensionHostStarterWorker exited with code ${exitCode}.`)); - worker.postMessage(moduleId, []); - return worker; - } - }; - this._worker = new SimpleWorkerClient( - workerFactory, - 'vs/platform/extensions/node/extensionHostStarterWorker', - new ExtensionHostStarterWorkerHost(this._logService) - ); - this._initialize(); - - // On shutdown: gracefully await extension host shutdowns - lifecycleMainService.onWillShutdown((e) => { - this._shutdown = true; - if (this._proxy) { - e.join(this._proxy.waitForAllExit(6000)); - } - }); - } - - dispose(): void { - // Intentionally not killing the extension host processes - } - - async _initialize(): Promise { - this._proxy = await this._worker.getProxyObject(); - this._logService.info(`ExtensionHostStarterWorker created`); - } - - onDynamicStdout(id: string): Event { - return this._proxy!.onDynamicStdout(id); - } - - onDynamicStderr(id: string): Event { - return this._proxy!.onDynamicStderr(id); - } - - onDynamicMessage(id: string): Event { - return this._proxy!.onDynamicMessage(id); - } - - onDynamicError(id: string): Event<{ error: SerializedError }> { - return this._proxy!.onDynamicError(id); - } - - onDynamicExit(id: string): Event<{ code: number; signal: string }> { - return this._proxy!.onDynamicExit(id); - } - - async createExtensionHost(): Promise<{ id: string }> { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.createExtensionHost(); - } - - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { - const sw = StopWatch.create(false); - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - const timeout = setTimeout(() => { - this._logService.info(`ExtensionHostStarterWorker.start() did not return within 30s. This might be a problem.`); - }, 30000); - const result = await proxy.start(id, opts); - const duration = sw.elapsed(); - this._logService.info(`ExtensionHostStarterWorker.start() took ${duration} ms.`); - clearTimeout(timeout); - return result; - } - - async enableInspectPort(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.enableInspectPort(id); - } - - async kill(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.kill(id); - } -} diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts deleted file mode 100644 index b4efa0c798f..00000000000 --- a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -(function () { - 'use strict'; - - const loader = require('../../../loader'); - const bootstrap = require('../../../../bootstrap'); - const path = require('path'); - const parentPort = require('worker_threads').parentPort; - - // Bootstrap: NLS - const nlsConfig = bootstrap.setupNLS(); - - // Bootstrap: Loader - loader.config({ - baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../../'), { isWindows: process.platform === 'win32' }), - catchError: true, - nodeRequire: require, - nodeMain: __filename, - 'vs/nls': nlsConfig, - amdModulesPattern: /^vs\//, - recordStats: true - }); - - let isFirstMessage = true; - let beforeReadyMessages: any[] = []; - - const initialMessageHandler = (data: any) => { - if (!isFirstMessage) { - beforeReadyMessages.push(data); - return; - } - - isFirstMessage = false; - loadCode(data); - }; - - parentPort.on('message', initialMessageHandler); - - const loadCode = function (moduleId: string) { - loader([moduleId], function (ws: any) { - setTimeout(() => { - - const messageHandler = ws.create((msg: any, transfer?: ArrayBuffer[]) => { - parentPort.postMessage(msg, transfer); - }, null); - parentPort.off('message', initialMessageHandler); - parentPort.on('message', (data: any) => { - messageHandler.onmessage(data); - }); - while (beforeReadyMessages.length > 0) { - const msg = beforeReadyMessages.shift()!; - messageHandler.onmessage(msg); - } - - }); - }, (err: any) => console.error(err)); - }; - - parentPort.on('messageerror', (err: Error) => { - console.error(err); - }); -})(); From c4870970ab275e89338d44a6b9750baf25ae6597 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 20 May 2022 10:52:56 +0200 Subject: [PATCH 171/350] Run a job on `main` which only warms up the node modules cache (#150007) --- .github/workflows/basic.yml | 52 +++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 270e9523938..0d94b83350a 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -1,17 +1,16 @@ name: Basic checks on: - # push: - # branches: - # - main - # - release/* + push: + branches: + - main pull_request: branches: - main - - release/* jobs: main: + if: github.ref != 'refs/heads/main' name: Compilation, Unit and Integration Tests runs-on: ubuntu-latest timeout-minutes: 40 @@ -77,6 +76,7 @@ jobs: run: DISPLAY=:10 ./scripts/test-integration.sh hygiene: + if: github.ref != 'refs/heads/main' name: Hygiene and Layering runs-on: ubuntu-latest timeout-minutes: 40 @@ -140,3 +140,45 @@ jobs: - name: Run Trusted Types Checks run: yarn tsec-compile-check + + warm-cache: + name: Warm up node modules cache + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 From 29f30af16430560b20899649ce888fb22464e0e4 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 20 May 2022 05:23:39 -0700 Subject: [PATCH 172/350] Add an aria label to the checkall checkbox (#149994) --- src/vs/base/parts/quickinput/browser/quickInput.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 0dca8a2bd38..49fb9f19455 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1233,6 +1233,7 @@ export class QuickInputController extends Disposable { const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; + checkAll.setAttribute('aria-label', localize('quickInput.checkAll', "Toggle all checkboxes")); this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => { const checked = checkAll.checked; list.setAllVisibleChecked(checked); From d93881d70673837285d6e42137f6d45c1d9769f5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 May 2022 14:32:37 +0200 Subject: [PATCH 173/350] Fix #149518 (#150022) --- .../common/extensionGalleryService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index a6556211581..3f563b19e40 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -679,8 +679,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } if (compatible) { - const engine = await this.getEngine(rawGalleryExtensionVersion); - if (!isEngineValid(engine, this.productService.version, this.productService.date)) { + try { + const engine = await this.getEngine(rawGalleryExtensionVersion); + if (!isEngineValid(engine, this.productService.version, this.productService.date)) { + return false; + } + } catch (error) { + this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error)); return false; } } From 993c36c9205f156c1246f65161fa4796d460502f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 May 2022 09:17:57 -0700 Subject: [PATCH 174/350] Fix markdown ext errors accessing disposed webview (#149960) When reloading windows that need to restore a markdown preview, sometimes I'll see an error when the markdown preview tries accessing a disposed of webview. This seems to be cause caused by `provideTextDocumentContent`, where we end up disposing of the webview before an `await` resumes execution --- .../markdown-language-features/src/preview/preview.ts | 10 ++++++++-- .../src/preview/previewContentProvider.ts | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index f598dd62fa3..dd3c310a194 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -109,6 +109,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); public readonly onScroll = this._onScrollEmitter.event; + private readonly _disposeCts = this._register(new vscode.CancellationTokenSource()); + constructor( webview: vscode.WebviewPanel, resource: vscode.Uri, @@ -202,6 +204,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } override dispose() { + this._disposeCts.cancel(); + super.dispose(); this._disposed = true; @@ -286,7 +290,9 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { try { document = await vscode.workspace.openTextDocument(this._resource); } catch { - await this.showFileNotFoundError(); + if (!this._disposed) { + await this.showFileNotFoundError(); + } return; } @@ -306,7 +312,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { this.currentVersion = pendingVersion; const content = await (shouldReloadPage - ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state) + ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state, this._disposeCts.token) : this._contentProvider.markdownBody(document, this)); // Another call to `doUpdate` may have happened. diff --git a/extensions/markdown-language-features/src/preview/previewContentProvider.ts b/extensions/markdown-language-features/src/preview/previewContentProvider.ts index b7ff739ce91..8437017dc4f 100644 --- a/extensions/markdown-language-features/src/preview/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/preview/previewContentProvider.ts @@ -66,7 +66,8 @@ export class MarkdownContentProvider { resourceProvider: WebviewResourceProvider, previewConfigurations: MarkdownPreviewConfigurationManager, initialLine: number | undefined = undefined, - state?: any + state: any | undefined, + token: vscode.CancellationToken ): Promise { const sourceUri = markdownDocument.uri; const config = previewConfigurations.loadAndCacheConfiguration(sourceUri); @@ -89,6 +90,10 @@ export class MarkdownContentProvider { const csp = this.getCsp(resourceProvider, sourceUri, nonce); const body = await this.markdownBody(markdownDocument, resourceProvider); + if (token.isCancellationRequested) { + return { html: '', containingImages: [] }; + } + const html = ` From 90875504324fdb299a0996931990c4d801dc9e6a Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 20 May 2022 09:30:25 -0700 Subject: [PATCH 175/350] better z-indexes for title menu and quick pick (#150033) --- src/vs/base/parts/quickinput/browser/media/quickInput.css | 2 +- src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 802cc6adfe7..18cf3873a63 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -6,7 +6,7 @@ .quick-input-widget { position: absolute; width: 600px; - z-index: 4000; + z-index: 2550; padding: 0 1px 1px 1px; left: 50%; margin-left: -300px; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 8b516b544a7..1a1d9490d5c 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -81,7 +81,7 @@ /* Window Title Menu */ .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu { - z-index: 3000; + z-index: 2550; -webkit-app-region: no-drag; padding: 0 8px; } From e2dff56c3686f66cead62d4bd6850b15e9c0fb2d Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 20 May 2022 09:34:41 -0700 Subject: [PATCH 176/350] fixes #150034 (#150035) --- src/vs/base/parts/quickinput/browser/media/quickInput.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 18cf3873a63..efab21e77bf 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -10,6 +10,7 @@ padding: 0 1px 1px 1px; left: 50%; margin-left: -300px; + -webkit-app-region: no-drag; } .quick-input-titlebar { From de0b9e00eaea0c34abb0e9dc0bb764880e8ba2da Mon Sep 17 00:00:00 2001 From: zz <2418184580@qq.com> Date: Sat, 21 May 2022 00:42:27 +0800 Subject: [PATCH 177/350] Add "pnpm-lock.yaml" to the child patterns of "package.json" (#146869) Co-authored-by: Jackson Kearl --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index b162ad2ead5..aa794b52df6 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -500,7 +500,7 @@ configurationRegistry.registerConfiguration({ '*.jsx': '${capture}.js', '*.tsx': '${capture}.ts', 'tsconfig.json': 'tsconfig.*.json', - 'package.json': 'package-lock.json, yarn.lock', + 'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml', } } } From 4309c752dde6e07c0c0ace2878cc8ff2d44622e1 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 20 May 2022 10:56:14 -0700 Subject: [PATCH 178/350] use css px increments for view size changes (#150039) fixes #149488 --- src/vs/workbench/browser/actions/layoutActions.ts | 2 +- src/vs/workbench/browser/layout.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 8ace4020a72..4b4cc8b38f0 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -967,7 +967,7 @@ registerAction2(class extends Action2 { abstract class BaseResizeViewAction extends Action2 { - protected static readonly RESIZE_INCREMENT = 6.5; // This is a media-size percentage + protected static readonly RESIZE_INCREMENT = 60; // This is a css pixel size protected resizePart(widthChange: number, heightChange: number, layoutService: IWorkbenchLayoutService, partToResize?: Parts): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index acb2fc49198..2740f42fde9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform'; @@ -1328,8 +1328,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { - const sizeChangePxWidth = this.workbenchGrid.width * sizeChangeWidth / 100; - const sizeChangePxHeight = this.workbenchGrid.height * sizeChangeHeight / 100; + const sizeChangePxWidth = Math.sign(sizeChangeWidth) * computeScreenAwareSize(Math.abs(sizeChangeWidth)); + const sizeChangePxHeight = Math.sign(sizeChangeHeight) * computeScreenAwareSize(Math.abs(sizeChangeHeight)); let viewSize: IViewSize; From 279b7a8633558e8c95cba4d02d76e98cf996c741 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 11:38:31 -0700 Subject: [PATCH 179/350] Fix DecorationAddon being disposed of immediately Regressed in #148972 Fixes #149866 --- src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 6849cc1ff84..3285ae2c363 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -620,6 +620,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._decorationAddon.dispose(); this._decorationAddon = undefined; } + return; } if (this._decorationAddon) { this._decorationAddon.dispose(); From 351b4a914ea57139261d9d574341436ea66d0e8e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 11:41:17 -0700 Subject: [PATCH 180/350] Re-enable shell integration tests for macOS Fixes #149757 --- .../smoke/src/areas/terminal/terminal-shellIntegration.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index a5f5842b4c6..b94b40045f7 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -26,8 +26,7 @@ export function setup() { describe('Shell integration', function () { // TODO: Fix on Linux, some distros use sh as the default shell in which case shell integration will fail - // TODO: Fix on macOS, not sure reason for failing https://github.com/microsoft/vscode/issues/149757 - describe.skip('Decorations', function () { + (process.platform === 'win32' || process.platform === 'linux' ? describe.skip : describe)('Decorations', function () { describe('Should show default icons', function () { it('Placeholder', async () => { await terminal.createTerminal(); From 9e1c5d60ae7b68cf12293592fbb88b726a69fbb0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 11:29:55 -0700 Subject: [PATCH 181/350] Fix exception that could occur during render Fixes #149867 Fixes #149865 --- package.json | 4 ++-- remote/package.json | 4 ++-- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 ++++---- remote/yarn.lock | 16 ++++++++-------- yarn.lock | 16 ++++++++-------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index ca6cec1a6bd..b8992138afc 100644 --- a/package.json +++ b/package.json @@ -84,12 +84,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.47", + "xterm-headless": "4.19.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 75e83acbde5..3e833d3c5f6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.47", + "xterm-headless": "4.19.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index ece7c79fe5c..4ec42b01f12 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,7 +10,7 @@ "tas-client-umd": "0.1.5", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 97609688b6d..d8eac016d98 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -128,7 +128,7 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== diff --git a/remote/yarn.lock b/remote/yarn.lock index f023194fa26..52a1cc813ed 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -934,15 +934,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" - integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== +xterm-headless@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" + integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index 382b89816cd..70623ba39e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12205,15 +12205,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" - integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== +xterm-headless@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" + integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== y18n@^3.2.1: version "3.2.2" From e76ad21903c01bd420949366f8f26fc2a1d1f160 Mon Sep 17 00:00:00 2001 From: Esteban Dalel R Date: Fri, 20 May 2022 14:06:35 -0500 Subject: [PATCH 182/350] Fix typo (#150042) --- extensions/markdown-language-features/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index b2958199b91..5a97389a86a 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -28,7 +28,7 @@ "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", From 281f027d0065b407babd86866f0ea1b4c82ec0c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 12:16:53 -0700 Subject: [PATCH 183/350] Fix typo in autoReplies setting --- .../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 a90e8a5bf3b..e1e9d7887a9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -521,7 +521,7 @@ const terminalConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.AutoReplies]: { - markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that when encountered in the terminal will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "\\r"`', '`"\\r"`'), + markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that when encountered in the terminal will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), type: 'object', additionalProperties: { oneOf: [{ From 900b8700cb768150989e7f4ac02560bc8e18a4be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 12:31:47 -0700 Subject: [PATCH 184/350] Use correct terminal dimensions when restoring a terminal Fixes #146059 --- src/vs/platform/terminal/node/ptyService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index a3191a3230c..d9f8ae2b092 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -766,8 +766,8 @@ class XtermSerializer implements ITerminalSerializer { return { events: [ { - cols: this._xterm.getOption('cols'), - rows: this._xterm.getOption('rows'), + cols: this._xterm.cols, + rows: this._xterm.rows, data: serialized } ], From 3f5fed084c81964b92eea299bba0d534f1f0166b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 12:52:21 -0700 Subject: [PATCH 185/350] Normalize slashes on Windows to correct link URI Fixes #143832 --- .../contrib/terminal/browser/links/terminalLinkOpeners.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index 043a8472b5e..e03ec4351a5 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -170,7 +170,8 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { let resourceMatch: IResourceMatch | undefined; if (osPathModule(this._os).isAbsolute(sanitizedLink)) { const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const uri = URI.from({ scheme, path: sanitizedLink }); + const slashNormalizedPath = this._os === OperatingSystem.Windows ? sanitizedLink.replace(/\\/g, '/') : sanitizedLink; + const uri = URI.from({ scheme, path: slashNormalizedPath }); try { const fileStat = await this._fileService.stat(uri); resourceMatch = { uri, isDirectory: fileStat.isDirectory }; From da180bced6d4c6228649325a11b55a8f8aa8443a Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 20 May 2022 16:24:04 -0400 Subject: [PATCH 186/350] Hide explorer entries based on git ignore file (#149967) * Hide explorer entries based on git ignore file * Remove dependency on search * Address PR feedback * Add setting and make multi workspace aware * Use `isPathIncludedInTraversal` --- src/vs/platform/files/common/files.ts | 1 + .../files/browser/files.contribution.ts | 5 + .../files/browser/views/explorerViewer.ts | 92 ++++++++++++++++++- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 47e7c042f8f..f278a476e0e 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -1218,6 +1218,7 @@ export interface IFilesConfiguration { autoSaveDelay: number; eol: string; enableTrash: boolean; + excludeGitIgnore: boolean; hotExit: string; saveConflictResolution: 'askUser' | 'overwriteFileOnDisk'; }; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index aa794b52df6..15e889898d2 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -186,6 +186,11 @@ configurationRegistry.registerConfiguration({ 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, + 'files.excludeGitIgnore': { + type: 'boolean', + description: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `files.exclude`."), + default: false + }, 'files.eol': { 'type': 'string', 'enum': [ diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a24f390cf51..a857b3eec7d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IProgressService, ProgressLocation, } from 'vs/platform/progress/common/progress'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IFileService, FileKind, FileOperationError, FileOperationResult, FileChangeType } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -58,6 +58,8 @@ import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { BrowserFileUpload, ExternalFileImport, getMultipleFilesOverwriteConfirm } from 'vs/workbench/contrib/files/browser/fileImportExport'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { IgnoreFile } from 'vs/workbench/services/search/common/ignoreFile'; +import { ResourceSet, TernarySearchTree } from 'vs/base/common/map'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -605,20 +607,39 @@ export class FilesFilter implements ITreeFilter { private editorsAffectingFilter = new Set(); private _onDidChange = new Emitter(); private toDispose: IDisposable[] = []; + // List of ignoreFile resources. Used to detect changes to the ignoreFiles. + private ignoreFileResourcesPerRoot = new Map(); + // Ignore tree per root. Similar to `hiddenExpressionPerRoot` + private ignoreTreesPerRoot = new Map>(); constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExplorerService private readonly explorerService: IExplorerService, @IEditorService private readonly editorService: IEditorService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IFileService private readonly fileService: IFileService ) { this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration())); this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('files.exclude')) { + if (e.affectsConfiguration('files.exclude') || e.affectsConfiguration('files.excludeGitIgnore')) { this.updateConfiguration(); } })); + this.toDispose.push(this.fileService.onDidFilesChange(e => { + // Check to see if the update contains any of the ignoreFileResources + for (const [root, ignoreFileResourceSet] of this.ignoreFileResourcesPerRoot.entries()) { + ignoreFileResourceSet.forEach(async ignoreResource => { + if (e.contains(ignoreResource, FileChangeType.UPDATED)) { + await this.processIgnoreFile(root, ignoreResource, true); + } + if (e.contains(ignoreResource, FileChangeType.DELETED)) { + this.ignoreTreesPerRoot.get(root)?.delete(ignoreResource); + ignoreFileResourceSet.delete(ignoreResource); + } + }); + } + })); this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => { const editors = this.editorService.visibleEditors; let shouldFire = false; @@ -658,9 +679,25 @@ export class FilesFilter implements ITreeFilter { private updateConfiguration(): void { let shouldFire = false; + let updatedGitIgnoreSetting = false; this.contextService.getWorkspace().folders.forEach(folder => { const configuration = this.configurationService.getValue({ resource: folder.uri }); const excludesConfig: glob.IExpression = configuration?.files?.exclude || Object.create(null); + const parseIgnoreFile: boolean = configuration.files.excludeGitIgnore; + + // If we should be parsing ignoreFiles for this workspace and don't have an ignore tree initialize one + if (parseIgnoreFile && !this.ignoreTreesPerRoot.has(folder.uri.toString())) { + updatedGitIgnoreSetting = true; + this.ignoreFileResourcesPerRoot.set(folder.uri.toString(), new ResourceSet()); + this.ignoreTreesPerRoot.set(folder.uri.toString(), TernarySearchTree.forUris((uri) => this.uriIdentityService.extUri.ignorePathCasing(uri))); + } + + // If we shouldn't be parsing ignore files but have an ignore tree, clear the ignore tree + if (!parseIgnoreFile && this.ignoreTreesPerRoot.has(folder.uri.toString())) { + updatedGitIgnoreSetting = true; + this.ignoreFileResourcesPerRoot.delete(folder.uri.toString()); + this.ignoreTreesPerRoot.delete(folder.uri.toString()); + } if (!shouldFire) { const cached = this.hiddenExpressionPerRoot.get(folder.uri.toString()); @@ -672,13 +709,54 @@ export class FilesFilter implements ITreeFilter { this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) }); }); - if (shouldFire) { + if (shouldFire || updatedGitIgnoreSetting) { this.editorsAffectingFilter.clear(); this._onDidChange.fire(); } } + /** + * Given a .gitignore file resource, processes the resource and adds it to the ignore tree which hides explorer items + * @param root The root folder of the workspace as a string. Used for lookup key for ignore tree and resource list + * @param ignoreFileResource The resource of the .gitignore file + * @param update Whether or not we're updating an existing ignore file. If true it deletes the old entry + */ + private async processIgnoreFile(root: string, ignoreFileResource: URI, update?: boolean) { + // Get the name of the directory which the ignore file is in + const dirUri = dirname(ignoreFileResource); + const ignoreTree = this.ignoreTreesPerRoot.get(root); + if (!ignoreTree) { + return; + } + // If it's an update we remove the stale ignore file as we will be adding a new one + if (update) { + ignoreTree.delete(dirUri); + } + // Don't process a directory if we already have it in the tree + if (ignoreTree.has(dirUri)) { + return; + } + // Maybe we need a cancellation token here in case it's super long? + const content = await this.fileService.readFile(ignoreFileResource); + const ignoreParent = ignoreTree.findSubstr(dirUri); + const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path + path.sep, ignoreParent); + ignoreTree.set(dirUri, ignoreFile); + // If we haven't seen this resource before then we need to add it to the list of resources we're tracking + if (!this.ignoreFileResourcesPerRoot.get(root)?.has(ignoreFileResource)) { + this.ignoreFileResourcesPerRoot.get(root)?.add(ignoreFileResource); + } + // Notify the explorer of the change so we may ignore these files + this._onDidChange.fire(); + } + filter(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + // Add newly visited .gitignore files to the ignore tree + if (stat.name === '.gitignore') { + this.processIgnoreFile(stat.root.resource.toString(), stat.resource, false); + // Never hide .gitignore files + return true; + } + return this.isVisible(stat, parentVisibility); } @@ -694,7 +772,11 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - if ((cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) || stat.parent?.isExcluded) { + const globMatch = cached?.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name))); + // Small optimization to only traverse gitIgnore if the globMatch from fileExclude returned nothing + const ignoreFile = globMatch ? undefined : this.ignoreTreesPerRoot.get(stat.root.resource.toString())?.findSubstr(stat.resource); + const isIgnoredByIgnoreFile = !ignoreFile?.isPathIncludedInTraversal(stat.resource.path, stat.isDirectory); + if (isIgnoredByIgnoreFile || globMatch || stat.parent?.isExcluded) { stat.isExcluded = true; const editors = this.editorService.visibleEditors; const editor = editors.find(e => e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, stat.resource)); From 5062feaeaa99c45da77574e97a09d3a6f91401ae Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 20 May 2022 13:27:46 -0700 Subject: [PATCH 187/350] Fix #150028 (#150053) --- src/vs/workbench/contrib/files/browser/explorerService.ts | 2 +- src/vs/workbench/contrib/files/browser/files.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts index a1d5910f284..5b25d2e5590 100644 --- a/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -156,7 +156,7 @@ export class ExplorerService implements IExplorerService { const items = new Set(this.view.getContext(respectMultiSelection)); items.forEach(item => { - if (this.view?.isItemCollapsed(item) && item.nestedChildren) { + if (respectMultiSelection && this.view?.isItemCollapsed(item) && item.nestedChildren) { for (const child of item.nestedChildren) { items.add(child); } diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index b4b01687527..83b5e3b6d6b 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -23,7 +23,7 @@ export interface IExplorerService { readonly roots: ExplorerItem[]; readonly sortOrderConfiguration: ISortOrderConfiguration; - getContext(respectMultiSelection: boolean, includeNestedChildren?: boolean): ExplorerItem[]; + getContext(respectMultiSelection: boolean): ExplorerItem[]; hasViewFocus(): boolean; setEditable(stat: ExplorerItem, data: IEditableData | null): Promise; getEditable(): { stat: ExplorerItem; data: IEditableData } | undefined; From 1f5563fe8e8a7afabd1e05f938af599d527be753 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 13:35:42 -0700 Subject: [PATCH 188/350] Fix unit test to use new correct uri path --- .../terminal/test/browser/links/terminalLinkOpeners.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index b6efab0951f..a67d0f4b0d2 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -177,7 +177,7 @@ suite('Workbench - TerminalLinkOpeners', () => { type: TerminalBuiltinLinkType.Search }); deepStrictEqual(activationResult, { - link: 'file:///c%3A%5CUsers%5Chome%5Cfolder%5Cfile.txt', + link: 'file:///c%3A/Users/home/folder/file.txt', source: 'editor' }); From fdeba7d869aeb247aa5b26d7f02fc5b5c1b392df Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 20 May 2022 13:40:39 -0700 Subject: [PATCH 189/350] fix task status issues (#149976) --- .../tasks/browser/taskTerminalStatus.ts | 39 +++--- .../test/browser/taskTerminalStatus.test.ts | 117 ++++++++++++++++++ 2 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index ced0fd8cf8f..3c6dc5ca458 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { AbstractProblemCollector, StartStopProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; -import { TaskEvent, TaskEventKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { TaskEvent, TaskEventKind, TaskRunType } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, Task } from 'vs/workbench/contrib/tasks/common/taskService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; @@ -17,15 +17,18 @@ import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; interface TerminalData { terminal: ITerminalInstance; + task: Task; status: ITerminalStatus; problemMatcher: AbstractProblemCollector; + taskRunEnded: boolean; + disposeListener?: IDisposable; } const TASK_TERMINAL_STATUS_ID = 'task_terminal_status'; -const ACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: spinningLoading, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.active', "Task is running") }; -const SUCCEEDED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeeded', "Task succeeded") }; +export const ACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: spinningLoading, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.active', "Task is running") }; +export const SUCCEEDED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeeded', "Task succeeded") }; const SUCCEEDED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeededInactive', "Task succeeded and waiting...") }; -const FAILED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errors', "Task has errors") }; +export const FAILED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errors', "Task has errors") }; const FAILED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errorsInactive', "Task has errors and is waiting...") }; const WARNING_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.warning, severity: Severity.Warning, tooltip: nls.localize('taskTerminalStatus.warnings', "Task has warnings") }; const WARNING_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.warning, severity: Severity.Warning, tooltip: nls.localize('taskTerminalStatus.warningsInactive', "Task has warnings and is waiting...") }; @@ -33,7 +36,7 @@ const INFO_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: C const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.info, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.infosInactive', "Task has infos and is waiting...") }; export class TaskTerminalStatus extends Disposable { - private terminalMap: Map = new Map(); + private terminalMap: Map = new Map(); constructor(taskService: ITaskService) { super(); @@ -50,15 +53,15 @@ 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); - this.terminalMap.set(task, { terminal, status, problemMatcher }); + this.terminalMap.set(task._id, { terminal, task, status, problemMatcher, taskRunEnded: false }); } private terminalFromEvent(event: TaskEvent): TerminalData | undefined { - if (!event.__task || !this.terminalMap.get(event.__task)) { + if (!event.__task) { return undefined; } - return this.terminalMap.get(event.__task); + return this.terminalMap.get(event.__task._id); } private eventEnd(event: TaskEvent) { @@ -66,13 +69,11 @@ export class TaskTerminalStatus extends Disposable { if (!terminalData) { return; } - - this.terminalMap.delete(event.__task!); - + terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); - } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { + } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -83,7 +84,7 @@ export class TaskTerminalStatus extends Disposable { private eventInactive(event: TaskEvent) { const terminalData = this.terminalFromEvent(event); - if (!terminalData || !terminalData.problemMatcher) { + if (!terminalData || !terminalData.problemMatcher || terminalData.taskRunEnded) { return; } terminalData.terminal.statusList.remove(terminalData.status); @@ -103,10 +104,16 @@ export class TaskTerminalStatus extends Disposable { if (!terminalData) { return; } - + if (!terminalData.disposeListener) { + terminalData.disposeListener = terminalData.terminal.onDisposed(() => { + this.terminalMap.delete(event.__task?._id!); + terminalData.disposeListener?.dispose(); + }); + } + terminalData.taskRunEnded = false; terminalData.terminal.statusList.remove(terminalData.status); // We don't want to show an infinite status for a background task that doesn't have a problem matcher. - if ((terminalData.problemMatcher instanceof StartStopProblemCollector) || (terminalData.problemMatcher?.problemMatchers.length > 0)) { + if ((terminalData.problemMatcher instanceof StartStopProblemCollector) || (terminalData.problemMatcher?.problemMatchers.length > 0) || event.runType === TaskRunType.SingleRun) { terminalData.terminal.statusList.add(ACTIVE_TASK_STATUS); } } diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts new file mode 100644 index 00000000000..3b00f83eaf5 --- /dev/null +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ok } from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ACTIVE_TASK_STATUS, FAILED_TASK_STATUS, SUCCEEDED_TASK_STATUS, TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; +import { AbstractProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; +import { CommonTask, TaskEvent, TaskEventKind, TaskRunType } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService, Task } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalStatus, ITerminalStatusList, TerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; + +class TestTaskService implements Partial { + private readonly _onDidStateChange: Emitter = new Emitter(); + public get onDidStateChange(): Event { + return this._onDidStateChange.event; + } + public triggerStateChange(event: TaskEvent): void { + this._onDidStateChange.fire(event); + } +} + +class TestTerminal implements Partial { + statusList: TerminalStatusList = new TerminalStatusList(new TestConfigurationService()); +} + +class TestTask extends CommonTask { + protected getFolderId(): string | undefined { + throw new Error('Method not implemented.'); + } + protected fromObject(object: any): Task { + throw new Error('Method not implemented.'); + } +} + +class TestProblemCollector implements Partial { + +} + +suite('Task Terminal Status', () => { + let instantiationService: TestInstantiationService; + let taskService: TestTaskService; + let taskTerminalStatus: TaskTerminalStatus; + let testTerminal: ITerminalInstance; + let testTask: Task; + let problemCollector: AbstractProblemCollector; + setup(() => { + instantiationService = new TestInstantiationService(); + taskService = new TestTaskService(); + taskTerminalStatus = instantiationService.createInstance(TaskTerminalStatus, taskService); + testTerminal = instantiationService.createInstance(TestTerminal); + testTask = instantiationService.createInstance(TestTask); + problemCollector = instantiationService.createInstance(TestProblemCollector); + }); + 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 }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.End, exitCode: 2 }); + await poll(async () => Promise.resolve(), () => testTerminal?.statusList.primary?.id === FAILED_TASK_STATUS.id, 'terminal status should be updated'); + }); + test('Should add active status when a non-background task is run for a second time in the same terminal', async () => { + taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted, runType: TaskRunType.SingleRun }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + }); +}); + +function assertStatus(actual: ITerminalStatusList, expected: ITerminalStatus): void { + ok(actual.statuses.length === 1, '# of statuses'); + ok(actual.primary?.id === expected.id, 'ID'); + ok(actual.primary?.severity === expected.severity, 'Severity'); +} + +async function poll( + fn: () => Thenable, + acceptFn: (result: T) => boolean, + timeoutMessage: string, + retryCount: number = 200, + retryInterval: number = 10 // millis +): Promise { + let trial = 1; + let lastError: string = ''; + + while (true) { + if (trial > retryCount) { + throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.\r${lastError}`); + } + + let result; + try { + result = await fn(); + if (acceptFn(result)) { + return result; + } else { + lastError = 'Did not pass accept function'; + } + } catch (e: any) { + lastError = Array.isArray(e.stack) ? e.stack.join('\n') : e.stack; + } + + await new Promise(resolve => setTimeout(resolve, retryInterval)); + trial++; + } +} From ecf7f9cfd9ab5460cc2203fb1eb56ae2569157e6 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 20 May 2022 17:34:17 -0400 Subject: [PATCH 190/350] Add scope resource (#150056) --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 5 +++-- 1 file changed, 3 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 15e889898d2..cd4f1fd7ee5 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -188,8 +188,9 @@ configurationRegistry.registerConfiguration({ }, 'files.excludeGitIgnore': { type: 'boolean', - description: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `files.exclude`."), - default: false + markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."), + default: false, + scope: ConfigurationScope.RESOURCE }, 'files.eol': { 'type': 'string', From 96cae45ba98cf6f221e7933b535cd9e9b6a09e2e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 May 2022 15:54:21 -0700 Subject: [PATCH 191/350] Rename skipPaths to ignoreLinks (#150064) This change renames the experimental skipPaths setting to ignoreLinks. This setting applies to all types of links, and can also be used to ignore links to headers --- .../markdown-language-features/package.json | 4 +- .../package.nls.json | 2 +- .../src/languageFeatures/diagnostics.ts | 57 +++++++++++-------- .../src/test/diagnostic.test.ts | 43 +++++++++++--- 4 files changed, 71 insertions(+), 35 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index b1d4d1bc529..4ef07a0dd65 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -453,10 +453,10 @@ "error" ] }, - "markdown.experimental.validate.fileLinks.skipPaths": { + "markdown.experimental.validate.ignoreLinks": { "type": "array", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.skipPaths.description%", + "markdownDescription": "%configuration.markdown.experimental.validate.ignoreLinks.description%", "items": { "type": "string" } diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 5a97389a86a..2c8977cf2e0 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -33,6 +33,6 @@ "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fileLinks.skipPaths.description": "Configure glob patterns for links to treat as valid, even if they don't exist in the workspace. For example `/about` would make the link `[about](/about)` valid, while the glob `/assets/**/*.svg` would let you link to any `.svg` asset under the `assets` directory", + "configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index eb4c436afd5..89e6b29f240 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -39,7 +39,7 @@ export interface DiagnosticOptions { readonly validateReferences: DiagnosticLevel; readonly validateOwnHeaders: DiagnosticLevel; readonly validateFilePaths: DiagnosticLevel; - readonly skipPaths: readonly string[]; + readonly ignoreLinks: readonly string[]; } function toSeverity(level: DiagnosticLevel): vscode.DiagnosticSeverity | undefined { @@ -64,7 +64,7 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf || e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled') || e.affectsConfiguration('markdown.experimental.validate.headerLinks.enabled') || e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.skipPaths') + || e.affectsConfiguration('markdown.experimental.validate.ignoreLinks') ) { this._onDidChange.fire(); } @@ -78,7 +78,7 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf validateReferences: config.get('experimental.validate.referenceLinks.enabled', DiagnosticLevel.ignore), validateOwnHeaders: config.get('experimental.validate.headerLinks.enabled', DiagnosticLevel.ignore), validateFilePaths: config.get('experimental.validate.fileLinks.enabled', DiagnosticLevel.ignore), - skipPaths: config.get('experimental.validate.fileLinks.skipPaths', []), + ignoreLinks: config.get('experimental.validate.ignoreLinks', []), }; } } @@ -216,13 +216,13 @@ class LinkWatcher extends Disposable { } } -class FileDoesNotExistDiagnostic extends vscode.Diagnostic { +class LinkDoesNotExistDiagnostic extends vscode.Diagnostic { - public readonly path: string; + public readonly link: string; - constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, path: string) { + constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, link: string) { super(range, message, severity); - this.path = path; + this.link = link; } } @@ -424,10 +424,13 @@ export class DiagnosticComputer { && link.href.fragment && !toc.lookup(link.href.fragment) ) { - diagnostics.push(new vscode.Diagnostic( - link.source.hrefRange, - localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), - severity)); + if (!this.isIgnoredLink(options, link.source.text)) { + diagnostics.push(new LinkDoesNotExistDiagnostic( + link.source.hrefRange, + localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), + severity, + link.source.text)); + } } } @@ -481,8 +484,8 @@ export class DiagnosticComputer { if (!hrefDoc && !await this.workspaceContents.pathExists(path)) { const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath); for (const link of links) { - if (!options.skipPaths.some(glob => picomatch.isMatch(link.source.pathText, glob))) { - diagnostics.push(new FileDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText)); + if (!this.isIgnoredLink(options, link.source.pathText)) { + diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText)); } } } else if (hrefDoc) { @@ -491,9 +494,9 @@ export class DiagnosticComputer { if (fragmentLinks.length) { const toc = await TableOfContents.create(this.engine, hrefDoc); for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment)) { + if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.text)) { const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); - diagnostics.push(new vscode.Diagnostic(link.source.hrefRange, msg, severity)); + diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.text)); } } } @@ -502,11 +505,15 @@ export class DiagnosticComputer { })); return diagnostics; } + + private isIgnoredLink(options: DiagnosticOptions, link: string): boolean { + return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob)); + } } -class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { +class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { - private static readonly _addToSkipPathsCommandId = '_markdown.addToSkipPaths'; + private static readonly _addToIgnoreLinksCommandId = '_markdown.addToIgnoreLinks'; private static readonly metadata: vscode.CodeActionProviderMetadata = { providedCodeActionKinds: [ @@ -515,11 +522,11 @@ class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { }; public static register(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable { - const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToSkipPathsQuickFixProvider(), AddToSkipPathsQuickFixProvider.metadata); + const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider.metadata); const commandReg = commandManager.register({ - id: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + id: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, execute(resource: vscode.Uri, path: string) { - const settingId = 'experimental.validate.fileLinks.skipPaths'; + const settingId = 'experimental.validate.ignoreLinks'; const config = vscode.workspace.getConfiguration('markdown', resource); const paths = new Set(config.get(settingId, [])); paths.add(path); @@ -533,15 +540,15 @@ class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { const fixes: vscode.CodeAction[] = []; for (const diagnostic of context.diagnostics) { - if (diagnostic instanceof FileDoesNotExistDiagnostic) { + if (diagnostic instanceof LinkDoesNotExistDiagnostic) { const fix = new vscode.CodeAction( - localize('skipPathsQuickFix.title', "Add '{0}' to paths that skip link validation.", diagnostic.path), + localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", diagnostic.link), vscode.CodeActionKind.QuickFix); fix.command = { - command: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, title: '', - arguments: [document.uri, diagnostic.path] + arguments: [document.uri, diagnostic.link] }; fixes.push(fix); } @@ -563,5 +570,5 @@ export function register( return vscode.Disposable.from( configuration, manager, - AddToSkipPathsQuickFixProvider.register(selector, commandManager)); + AddToIgnoreLinksQuickFixProvider.register(selector, commandManager)); } diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index e58dabd983e..1490df29279 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -26,7 +26,7 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, - skipPaths: [], + ignoreLinks: [], }, noopToken) ).diagnostics; } @@ -44,7 +44,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { constructor( private readonly enabled: boolean = true, - private readonly skipPaths: string[] = [], + private readonly ignoreLinks: string[] = [], ) { } getOptions(_resource: vscode.Uri): DiagnosticOptions { @@ -54,7 +54,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.ignore, validateOwnHeaders: DiagnosticLevel.ignore, validateReferences: DiagnosticLevel.ignore, - skipPaths: this.skipPaths, + ignoreLinks: this.ignoreLinks, }; } return { @@ -62,7 +62,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, - skipPaths: this.skipPaths, + ignoreLinks: this.ignoreLinks, }; } } @@ -196,7 +196,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should allow skipping non-existent file', async () => { + test('ignoreLinks should allow skipping link to non-existent file', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); @@ -206,7 +206,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should not consider link fragment', async () => { + test('ignoreLinks should not consider link fragment', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); @@ -216,7 +216,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should support globs', async () => { + test('ignoreLinks should support globs', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/images/aaa.png)`, `![i](/images/sub/bbb.png)`, @@ -227,4 +227,33 @@ suite('markdown: Diagnostics', () => { const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); assert.deepStrictEqual(diagnostics.length, 0); }); + + test('ignoreLinks should support ignoring header', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](#no-such)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['#no-such'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('ignoreLinks should support ignoring header in file', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](/doc2.md#no-such)`, + )); + const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); + + const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); + { + const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#no-such'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + } + { + const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#*'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + } + }); }); From 2796b3425ddf413a33937b1167cb96cee544904f Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 20 May 2022 16:28:03 -0700 Subject: [PATCH 192/350] Allow single source action to be executed without picking --- .../browser/notebookExecutionServiceImpl.ts | 21 +++++++++++- .../viewParts/notebookKernelActionViewItem.ts | 32 ++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index c92696d34eb..aa57b01e95a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -14,7 +14,7 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class NotebookExecutionService implements INotebookExecutionService, IDisposable { declare _serviceBrand: undefined; @@ -39,6 +39,10 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis } let kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); + if (!kernel) { + kernel = await this.resolveSourceActions(notebook); + } + if (!kernel) { await this._commandService.executeCommand(SELECT_KERNEL_ID); kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); @@ -86,6 +90,21 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis } } + private async resolveSourceActions(notebook: INotebookTextModel) { + let kernel: INotebookKernel | undefined; + const info = this._notebookKernelService.getMatchingKernel(notebook); + if (info.all.length === 0) { + // no kernel at all + const sourceActions = this._notebookKernelService.getSourceActions(); + if (sourceActions.length === 1) { + await this._notebookKernelService.runSourceAction(sourceActions[0]); + kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); + } + } + + return kernel; + } + async cancelNotebookCellHandles(notebook: INotebookTextModel, cells: Iterable): Promise { const cellsArr = Array.from(cells); this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 4f23f001c3a..060e17ac37a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -64,25 +64,41 @@ export class NotebooKernelActionViewItem extends ActionViewItem { const runningAction = this._notebookKernelService.getRunningSourceAction(); if (runningAction) { - this._updateActionFromSourceAction(runningAction); - return; - } else { - this.action.class = ThemeIcon.asClassName(selectKernelIcon); - const info = this._notebookKernelService.getMatchingKernel(notebook); - this._updateActionFromKernelInfo(info); + return this._updateActionFromSourceAction(runningAction, true); } + + const info = this._notebookKernelService.getMatchingKernel(notebook); + if (info.all.length === 0) { + return this._updateActionsFromSourceActions(); + } + + this._updateActionFromKernelInfo(info); } - private _updateActionFromSourceAction(sourceAction: IAction) { - this.action.class = ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')); + private _updateActionFromSourceAction(sourceAction: IAction, running: boolean) { + this.action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon); this.updateClass(); this._action.label = sourceAction.label; this._action.enabled = true; } + private _updateActionsFromSourceActions() { + this._action.enabled = true; + const sourceActions = this._notebookKernelService.getSourceActions(); + if (sourceActions.length === 1) { + // exact one action + this._updateActionFromSourceAction(sourceActions[0], false); + } else { + this._action.class = ThemeIcon.asClassName(selectKernelIcon); + this._action.label = localize('select', "Select Kernel"); + this._action.tooltip = ''; + } + } + private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { this._kernelDisposable.clear(); this._action.enabled = true; + this._action.class = ThemeIcon.asClassName(selectKernelIcon); const selectedOrSuggested = info.selected ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined); if (selectedOrSuggested) { // selected or suggested kernel From 8c88eb6e5576bc352955f86a5a1c277bfe94f8f1 Mon Sep 17 00:00:00 2001 From: justanotheranonymoususer Date: Sat, 21 May 2022 02:33:43 +0300 Subject: [PATCH 193/350] Add extension output label to url --- src/vs/workbench/api/browser/mainThreadOutputService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadOutputService.ts b/src/vs/workbench/api/browser/mainThreadOutputService.ts index 34859bb4235..85f8495e80d 100644 --- a/src/vs/workbench/api/browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/browser/mainThreadOutputService.ts @@ -44,7 +44,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut public async $register(label: string, log: boolean, file: UriComponents, languageId: string, extensionId: string): Promise { const idCounter = (MainThreadOutputService._extensionIdPool.get(extensionId) || 0) + 1; MainThreadOutputService._extensionIdPool.set(extensionId, idCounter); - const id = `extension-output-${extensionId}-#${idCounter}`; + const id = `extension-output-${extensionId}-#${idCounter}-${label}`; Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: URI.revive(file), log, languageId }); this._register(toDisposable(() => this.$dispose(id))); From 8d503ffaf4ffd70a1ea7329a6cced91661858ff3 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 20 May 2022 16:35:46 -0700 Subject: [PATCH 194/350] connection state in another pr --- .../api/browser/mainThreadNotebookKernels.ts | 16 ++-------------- src/vs/workbench/api/common/extHost.api.impl.ts | 1 - src/vs/workbench/api/common/extHost.protocol.ts | 6 ------ .../api/common/extHostNotebookKernels.ts | 9 --------- src/vs/workbench/api/common/extHostTypes.ts | 4 ---- .../contrib/editorStatusBar/editorStatusBar.ts | 4 ++-- .../browser/notebookExecutionServiceImpl.ts | 12 ------------ .../viewParts/notebookKernelActionViewItem.ts | 8 ++------ .../notebook/common/notebookKernelService.ts | 8 -------- .../browser/notebookExecutionService.test.ts | 4 +--- .../notebookExecutionStateService.test.ts | 1 - .../test/browser/notebookKernelService.test.ts | 4 +--- .../actions/common/menusExtensionPoint.ts | 3 ++- .../vscode.proposed.notebookProxyController.d.ts | 9 --------- 14 files changed, 10 insertions(+), 79 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 38c81e4ba0e..b6ce5aaa0f8 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -17,14 +17,12 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape, NotebookControllerState } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI; provides: string[] }[]; readonly onDidChange: Event = this._onDidChange.event; - private readonly _onDispose = new Emitter(); - readonly onDispose = this._onDispose.event; readonly id: string; readonly viewType: string; readonly extension: ExtensionIdentifier; @@ -34,7 +32,6 @@ abstract class MainThreadKernel implements INotebookKernel { description?: string; detail?: string; kind?: string; - state?: NotebookControllerState; supportedLanguages: string[]; implementsExecutionOrder: boolean; localResourceRoot: URI; @@ -57,7 +54,6 @@ abstract class MainThreadKernel implements INotebookKernel { this.description = data.description; this.detail = data.detail; this.kind = data.kind; - this.state = data.state; this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : _languageService.getRegisteredLanguageIds(); this.implementsExecutionOrder = data.supportsExecutionOrder ?? false; this.localResourceRoot = URI.revive(data.extensionLocation); @@ -84,10 +80,6 @@ abstract class MainThreadKernel implements INotebookKernel { this.kind = data.kind; event.kind = true; } - if (data.state !== undefined) { - this.state = data.state; - event.state = true; - } if (data.supportedLanguages !== undefined) { this.supportedLanguages = isNonEmptyArray(data.supportedLanguages) ? data.supportedLanguages : this._languageService.getRegisteredLanguageIds(); event.supportedLanguages = true; @@ -99,10 +91,6 @@ abstract class MainThreadKernel implements INotebookKernel { this._onDidChange.fire(event); } - dispose() { - this._onDispose.fire(); - } - abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; } @@ -228,7 +216,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape }); const registration = this._notebookKernelService.registerKernel(kernel); - this._kernels.set(handle, [kernel, combinedDisposable(kernel, listener, registration)]); + this._kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); } $updateKernel(handle: number, data: Partial): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5140d9770d5..e4b23860c6e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1315,7 +1315,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem, NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, - NotebookControllerState: extHostTypes.NotebookControllerState, NotebookEdit: extHostTypes.NotebookEdit, PortAttributes: extHostTypes.PortAttributes, LinkedEditingRanges: extHostTypes.LinkedEditingRanges, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d2e87c6e7c6..cff9c82244a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -970,11 +970,6 @@ export interface MainThreadNotebookDocumentsShape extends IDisposable { $trySaveNotebook(uri: UriComponents): Promise; } -export enum NotebookControllerState { - Idle = 1, - Connecting = 2 -} - export interface INotebookKernelDto2 { id: string; notebookType: string; @@ -984,7 +979,6 @@ export interface INotebookKernelDto2 { detail?: string; description?: string; kind?: string; - state?: NotebookControllerState; supportedLanguages?: string[]; supportsInterrupt?: boolean; supportsExecutionOrder?: boolean; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 4e4ca9640c5..f99ead59ac7 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -196,15 +196,6 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { get rendererScripts() { return data.preloads ? data.preloads.map(extHostTypeConverters.NotebookRendererScript.to) : []; }, - get state() { - checkProposedApiEnabled(extension, 'notebookProxyController'); - return data.state; - }, - set state(value) { - checkProposedApiEnabled(extension, 'notebookProxyController'); - data.state = value; - _update(); - }, get executeHandler() { return _executeHandler; }, diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index fd1f2558140..039f43fce8b 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3426,10 +3426,6 @@ export class NotebookRendererScript { } } -export enum NotebookControllerState { - Idle = 1, - Connecting = 2 -} //#endregion diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index a62de5e8e65..bb1bd41aeb1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -29,7 +29,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -408,7 +408,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { this._kernelInfoElement.add(this._statusbarService.addEntry( { name: nls.localize('notebook.info', "Notebook Kernel Info"), - text: `$(notebook-kernel-select) ${kernel.label}` + (kernel.state === NotebookControllerState.Idle ? '' : ' Connecting...'), + text: `$(notebook-kernel-select) ${kernel.label}`, ariaLabel: kernel.label, tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, command: SELECT_KERNEL_ID, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index aa57b01e95a..75b6b1a55dd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -64,18 +64,6 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis executeCells.push(cell); } - if (kernel.state !== undefined) { - // kernel has connection state management - const kernelId = kernel.id; - kernel.onDispose(() => { - // proxy kernel scenario, kernel disposed, we should now make way for new preferred kernel - const exes = executeCells.map(c => this._notebookExecutionStateService.createCellExecution(kernelId, notebook.uri, c.handle)); - exes.forEach(e => e.complete({})); - - this.executeNotebookCells(notebook, executeCells); - }); - } - if (executeCells.length > 0) { this._notebookKernelService.selectKernelForNotebook(kernel, notebook); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 060e17ac37a..8b8f92277e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -10,7 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -121,11 +121,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { } private _generateKenrelLabel(kernel: INotebookKernel) { - if (kernel.state === NotebookControllerState.Connecting) { - return localize('kernelconnecting', "Connecting..."); - } else { - return kernel.label; - } + return kernel.label; } private _resetAction(): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index e47d930372f..0068bd968e1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -28,16 +28,10 @@ export interface INotebookKernelChangeEvent { description?: true; detail?: true; kind?: true; - state?: true; supportedLanguages?: true; hasExecutionOrder?: true; } -export enum NotebookControllerState { - Idle = 1, - Connecting = 2 -} - export interface INotebookKernel { readonly id: string; readonly viewType: string; @@ -52,14 +46,12 @@ export interface INotebookKernel { description?: string; detail?: string; kind?: string; - state?: NotebookControllerState; supportedLanguages: string[]; implementsInterrupt?: boolean; implementsExecutionOrder?: boolean; executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise; cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise; - onDispose: Event; } export const enum ProxyKernelState { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 9ceec332a7a..b0a288f1e8e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -20,7 +20,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -178,7 +178,6 @@ class TestNotebookKernel implements INotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; - onDispose = Event.None; executeNotebookCellsRequest(): Promise { throw new Error('Method not implemented.'); } @@ -189,7 +188,6 @@ class TestNotebookKernel implements INotebookKernel { this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID]; } kind?: string | undefined; - state?: NotebookControllerState | undefined; implementsInterrupt?: boolean | undefined; implementsExecutionOrder?: boolean | undefined; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 4763ee855c6..887a4a05443 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -182,7 +182,6 @@ class TestNotebookKernel implements INotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; - onDispose = Event.None; async executeNotebookCellsRequest(): Promise { } async cancelNotebookCellExecution(): Promise { } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index 54b3b52cb3c..b1ebabc1db2 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookKernel, INotebookKernelService, NotebookControllerState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { mock } from 'vs/base/test/common/mock'; @@ -171,8 +171,6 @@ class TestNotebookKernel implements INotebookKernel { preloadUris: URI[] = []; preloadProvides: string[] = []; supportedLanguages: string[] = []; - state?: NotebookControllerState | undefined = undefined; - onDispose = Event.None; executeNotebookCellsRequest(): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 409cfb9924c..bceef1e438f 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -174,7 +174,8 @@ const apiMenus: IAPIMenu[] = [ { key: 'notebook/kernelSource', id: MenuId.NotebookKernelSource, - description: localize('notebook.kernelSource', "The contributed notebook kernel sources menu") + description: localize('notebook.kernelSource', "The contributed notebook kernel sources menu"), + proposed: 'notebookProxyController' }, { key: 'notebook/cell/title', diff --git a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts index df429123049..a0f2c9e2df8 100644 --- a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts @@ -4,13 +4,4 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { - - export enum NotebookControllerState { - Idle = 1, - Connecting = 2 - } - - export interface NotebookController { - state?: NotebookControllerState; - } } From 50d802e75ffd879157d631e5a579f6e7b44f38e0 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 20 May 2022 16:40:19 -0700 Subject: [PATCH 195/350] :lipstick: --- src/vs/workbench/api/browser/mainThreadNotebookKernels.ts | 1 + src/vs/workbench/api/common/extHostTypes.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index b6ce5aaa0f8..6b734982787 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -23,6 +23,7 @@ abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); private readonly preloads: { uri: URI; provides: string[] }[]; readonly onDidChange: Event = this._onDidChange.event; + readonly id: string; readonly viewType: string; readonly extension: ExtensionIdentifier; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 039f43fce8b..dc08ede3d98 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3426,7 +3426,6 @@ export class NotebookRendererScript { } } - //#endregion //#region Timeline From d1d31a284915522fed87bf922d4181124b7350fe Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 20 May 2022 19:41:56 -0400 Subject: [PATCH 196/350] Fix weird undefined bug (#150063) --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a857b3eec7d..73c8913b840 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -775,7 +775,9 @@ export class FilesFilter implements ITreeFilter { const globMatch = cached?.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name))); // Small optimization to only traverse gitIgnore if the globMatch from fileExclude returned nothing const ignoreFile = globMatch ? undefined : this.ignoreTreesPerRoot.get(stat.root.resource.toString())?.findSubstr(stat.resource); - const isIgnoredByIgnoreFile = !ignoreFile?.isPathIncludedInTraversal(stat.resource.path, stat.isDirectory); + const isIncludedInTraversal = ignoreFile?.isPathIncludedInTraversal(stat.resource.path, stat.isDirectory); + // Doing !undefined returns true and we want it to be false when undefined because that means it's not included in the ignore file + const isIgnoredByIgnoreFile = isIncludedInTraversal === undefined ? false : !isIncludedInTraversal; if (isIgnoredByIgnoreFile || globMatch || stat.parent?.isExcluded) { stat.isExcluded = true; const editors = this.editorService.visibleEditors; From 03694c8c5ca6fde4e9624a7caca989e39c673564 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Fri, 20 May 2022 17:17:03 -0700 Subject: [PATCH 197/350] Ideas for tracking last output --- .../browser/diff/notebookTextDiffEditor.ts | 4 ++ .../notebook/browser/notebookEditorWidget.ts | 1 + .../view/renderers/backLayerWebView.ts | 21 +++++++++ .../browser/view/renderers/webviewMessages.ts | 17 ++++++- .../browser/view/renderers/webviewPreloads.ts | 45 +++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 1369de699b9..a791887d639 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -789,6 +789,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD throw new Error('Not implemented'); } + getCellByHandle(cellHandle: number): IGenericCellViewModel | undefined { + throw new Error('Not implemented'); + } + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8567bdded40..7197952d03c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1385,6 +1385,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD triggerScroll(event: IMouseWheelEvent) { that._listViewInfoAccessor.triggerScroll(event); }, getCellByInfo: that.getCellByInfo.bind(that), getCellById: that._getCellById.bind(that), + getCellByHandle: that.getCellByHandle.bind(that), toggleNotebookCellSelection: that._toggleNotebookCellSelection.bind(that), focusNotebookCell: that.focusNotebookCell.bind(that), focusNextNotebookCell: that.focusNextNotebookCell.bind(that), diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index af4606ffee6..59046e6159e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -37,6 +37,7 @@ import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebo import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -66,6 +67,7 @@ export interface INotebookDelegateForWebview { focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions): Promise; toggleNotebookCellSelection(cell: IGenericCellViewModel, selectFromPrevious: boolean): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; + getCellByHandle(cellHandle: number): IGenericCellViewModel | undefined; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise; updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean, source?: string): void; scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number): void; @@ -134,6 +136,7 @@ export class BackLayerWebView extends Disposable { @ILanguageService private readonly languageService: ILanguageService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService ) { super(); @@ -173,6 +176,19 @@ export class BackLayerWebView extends Disposable { css: getTokenizationCss(), }); })); + + this._register(notebookExecutionStateService.onDidChangeCellExecution((e) => { + // If e.changed is undefined, it means notebook execution just finished. + if (e.changed === undefined && e.affectsNotebook(this.documentUri)) { + const cell = this.notebookEditor.getCellByHandle(e.cellHandle); + if (cell) { + this._sendMessageToWebview({ + type: 'trackFinalOutputRender', + cellId: cell.id + }); + } + } + })); } updateOptions(options: BacklayerWebviewOptions) { @@ -818,6 +834,11 @@ var requirejs = (function() { this._handleHighlightCodeBlock(data.codeBlocks); break; } + + case 'didRenderFinalOutput': { + console.log(`Rendered final output for ${data.cellId}`); + break; + } } })); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 8b98bba83f8..e37283b1809 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -401,6 +401,16 @@ export interface IDidFindHighlightMessage extends BaseToWebviewMessage { readonly offset: number; } +export interface ITrackFinalOutputRenderMessage { + readonly type: 'trackFinalOutputRender'; + readonly cellId: string; +} + +export interface IDidRenderFinalOutputMessage extends BaseToWebviewMessage { + readonly type: 'didRenderFinalOutput'; + readonly cellId: string; +} + export type FromWebviewMessage = WebviewInitialized | IDimensionMessage | IMouseEnterMessage | @@ -428,7 +438,8 @@ export type FromWebviewMessage = WebviewInitialized | IRenderedMarkupMessage | IRenderedCellOutputMessage | IDidFindMessage | - IDidFindHighlightMessage; + IDidFindHighlightMessage | + IDidRenderFinalOutputMessage; export type ToWebviewMessage = IClearMessage | IFocusOutputMessage | @@ -458,6 +469,8 @@ export type ToWebviewMessage = IClearMessage | IFindMessage | IFindHighlightMessage | IFindUnHighlightMessage | - IFindStopMessage; + IFindStopMessage | + ITrackFinalOutputRenderMessage; + export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 33155f8dc15..786cc9377ce 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1172,6 +1172,10 @@ async function webviewPreloads(ctx: PreloadContext) { _highlighter?.dispose(); break; } + case 'trackFinalOutputRender': { + viewModel.trackFinalOutput(event.data.cellId); + break; + } } }); @@ -1394,6 +1398,7 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly _markupCells = new Map(); private readonly _outputCells = new Map(); + private _pendingFinalOutput: string | undefined; public clearAll() { this._markupCells.clear(); @@ -1405,6 +1410,15 @@ async function webviewPreloads(ctx: PreloadContext) { this.renderOutputCells(); } + public trackFinalOutput(cellId: string) { + const cell = this._outputCells.get(cellId); + if (cell) { + cell.addFinalOutputTracker(); + } else { + this._pendingFinalOutput = cellId; + } + } + private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number, visible: boolean): Promise { const existing = this._markupCells.get(init.cellId); if (existing) { @@ -1517,6 +1531,12 @@ async function webviewPreloads(ctx: PreloadContext) { // don't hide until after this step so that the height is right cellOutput.element.style.visibility = data.initiallyHidden ? 'hidden' : 'visible'; + + // If notebook side has indicated we are done (and we haven't added it yet), stick in the final render + if (data.cellId === this._pendingFinalOutput) { + this._pendingFinalOutput = undefined; + cellOutput.addFinalOutputTracker(); + } } public ensureOutputCell(cellId: string, cellTop: number, skipCellTopUpdateIfExist: boolean): OutputCell { @@ -1817,8 +1837,15 @@ async function webviewPreloads(ctx: PreloadContext) { container.appendChild(this.element); this.element = this.element; + const lowerWrapperElement = createFocusSink(cellId, true); container.appendChild(lowerWrapperElement); + + // New thoughts. + // Send message to indicate force scroll. + // Add resizeObserver on the element + // When resize occurs, scroll lowerWrapperElement into view + // Send message to indicate stop scrolling } public createOutputElement(outputId: string, outputOffset: number, left: number): OutputElement { @@ -1878,6 +1905,24 @@ async function webviewPreloads(ctx: PreloadContext) { this.element.style.visibility = 'visible'; } } + + public addFinalOutputTracker() { + const id = `${this.element.id}-final-output-tracker`; + let tracker: HTMLImageElement | null = document.getElementById(id) as HTMLImageElement; + if (!tracker) { + tracker = document.createElement('img') as HTMLImageElement; + tracker.id = id; + tracker.style.height = '0px'; + tracker.src = ''; + tracker.addEventListener('error', (ev) => { + console.log(`Final cell output has rendered`); + + // Src is empty so this should fire as soon as it renders + postNotebookMessage('didRenderFinalOutput', { cellId: this.element.id }); + }); + this.element.appendChild(tracker); + } + } } class OutputContainer { From 3649387f3fc1f8bbb9d2f58dc1fe66dd01f50a6a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 20 May 2022 17:18:25 -0700 Subject: [PATCH 198/350] Bump php grammar, fix #142824, fix #116103 (#150067) --- extensions/php/cgmanifest.json | 4 +- extensions/php/syntaxes/php.tmLanguage.json | 430 +++++++++++++------- 2 files changed, 293 insertions(+), 141 deletions(-) diff --git a/extensions/php/cgmanifest.json b/extensions/php/cgmanifest.json index 4b25149ffdd..4fe8f5ebcb5 100644 --- a/extensions/php/cgmanifest.json +++ b/extensions/php/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "language-php", "repositoryUrl": "https://github.com/atom/language-php", - "commitHash": "ff64523c94c014d68f5dec189b05557649c5872a" + "commitHash": "eb28b8aea1214dcbc732f3d9b9ed20c089c648bd" } }, "license": "MIT", - "version": "0.48.0" + "version": "0.48.1" } ], "version": 1 diff --git a/extensions/php/syntaxes/php.tmLanguage.json b/extensions/php/syntaxes/php.tmLanguage.json index 69a11ec2529..542b02023a7 100644 --- a/extensions/php/syntaxes/php.tmLanguage.json +++ b/extensions/php/syntaxes/php.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/atom/language-php/commit/ff64523c94c014d68f5dec189b05557649c5872a", + "version": "https://github.com/atom/language-php/commit/eb28b8aea1214dcbc732f3d9b9ed20c089c648bd", "scopeName": "source.php", "patterns": [ { @@ -13,62 +13,6 @@ { "include": "#comments" }, - { - "begin": "(?i)^\\s*(interface)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\\s*(extends)?\\s*", - "beginCaptures": { - "1": { - "name": "storage.type.interface.php" - }, - "2": { - "name": "entity.name.type.interface.php" - }, - "3": { - "name": "storage.modifier.extends.php" - } - }, - "end": "(?i)((?:[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\s*,\\s*)*)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?\\s*(?:(?={)|$)", - "endCaptures": { - "1": { - "patterns": [ - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - }, - { - "match": ",", - "name": "punctuation.separator.classes.php" - } - ] - }, - "2": { - "name": "entity.other.inherited-class.php" - } - }, - "name": "meta.interface.php", - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "begin": "(?i)^\\s*(trait)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", - "beginCaptures": { - "1": { - "name": "storage.type.trait.php" - }, - "2": { - "name": "entity.name.type.trait.php" - } - }, - "end": "(?={)", - "name": "meta.trait.php", - "patterns": [ - { - "include": "#comments" - } - ] - }, { "match": "(?i)(?:^|(?<=<\\?php))\\s*(namespace)\\s+([a-z0-9_\\x{7f}-\\x{10ffff}\\\\]+)(?=\\s*;)", "name": "meta.namespace.php", @@ -232,6 +176,149 @@ } ] }, + { + "begin": "(?ix)\n\\b(trait)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.type.trait.php" + }, + "2": { + "name": "entity.name.type.trait.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.trait.end.bracket.curly.php" + } + }, + "name": "meta.trait.php", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.trait.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.trait.body.php", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?ix)\n\\b(interface)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.type.interface.php" + }, + "2": { + "name": "entity.name.type.interface.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.interface.end.bracket.curly.php" + } + }, + "name": "meta.interface.php", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#interface-extends" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.interface.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.interface.body.php", + "patterns": [ + { + "include": "#class-constant" + }, + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?ix)\n\\b(enum)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\n(?: \\s* (:) \\s* (int | string) \\b )?", + "beginCaptures": { + "1": { + "name": "storage.type.enum.php" + }, + "2": { + "name": "entity.name.type.enum.php" + }, + "3": { + "name": "keyword.operator.return-value.php" + }, + "4": { + "name": "keyword.other.type.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.enum.end.bracket.curly.php" + } + }, + "name": "meta.enum.php", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#class-implements" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.enum.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.enum.body.php", + "patterns": [ + { + "match": "(?i)\\b(case)\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "captures": { + "1": { + "name": "storage.modifier.php" + }, + "2": { + "name": "constant.enum.php" + } + } + }, + { + "include": "#class-constant" + }, + { + "include": "$self" + } + ] + } + ] + }, { "begin": "(?ix)\n(?:\n \\b(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\n |\\b(new)\\b\\s*(\\#\\[.*\\])?\\s*\\b(class)\\b # anonymous class\n)", "beginCaptures": { @@ -266,89 +353,37 @@ }, "name": "meta.class.php", "patterns": [ + { + "begin": "(?<=class)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.bracket.round.php" + } + }, + "end": "\\)|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.bracket.round.php" + } + }, + "name": "meta.function-call.php", + "patterns": [ + { + "include": "#named-arguments" + }, + { + "include": "$self" + } + ] + }, { "include": "#comments" }, { - "begin": "(?i)(extends)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.extends.php" - } - }, - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - } - ] + "include": "#class-extends" }, { - "begin": "(?i)(implements)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.implements.php" - } - }, - "end": "(?i)(?=[;{])", - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(?i)(?=[a-z0-9_\\x{7f}-\\x{10ffff}\\\\]+)", - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?:\\s*(?:,|(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\\\s]))\\s*)", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - } - ] - } - ] + "include": "#class-implements" }, { "begin": "{", @@ -360,6 +395,9 @@ "end": "(?=}|\\?>)", "contentName": "meta.class.body.php", "patterns": [ + { + "include": "#class-constant" + }, { "include": "$self" } @@ -382,7 +420,7 @@ } }, { - "match": "(?x)\n\\s* # FIXME: Removing this causes specs to fail. Investigate.\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", + "match": "(?x)\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", "captures": { "1": { "name": "keyword.control.${1:/downcase}.php" @@ -853,7 +891,7 @@ "name": "storage.type.php" }, { - "match": "(?i)\\b(global|abstract|const|extends|implements|final|private|protected|public|static)\\b", + "match": "(?i)\\b(global|abstract|const|final|private|protected|public|static)\\b", "name": "storage.modifier.php" }, { @@ -975,7 +1013,7 @@ "name": "entity.name.goto-label.php" } }, - "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\\s*:(?!:)" + "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*(? Date: Sat, 21 May 2022 22:49:45 -0700 Subject: [PATCH 199/350] Screen reader should read quick pick title field if set (#149992) --- src/vs/base/parts/quickinput/browser/quickInput.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 49fb9f19455..e7099f2655b 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -979,7 +979,15 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } - const ariaLabel = this.ariaLabel || this.placeholder || QuickPick.DEFAULT_ARIA_LABEL; + + let ariaLabel = this.ariaLabel; + if (!ariaLabel) { + ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL; + // If we have a title, include it in the aria label. + if (this.title) { + ariaLabel += ` - ${this.title}`; + } + } if (this.ui.inputBox.ariaLabel !== ariaLabel) { this.ui.inputBox.ariaLabel = ariaLabel; } From aacb387ef1c926af69224ea45b1c2ccce899d4db Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 May 2022 17:08:42 +0200 Subject: [PATCH 200/350] implement diagnostic pull --- .../server/src/cssServer.ts | 67 ++++------- .../server/src/utils/validation.ts | 109 ++++++++++++++++++ .../server/src/utils/validation.ts | 72 ++++++++++++ .../server/src/jsonServer.ts | 81 ++++--------- .../server/src/utils/validation.ts | 109 ++++++++++++++++++ 5 files changed, 335 insertions(+), 103 deletions(-) create mode 100644 extensions/css-language-features/server/src/utils/validation.ts create mode 100644 extensions/html-language-features/server/src/utils/validation.ts create mode 100644 extensions/json-language-features/server/src/utils/validation.ts diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 3eed3962879..b4ce51c162a 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -9,7 +9,8 @@ import { import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position, CSSFormatConfiguration } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; -import { formatError, runSafeAsync } from './utils/runner'; +import { runSafeAsync } from './utils/runner'; +import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation'; import { getDocumentContext } from './utils/documentContext'; import { fetchDataProviders } from './customData'; import { RequestService, getRequestService } from './requests'; @@ -56,6 +57,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let dataProvidersReady: Promise = Promise.resolve(); + let diagnosticPushSupport: DiagnosticsSupport | undefined; + const languageServices: { [id: string]: LanguageService } = {}; const notReady = () => Promise.reject('Not Ready'); @@ -91,12 +94,20 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false); scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); languageServices.less = getLESSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); + const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); + if (pullDiagnosticSupport === undefined) { + diagnosticPushSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + } else { + diagnosticPushSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + } + const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/', '-', ':'] } : undefined, @@ -113,6 +124,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, + diagnosticProvider: { + documentSelector: null, + interFileDependencies: false, + workspaceDiagnostics: false + }, documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, documentFormattingProvider: initializationOptions?.provideFormatter === true, }; @@ -157,53 +173,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } // reset all document settings documentSettings = {}; - // Revalidate any open text documents - documents.all().forEach(triggerValidation); + diagnosticPushSupport?.requestRefresh(); } - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 500; - - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent(change => { - triggerValidation(change.document); - }); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }); - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { - delete pendingValidationRequests[textDocument.uri]; - validateTextDocument(textDocument); - }, validationDelayMs); - } - - function validateTextDocument(textDocument: TextDocument): void { + async function validateTextDocument(textDocument: TextDocument): Promise { const settingsPromise = getDocumentSettings(textDocument); - Promise.all([settingsPromise, dataProvidersReady]).then(async ([settings]) => { - const stylesheet = stylesheets.get(textDocument); - const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings) as Diagnostic[]; - // Send the computed diagnostics to VSCode. - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); - }, e => { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); - }); - } + const [settings] = await Promise.all([settingsPromise, dataProvidersReady]); + const stylesheet = stylesheets.get(textDocument); + return getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); + } function updateDataProviders(dataPaths: string[]) { dataProvidersReady = fetchDataProviders(dataPaths, requestService).then(customDataProviders => { diff --git a/extensions/css-language-features/server/src/utils/validation.ts b/extensions/css-language-features/server/src/utils/validation.ts new file mode 100644 index 00000000000..2658f74016a --- /dev/null +++ b/extensions/css-language-features/server/src/utils/validation.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-css-languageservice'; +import { formatError, runSafeAsync } from './runner'; +import { RuntimeEnvironment } from '../cssServer'; + +export type Validator = (textDocument: TextDocument) => Promise; +export type DiagnosticsSupport = { + dispose(): void; + requestRefresh(): void; +}; + +export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { + + const pendingValidationRequests: { [uri: string]: Disposable } = {}; + const validationDelayMs = 500; + + const disposables: Disposable[] = []; + + // The content of a text document has changed. This event is emitted + // when the text document first opened or when its content has changed. + documents.onDidChangeContent(change => { + triggerValidation(change.document); + }, undefined, disposables); + + // a document has closed: clear all diagnostics + documents.onDidClose(event => { + cleanPendingValidation(event.document); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); + }, undefined, disposables); + + function cleanPendingValidation(textDocument: TextDocument): void { + const request = pendingValidationRequests[textDocument.uri]; + if (request) { + request.dispose(); + delete pendingValidationRequests[textDocument.uri]; + } + } + + function triggerValidation(textDocument: TextDocument): void { + cleanPendingValidation(textDocument); + const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { + if (request === pendingValidationRequests[textDocument.uri]) { + try { + const diagnostics = await validate(textDocument); + if (request === pendingValidationRequests[textDocument.uri]) { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } + delete pendingValidationRequests[textDocument.uri]; + } catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); + } + } + }, validationDelayMs); + } + + documents.all().forEach(triggerValidation); + + return { + requestRefresh: () => { + documents.all().forEach(triggerValidation); + }, + dispose: () => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + const keys = Object.keys(pendingValidationRequests); + for (const key of keys) { + pendingValidationRequests[key].dispose(); + delete pendingValidationRequests[key]; + } + } + }; +} + +export function registerDiagnosticsPullSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { + + function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { + return { + kind: DocumentDiagnosticReportKind.Full, + items: diagnostics + }; + } + + connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + return newDocumentDiagnosticReport(await validate(document)); + } + return newDocumentDiagnosticReport([]); + + }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); + }); + + function requestRefresh(): void { + connection.languages.diagnostics.refresh(); + } + + return { + requestRefresh, + dispose: () => { + } + }; + +} diff --git a/extensions/html-language-features/server/src/utils/validation.ts b/extensions/html-language-features/server/src/utils/validation.ts new file mode 100644 index 00000000000..5c5261cf9e5 --- /dev/null +++ b/extensions/html-language-features/server/src/utils/validation.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. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, Diagnostic, Disposable, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-html-languageservice'; +import { formatError } from './runner'; +import { RuntimeEnvironment } from '../htmlServer'; + +export type Validator = (textDocument: TextDocument) => Promise; +export type DiagnosticPushSupport = { dispose(): void; triggerValidation(textDocument: TextDocument): void }; + +export function registerDiagnosticPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticPushSupport { + + const pendingValidationRequests: { [uri: string]: Disposable } = {}; + const validationDelayMs = 500; + + const disposables: Disposable[] = []; + + // The content of a text document has changed. This event is emitted + // when the text document first opened or when its content has changed. + documents.onDidChangeContent(change => { + triggerValidation(change.document); + }, undefined, disposables); + + // a document has closed: clear all diagnostics + documents.onDidClose(event => { + cleanPendingValidation(event.document); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); + }, undefined, disposables); + + function cleanPendingValidation(textDocument: TextDocument): void { + const request = pendingValidationRequests[textDocument.uri]; + if (request) { + request.dispose(); + delete pendingValidationRequests[textDocument.uri]; + } + } + + function triggerValidation(textDocument: TextDocument): void { + cleanPendingValidation(textDocument); + const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { + if (request === pendingValidationRequests[textDocument.uri]) { + try { + const diagnostics = await validate(textDocument); + if (request === pendingValidationRequests[textDocument.uri]) { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } + delete pendingValidationRequests[textDocument.uri]; + } catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); + } + } + }, validationDelayMs); + } + + documents.all().forEach(triggerValidation); + + return { + triggerValidation, + dispose: () => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + const keys = Object.keys(pendingValidationRequests); + for (const key of keys) { + pendingValidationRequests[key].dispose(); + delete pendingValidationRequests[key]; + } + } + }; +} diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index b818bcefbf2..df8bb6faa35 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -10,6 +10,7 @@ import { } from 'vscode-languageserver'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; +import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation'; import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { Utils, URI } from 'vscode-uri'; @@ -113,6 +114,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let resultLimit = Number.MAX_VALUE; let formatterMaxNumberOfEdits = Number.MAX_VALUE; + let diagnosticSupport: DiagnosticsSupport | undefined; + + // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { @@ -147,6 +151,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); formatterMaxNumberOfEdits = initializationOptions.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + + const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); + if (pullDiagnosticSupport === undefined) { + diagnosticSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + } else { + diagnosticSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + } + + const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { @@ -160,7 +173,12 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, - documentLinkProvider: {} + documentLinkProvider: {}, + diagnosticProvider: { + documentSelector: null, + interFileDependencies: false, + workspaceDiagnostics: false + } }; return { capabilities }; @@ -351,68 +369,13 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) documents.all().forEach(triggerValidation); } - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent((change) => { - limitExceededWarnings.cancel(change.document.uri); - triggerValidation(change.document); - }); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - limitExceededWarnings.cancel(event.document.uri); - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }); - - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 300; - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - if (validateEnabled) { - pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { - delete pendingValidationRequests[textDocument.uri]; - validateTextDocument(textDocument); - }, validationDelayMs); - } else { - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }); - } - } - - function validateTextDocument(textDocument: TextDocument, callback?: (diagnostics: Diagnostic[]) => void): void { - const respond = (diagnostics: Diagnostic[]) => { - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); - if (callback) { - callback(diagnostics); - } - }; + async function validateTextDocument(textDocument: TextDocument): Promise { if (textDocument.getText().length === 0) { - respond([]); // ignore empty documents - return; + return []; // ignore empty documents } const jsonDocument = getJSONDocument(textDocument); - const version = textDocument.version; - const documentSettings: DocumentLanguageSettings = textDocument.languageId === 'jsonc' ? { comments: 'ignore', trailingCommas: 'warning' } : { comments: 'error', trailingCommas: 'error' }; - languageService.doValidation(textDocument, jsonDocument, documentSettings).then(diagnostics => { - runtime.timer.setImmediate(() => { - const currDocument = documents.get(textDocument.uri); - if (currDocument && currDocument.version === version) { - respond(diagnostics as Diagnostic[]); // Send the computed diagnostics to VSCode. - } - }); - }, error => { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, error)); - }); + return await languageService.doValidation(textDocument, jsonDocument, documentSettings); } connection.onDidChangeWatchedFiles((change) => { diff --git a/extensions/json-language-features/server/src/utils/validation.ts b/extensions/json-language-features/server/src/utils/validation.ts new file mode 100644 index 00000000000..10130d2684f --- /dev/null +++ b/extensions/json-language-features/server/src/utils/validation.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-json-languageservice'; +import { formatError, runSafeAsync } from './runner'; +import { RuntimeEnvironment } from '../jsonServer'; + +export type Validator = (textDocument: TextDocument) => Promise; +export type DiagnosticsSupport = { + dispose(): void; + requestRefresh(): void; +}; + +export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { + + const pendingValidationRequests: { [uri: string]: Disposable } = {}; + const validationDelayMs = 500; + + const disposables: Disposable[] = []; + + // The content of a text document has changed. This event is emitted + // when the text document first opened or when its content has changed. + documents.onDidChangeContent(change => { + triggerValidation(change.document); + }, undefined, disposables); + + // a document has closed: clear all diagnostics + documents.onDidClose(event => { + cleanPendingValidation(event.document); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); + }, undefined, disposables); + + function cleanPendingValidation(textDocument: TextDocument): void { + const request = pendingValidationRequests[textDocument.uri]; + if (request) { + request.dispose(); + delete pendingValidationRequests[textDocument.uri]; + } + } + + function triggerValidation(textDocument: TextDocument): void { + cleanPendingValidation(textDocument); + const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { + if (request === pendingValidationRequests[textDocument.uri]) { + try { + const diagnostics = await validate(textDocument); + if (request === pendingValidationRequests[textDocument.uri]) { + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } + delete pendingValidationRequests[textDocument.uri]; + } catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); + } + } + }, validationDelayMs); + } + + documents.all().forEach(triggerValidation); + + return { + requestRefresh: () => { + documents.all().forEach(triggerValidation); + }, + dispose: () => { + disposables.forEach(d => d.dispose()); + disposables.length = 0; + const keys = Object.keys(pendingValidationRequests); + for (const key of keys) { + pendingValidationRequests[key].dispose(); + delete pendingValidationRequests[key]; + } + } + }; +} + +export function registerDiagnosticsPullSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { + + function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { + return { + kind: DocumentDiagnosticReportKind.Full, + items: diagnostics + }; + } + + connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { + return runSafeAsync(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + return newDocumentDiagnosticReport(await validate(document)); + } + return newDocumentDiagnosticReport([]); + + }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); + }); + + function requestRefresh(): void { + connection.languages.diagnostics.refresh(); + } + + return { + requestRefresh, + dispose: () => { + } + }; + +} From 49944b15011247708da1a9a466fb38da5c7dda68 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 May 2022 22:27:28 +0200 Subject: [PATCH 201/350] DiagnosticsSupport for json and html --- .../server/src/cssServer.ts | 8 +-- .../server/src/utils/validation.ts | 5 +- .../server/src/htmlServer.ts | 58 +++++++------------ .../server/src/utils/validation.ts | 50 +++++++++++++--- .../server/src/jsonServer.ts | 36 +++++------- .../server/src/utils/validation.ts | 5 +- 6 files changed, 87 insertions(+), 75 deletions(-) diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index b4ce51c162a..af55ec28f06 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -57,7 +57,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let dataProvidersReady: Promise = Promise.resolve(); - let diagnosticPushSupport: DiagnosticsSupport | undefined; + let diagnosticsSupport: DiagnosticsSupport | undefined; const languageServices: { [id: string]: LanguageService } = {}; @@ -103,9 +103,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); if (pullDiagnosticSupport === undefined) { - diagnosticPushSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); } else { - diagnosticPushSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); } const capabilities: ServerCapabilities = { @@ -173,7 +173,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } // reset all document settings documentSettings = {}; - diagnosticPushSupport?.requestRefresh(); + diagnosticsSupport?.requestRefresh(); } async function validateTextDocument(textDocument: TextDocument): Promise { diff --git a/extensions/css-language-features/server/src/utils/validation.ts b/extensions/css-language-features/server/src/utils/validation.ts index 2658f74016a..edd5f5618c7 100644 --- a/extensions/css-language-features/server/src/utils/validation.ts +++ b/extensions/css-language-features/server/src/utils/validation.ts @@ -58,8 +58,6 @@ export function registerDiagnosticsPushSupport(documents: TextDocuments { documents.all().forEach(triggerValidation); @@ -85,7 +83,7 @@ export function registerDiagnosticsPullSupport(documents: TextDocuments { + const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { @@ -103,6 +101,7 @@ export function registerDiagnosticsPullSupport(documents: TextDocuments { + registration.dispose(); } }; diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 319399a038c..3198ad16d96 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -19,6 +19,7 @@ import { pushAll } from './utils/arrays'; import { getDocumentContext } from './utils/documentContext'; import { URI } from 'vscode-uri'; import { formatError, runSafe } from './utils/runner'; +import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation'; import { getFoldingRanges } from './modes/htmlFolding'; import { fetchHTMLDataProviders } from './customData'; @@ -92,6 +93,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let languageModes: LanguageModes; + let diagnosticsSupport: DiagnosticsSupport | undefined; + let clientSnippetSupport = false; let dynamicFormatterRegistration = false; let scopedSettingsSupport = false; @@ -180,6 +183,14 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + + const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); + if (pullDiagnosticSupport === undefined) { + diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + } else { + diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + } + const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined, @@ -196,7 +207,12 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) foldingRangeProvider: true, selectionRangeProvider: true, renameProvider: true, - linkedEditingRangeProvider: true + linkedEditingRangeProvider: true, + diagnosticProvider: { + documentSelector: null, + interFileDependencies: false, + workspaceDiagnostics: false + } }; return { capabilities }; }); @@ -217,7 +233,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } } workspaceFolders = updatedFolders.concat(toAdd); - documents.all().forEach(triggerValidation); + diagnosticsSupport?.requestRefresh(); }); } }); @@ -228,7 +244,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onDidChangeConfiguration((change) => { globalSettings = change.settings as Settings; documentSettings = {}; // reset all document settings - documents.all().forEach(triggerValidation); + diagnosticsSupport?.requestRefresh(); // dynamically enable & disable the formatter if (dynamicFormatterRegistration) { @@ -248,37 +264,6 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } }); - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 500; - - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent(change => { - triggerValidation(change.document); - }); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }); - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => { - delete pendingValidationRequests[textDocument.uri]; - validateTextDocument(textDocument); - }, validationDelayMs); - } - function isValidationEnabled(languageId: string, settings: Settings = globalSettings) { const validationSettings = settings && settings.html && settings.html.validate; if (validationSettings) { @@ -287,7 +272,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return true; } - async function validateTextDocument(textDocument: TextDocument) { + async function validateTextDocument(textDocument: TextDocument): Promise { try { const version = textDocument.version; const diagnostics: Diagnostic[] = []; @@ -301,12 +286,13 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) pushAll(diagnostics, await mode.doValidation(latestTextDocument, settings)); } } - connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics }); + return diagnostics; } } } catch (e) { connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); } + return []; } connection.onCompletion(async (textDocumentPosition, token) => { diff --git a/extensions/html-language-features/server/src/utils/validation.ts b/extensions/html-language-features/server/src/utils/validation.ts index 5c5261cf9e5..adb13086391 100644 --- a/extensions/html-language-features/server/src/utils/validation.ts +++ b/extensions/html-language-features/server/src/utils/validation.ts @@ -3,15 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Connection, Diagnostic, Disposable, TextDocuments } from 'vscode-languageserver'; +import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; import { TextDocument } from 'vscode-html-languageservice'; -import { formatError } from './runner'; +import { formatError, runSafe } from './runner'; import { RuntimeEnvironment } from '../htmlServer'; export type Validator = (textDocument: TextDocument) => Promise; -export type DiagnosticPushSupport = { dispose(): void; triggerValidation(textDocument: TextDocument): void }; +export type DiagnosticsSupport = { + dispose(): void; + requestRefresh(): void; +}; -export function registerDiagnosticPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticPushSupport { +export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { const pendingValidationRequests: { [uri: string]: Disposable } = {}; const validationDelayMs = 500; @@ -55,10 +58,10 @@ export function registerDiagnosticPushSupport(documents: TextDocuments { + documents.all().forEach(triggerValidation); + }, dispose: () => { disposables.forEach(d => d.dispose()); disposables.length = 0; @@ -70,3 +73,36 @@ export function registerDiagnosticPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { + + function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { + return { + kind: DocumentDiagnosticReportKind.Full, + items: diagnostics + }; + } + + const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { + return runSafe(runtime, async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + return newDocumentDiagnosticReport(await validate(document)); + } + return newDocumentDiagnosticReport([]); + + }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); + }); + + function requestRefresh(): void { + connection.languages.diagnostics.refresh(); + } + + return { + requestRefresh, + dispose: () => { + registration.dispose(); + } + }; + +} diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index df8bb6faa35..5df112220d1 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -9,7 +9,7 @@ import { DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions, Diagnostic } from 'vscode-languageserver'; -import { formatError, runSafe, runSafeAsync } from './utils/runner'; +import { runSafe, runSafeAsync } from './utils/runner'; import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation'; import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; @@ -114,7 +114,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let resultLimit = Number.MAX_VALUE; let formatterMaxNumberOfEdits = Number.MAX_VALUE; - let diagnosticSupport: DiagnosticsSupport | undefined; + let diagnosticsSupport: DiagnosticsSupport | undefined; // After the server has started the client sends an initialize request. The server receives @@ -154,9 +154,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); if (pullDiagnosticSupport === undefined) { - diagnosticSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); + diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); } else { - diagnosticSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); + diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); } @@ -301,25 +301,18 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) needsRevalidation = languageService.resetSchema(uriOrUris); } if (needsRevalidation) { - for (const doc of documents.all()) { - triggerValidation(doc); - } + diagnosticsSupport?.requestRefresh(); } }); // Retry schema validation on all open documents - connection.onRequest(ForceValidateRequest.type, uri => { - return new Promise(resolve => { - const document = documents.get(uri); - if (document) { - updateConfiguration(); - validateTextDocument(document, diagnostics => { - resolve(diagnostics); - }); - } else { - resolve([]); - } - }); + connection.onRequest(ForceValidateRequest.type, async uri => { + const document = documents.get(uri); + if (document) { + updateConfiguration(); + return await validateTextDocument(document); + } + return []; }); connection.onRequest(LanguageStatusRequest.type, async uri => { @@ -365,8 +358,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } languageService.configure(languageSettings); - // Revalidate any open text documents - documents.all().forEach(triggerValidation); + diagnosticsSupport?.requestRefresh(); } async function validateTextDocument(textDocument: TextDocument): Promise { @@ -387,7 +379,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } }); if (hasChanges) { - documents.all().forEach(triggerValidation); + diagnosticsSupport?.requestRefresh(); } }); diff --git a/extensions/json-language-features/server/src/utils/validation.ts b/extensions/json-language-features/server/src/utils/validation.ts index 10130d2684f..a4d06d90cd6 100644 --- a/extensions/json-language-features/server/src/utils/validation.ts +++ b/extensions/json-language-features/server/src/utils/validation.ts @@ -58,8 +58,6 @@ export function registerDiagnosticsPushSupport(documents: TextDocuments { documents.all().forEach(triggerValidation); @@ -85,7 +83,7 @@ export function registerDiagnosticsPullSupport(documents: TextDocuments { + const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { @@ -103,6 +101,7 @@ export function registerDiagnosticsPullSupport(documents: TextDocuments { + registration.dispose(); } }; From f1b392ed556d963195ffac589d329325d5e6dc74 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 May 2022 22:35:59 +0200 Subject: [PATCH 202/350] polish names --- extensions/css-language-features/server/src/cssServer.ts | 4 ++-- extensions/html-language-features/server/src/htmlServer.ts | 4 ++-- extensions/json-language-features/server/src/jsonServer.ts | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index af55ec28f06..314aa8aebc0 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -101,8 +101,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); languageServices.less = getLESSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); - const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); - if (pullDiagnosticSupport === undefined) { + const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined); + if (supportsDiagnosticPull === undefined) { diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); } else { diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 3198ad16d96..99e3cb75bcd 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -184,8 +184,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; - const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); - if (pullDiagnosticSupport === undefined) { + const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined); + if (supportsDiagnosticPull === undefined) { diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); } else { diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 5df112220d1..9594d242f51 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -144,22 +144,19 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return c; } - - clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof initializationOptions.provideFormatter !== 'boolean'); foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); formatterMaxNumberOfEdits = initializationOptions.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; - const pullDiagnosticSupport = getClientCapability('textDocument.diagnostic', undefined); - if (pullDiagnosticSupport === undefined) { + const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined); + if (supportsDiagnosticPull === undefined) { diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument); } else { diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument); } - const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { From c0769274fa136b45799edeccc0d0a2f645b75caf Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 22 May 2022 20:04:19 +0200 Subject: [PATCH 203/350] update json schema (#150001) --- src/vs/base/common/jsonSchema.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index da614983689..81262c2f46a 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -46,6 +46,7 @@ export interface IJSONSchema { const?: any; contains?: IJSONSchema; propertyNames?: IJSONSchema; + examples?: any[]; // schema draft 07 $comment?: string; @@ -53,7 +54,27 @@ export interface IJSONSchema { then?: IJSONSchema; else?: IJSONSchema; - // VS Code extensions + // schema 2019-09 + unevaluatedProperties?: boolean | IJSONSchema; + unevaluatedItems?: boolean | IJSONSchema; + minContains?: number; + maxContains?: number; + deprecated?: boolean; + dependentRequired?: { [prop: string]: string[] }; + dependentSchemas?: IJSONSchemaMap; + $defs?: { [name: string]: IJSONSchema }; + $anchor?: string; + $recursiveRef?: string; + $recursiveAnchor?: string; + $vocabulary?: any; + + // schema 2020-12 + prefixItems?: IJSONSchema[]; + $dynamicRef?: string; + $dynamicAnchor?: string; + + // VSCode extensions + defaultSnippets?: IJSONSchemaSnippet[]; errorMessage?: string; patternErrorMessage?: string; From 62ed0040bc4cbb248f19272d268a18eb78f63278 Mon Sep 17 00:00:00 2001 From: Song Xie Date: Sun, 22 May 2022 14:21:34 -0700 Subject: [PATCH 204/350] Format date strings with the right locale --- .../workbench/contrib/extensions/browser/extensionEditor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 4cfe2517ef5..da2bc141afc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/extensionEditor'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; -import { OS } from 'vs/base/common/platform'; +import { OS, locale } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { Cache, CacheResult } from 'vs/base/common/cache'; import { Action, IAction } from 'vs/base/common/actions'; @@ -997,11 +997,11 @@ export class ExtensionEditor extends EditorPane { append(moreInfo, $('.more-info-entry', undefined, $('div', undefined, localize('release date', "Released on")), - $('div', undefined, new Date(gallery.releaseDate).toLocaleString(undefined, { hourCycle: 'h23' })) + $('div', undefined, new Date(gallery.releaseDate).toLocaleString(locale, { hourCycle: 'h23' })) ), $('.more-info-entry', undefined, $('div', undefined, localize('last updated', "Last updated")), - $('div', undefined, new Date(gallery.lastUpdated).toLocaleString(undefined, { hourCycle: 'h23' })) + $('div', undefined, new Date(gallery.lastUpdated).toLocaleString(locale, { hourCycle: 'h23' })) ) ); } From 17f17b429b44b55e158913159fc06279921a23e4 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 22 May 2022 18:58:42 -0700 Subject: [PATCH 205/350] remove remaining kernel state. --- .../browser/viewParts/notebookKernelActionViewItem.ts | 10 ---------- .../services/actions/common/menusExtensionPoint.ts | 2 +- .../extensions/common/extensionsApiProposals.ts | 2 +- ....d.ts => vscode.proposed.notebookKernelSource.d.ts} | 0 4 files changed, 2 insertions(+), 12 deletions(-) rename src/vscode-dts/{vscode.proposed.notebookProxyController.d.ts => vscode.proposed.notebookKernelSource.d.ts} (100%) diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 8b8f92277e2..1e7b6980a23 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -6,7 +6,6 @@ import 'vs/css!./notebookKernelActionViewItem'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; @@ -18,7 +17,6 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB export class NotebooKernelActionViewItem extends ActionViewItem { private _kernelLabel?: HTMLAnchorElement; - private _kernelDisposable: DisposableStore; constructor( actualAction: IAction, @@ -34,7 +32,6 @@ export class NotebooKernelActionViewItem extends ActionViewItem { this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this)); this._register(_notebookKernelService.onDidChangeSelectedNotebooks(this._update, this)); this._register(_notebookKernelService.onDidChangeSourceActions(this._update, this)); - this._kernelDisposable = this._register(new DisposableStore()); } override render(container: HTMLElement): void { @@ -96,7 +93,6 @@ export class NotebooKernelActionViewItem extends ActionViewItem { } private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { - this._kernelDisposable.clear(); this._action.enabled = true; this._action.class = ThemeIcon.asClassName(selectKernelIcon); const selectedOrSuggested = info.selected ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined); @@ -107,12 +103,6 @@ export class NotebooKernelActionViewItem extends ActionViewItem { if (!info.selected) { // special UI for selected kernel? } - - this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => { - if (e.state) { - this._action.label = this._generateKenrelLabel(selectedOrSuggested); - } - })); } else { // many kernels or no kernels this._action.label = localize('select', "Select Kernel"); diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index bceef1e438f..adf39476671 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -175,7 +175,7 @@ const apiMenus: IAPIMenu[] = [ key: 'notebook/kernelSource', id: MenuId.NotebookKernelSource, description: localize('notebook.kernelSource', "The contributed notebook kernel sources menu"), - proposed: 'notebookProxyController' + proposed: 'notebookKernelSource' }, { key: 'notebook/cell/title', diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index c562da5671f..d5ded005d72 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -38,10 +38,10 @@ export const allApiProposals = Object.freeze({ notebookEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditor.d.ts', notebookEditorDecorationType: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorDecorationType.d.ts', notebookEditorEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts', + notebookKernelSource: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', - notebookProxyController: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts', notebookWorkspaceEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts', portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', diff --git a/src/vscode-dts/vscode.proposed.notebookProxyController.d.ts b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts similarity index 100% rename from src/vscode-dts/vscode.proposed.notebookProxyController.d.ts rename to src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts From cb30029e0208451c0f7cf0801b4ada76521449d8 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 22 May 2022 19:36:01 -0700 Subject: [PATCH 206/350] show install from marketplace only when there is no kernels and commands at all. --- .../contrib/editorStatusBar/editorStatusBar.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index bb1bd41aeb1..fab2077edd9 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -212,14 +212,6 @@ registerAction2(class extends Action2 { }); } - if (!all.length) { - // there is no kernel, show the install from marketplace - quickPickItems.push({ - id: 'install', - label: nls.localize('installKernels', "Install kernels from the marketplace"), - }); - } - const sourceActions = notebookKernelService.getSourceActions(); if (sourceActions.length) { quickPickItems.push({ @@ -238,6 +230,14 @@ registerAction2(class extends Action2 { }); } + if (!all.length && !sourceActions.length) { + // there is no kernel, show the install from marketplace + quickPickItems.push({ + id: 'install', + label: nls.localize('installKernels', "Install kernels from the marketplace"), + }); + } + const pick = await quickInputService.pick(quickPickItems, { placeHolder: selected ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) From 01e4dbd95c7133e5a955d24ab47c5e60b0e252b1 Mon Sep 17 00:00:00 2001 From: rebornix Date: Sun, 22 May 2022 20:01:02 -0700 Subject: [PATCH 207/350] hide explicit IAction. --- .../editorStatusBar/editorStatusBar.ts | 13 ++-- .../browser/notebookExecutionServiceImpl.ts | 2 +- .../browser/notebookKernelServiceImpl.ts | 65 +++++++++++++------ .../viewParts/notebookKernelActionViewItem.ts | 13 ++-- .../notebook/common/notebookKernelService.ts | 18 +++-- 5 files changed, 71 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index fab2077edd9..20c8c594f12 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from 'vs/base/common/actions'; import { groupBy } from 'vs/base/common/arrays'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -29,7 +28,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -164,7 +163,7 @@ registerAction2(class extends Action2 { } type KernelPick = IQuickPickItem & { kernel: INotebookKernel }; - type SourcePick = IQuickPickItem & { action: IAction }; + type SourcePick = IQuickPickItem & { action: ISourceAction }; const configButton: IQuickInputButton = { iconClass: ThemeIcon.asClassName(configureKernelIcon), @@ -219,11 +218,11 @@ registerAction2(class extends Action2 { // label: nls.localize('sourceActions', "") }); - sourceActions.forEach(action => { + sourceActions.forEach(sourceAction => { const res = { - action, + action: sourceAction, picked: false, - label: action.label, + label: sourceAction.action.label, }; quickPickItems.push(res); @@ -262,7 +261,7 @@ registerAction2(class extends Action2 { await this._showKernelExtension(paneCompositeService, notebook.viewType); } else if ('action' in pick) { // selected explicilty, it should trigger the execution? - notebookKernelService.runSourceAction(pick.action); + pick.action.runAction(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 75b6b1a55dd..0be1459d36a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -85,7 +85,7 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis // no kernel at all const sourceActions = this._notebookKernelService.getSourceActions(); if (sourceActions.length === 1) { - await this._notebookKernelService.runSourceAction(sourceActions[0]); + await sourceActions[0].runAction(); kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 6e0151f222c..8466386bbdb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; @@ -46,6 +46,34 @@ class NotebookTextModelLikeId { } } +class SourceAction extends Disposable implements ISourceAction { + execution: Promise | undefined; + private readonly _onDidChangeState = this._register(new Emitter()); + readonly onDidChangeState = this._onDidChangeState.event; + + constructor( + readonly action: IAction, + ) { + super(); + } + + async runAction() { + if (this.execution) { + return this.execution; + } + + this.execution = this._runAction(); + this._onDidChangeState.fire(); + await this.execution; + this.execution = undefined; + this._onDidChangeState.fire(); + } + + private async _runAction(): Promise { + await this.action.run(); + } +} + export class NotebookKernelService extends Disposable implements INotebookKernelService { declare _serviceBrand: undefined; @@ -61,7 +89,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private readonly _onDidChangeNotebookAffinity = this._register(new Emitter()); private readonly _onDidChangeSourceActions = this._register(new Emitter()); private readonly _sourceMenu: IMenu; - private _sourceActions: IAction[]; + private _sourceActions: [ISourceAction, IDisposable][]; readonly onDidChangeSelectedNotebooks: Event = this._onDidChangeNotebookKernelBinding.event; readonly onDidAddKernel: Event = this._onDidAddKernel.event; @@ -111,25 +139,32 @@ export class NotebookKernelService extends Disposable implements INotebookKernel } private _initSourceActions() { - const loadActions = (menu: IMenu) => { + const loadActionsFromMenu = (menu: IMenu) => { const groups = menu.getActions({ shouldForwardArgs: true }); const actions: IAction[] = []; groups.forEach(group => { actions.push(...group[1]); }); - this._sourceActions = actions; + this._sourceActions = actions.map(action => { + const sourceAction = new SourceAction(action); + const stateChangeListener = sourceAction.onDidChangeState(() => { + this._onDidChangeSourceActions.fire(); + }); + return [sourceAction, stateChangeListener]; + }); this._onDidChangeSourceActions.fire(); }; this._register(this._sourceMenu.onDidChange(() => { - loadActions(this._sourceMenu); + loadActionsFromMenu(this._sourceMenu); })); - loadActions(this._sourceMenu); + loadActionsFromMenu(this._sourceMenu); } override dispose() { this._kernels.clear(); + this._sourceActions.forEach(a => a[1].dispose()); super.dispose(); } @@ -288,21 +323,11 @@ export class NotebookKernelService extends Disposable implements INotebookKernel this._onDidChangeNotebookAffinity.fire(); } - private _runningAction: IAction | undefined = undefined; - - getRunningSourceAction() { - return this._runningAction; + getRunningSourceActions() { + return this._sourceActions.filter(action => action[0].execution).map(action => action[0]); } - getSourceActions(): IAction[] { - return this._sourceActions; - } - - async runSourceAction(action: IAction): Promise { - this._runningAction = action; - this._onDidChangeSourceActions.fire(); - await action.run(); - this._runningAction = undefined; - this._onDidChangeSourceActions.fire(); + getSourceActions(): ISourceAction[] { + return this._sourceActions.map(a => a[0]); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index 1e7b6980a23..958a59df428 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -9,7 +9,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { executingStateIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelMatchResult, INotebookKernelService, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -59,9 +59,9 @@ export class NotebooKernelActionViewItem extends ActionViewItem { return; } - const runningAction = this._notebookKernelService.getRunningSourceAction(); - if (runningAction) { - return this._updateActionFromSourceAction(runningAction, true); + const runningActions = this._notebookKernelService.getRunningSourceActions(); + if (runningActions.length) { + return this._updateActionFromSourceAction(runningActions[0] /** TODO handle multiple actions state */, true); } const info = this._notebookKernelService.getMatchingKernel(notebook); @@ -72,10 +72,11 @@ export class NotebooKernelActionViewItem extends ActionViewItem { this._updateActionFromKernelInfo(info); } - private _updateActionFromSourceAction(sourceAction: IAction, running: boolean) { + private _updateActionFromSourceAction(sourceAction: ISourceAction, running: boolean) { + const action = sourceAction.action; this.action.class = running ? ThemeIcon.asClassName(ThemeIcon.modify(executingStateIcon, 'spin')) : ThemeIcon.asClassName(selectKernelIcon); this.updateClass(); - this._action.label = sourceAction.label; + this._action.label = action.label; this._action.enabled = true; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 0068bd968e1..2ab657faf5b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -64,6 +64,13 @@ export interface INotebookProxyKernelChangeEvent extends INotebookKernelChangeEv connectionState?: true; } +export interface ISourceAction { + readonly action: IAction; + readonly onDidChangeState: Event; + execution: Promise | undefined; + runAction: () => Promise; +} + export interface INotebookTextModelLike { uri: URI; viewType: string } export const INotebookKernelService = createDecorator('INotebookKernelService'); @@ -75,8 +82,6 @@ export interface INotebookKernelService { readonly onDidRemoveKernel: Event; readonly onDidChangeSelectedNotebooks: Event; readonly onDidChangeNotebookAffinity: Event; - readonly onDidChangeSourceActions: Event; - registerKernel(kernel: INotebookKernel): IDisposable; getMatchingKernel(notebook: INotebookTextModelLike): INotebookKernelMatchResult; @@ -109,8 +114,9 @@ export interface INotebookKernelService { */ updateKernelNotebookAffinity(kernel: INotebookKernel, notebook: URI, preference: number | undefined): void; - getSourceActions(): IAction[]; - getRunningSourceAction(): IAction | undefined; - - runSourceAction(action: IAction): Promise; + //#region Kernel source actions + readonly onDidChangeSourceActions: Event; + getSourceActions(): ISourceAction[]; + getRunningSourceActions(): ISourceAction[]; + //#endregion } From 801eaecfbce0b4bccaa46de85fb7513cf1f8cd7f Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 23 May 2022 09:28:08 +0200 Subject: [PATCH 208/350] change label to `Show Search Modes...` --- src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts index 77e1ebb32eb..63dd190ea82 100644 --- a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts @@ -119,7 +119,7 @@ export class TitleMenuControl { const container = document.createElement('span'); container.classList.add('all-options'); parent.appendChild(container); - const action = new Action('all', localize('all', "Show Quick Pick Options..."), Codicon.chevronDown.classNames, true, () => { + const action = new Action('all', localize('all', "Show Search Modes..."), Codicon.chevronDown.classNames, true, () => { quickInputService.quickAccess.show('?'); }); const dropdown = new ActionViewItem(undefined, action, { icon: true, label: false, hoverDelegate }); From 8a27b48cff9d19c85b1d253e7046dd017d7df46a Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 23 May 2022 11:52:26 +0200 Subject: [PATCH 209/350] when temporarily showing/hiding inlay hints use the same "redraw range" that was used to compute inlays We would get all inlay hints but update the visible ranges. This duplicates all inlays that are outside the ranges, fixes https://github.com/microsoft/vscode/issues/149572 --- .../contrib/inlayHints/browser/inlayHintsController.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 9044a21c70d..0d234f8a4e9 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -253,9 +253,9 @@ export class InlayHintsController implements IEditorContribution { const newRenderMode = e.altKey && e.ctrlKey ? altMode : defaultMode; if (newRenderMode !== this._activeRenderMode) { this._activeRenderMode = newRenderMode; - const ranges = this._getHintsRanges(); - const copies = this._copyInlayHintsWithCurrentAnchor(this._editor.getModel()); - this._updateHintsDecorators(ranges, copies); + const model = this._editor.getModel(); + const copies = this._copyInlayHintsWithCurrentAnchor(model); + this._updateHintsDecorators([model.getFullModelRange()], copies); scheduler.schedule(0); } })); @@ -393,7 +393,7 @@ export class InlayHintsController implements IEditorContribution { } // return inlay hints but with an anchor that reflects "updates" - // that happens after receiving them, e.g adding new lines before a hint + // that happened after receiving them, e.g adding new lines before a hint private _copyInlayHintsWithCurrentAnchor(model: ITextModel): InlayHintItem[] { const items = new Map(); for (const [id, obj] of this._decorationsMetadata) { From c3bddb2a4379ba203907f23c911504b639aeda17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 23 May 2022 16:01:40 +0200 Subject: [PATCH 210/350] adopt policy from configuration service --- src/vs/code/electron-main/app.ts | 2 +- .../electron-main/abstractUpdateService.ts | 6 ++-- .../electron-main/updateService.darwin.ts | 4 +-- .../electron-main/updateService.win32.ts | 28 ------------------- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index a162ae7db70..2d7e71bef34 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1148,7 +1148,7 @@ export class CodeApplication extends Disposable { // Initialize update service const updateService = accessor.get(IUpdateService); if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { - await updateService.initialize(); + updateService.initialize(); } // Start to fetch shell environment (if needed) after window has opened diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 86737bd1657..42b9e8cee7a 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -57,7 +57,7 @@ export abstract class AbstractUpdateService implements IUpdateService { * optimization, to avoid using extra CPU cycles before first window open. * https://github.com/microsoft/vscode/issues/89784 */ - async initialize(): Promise { + initialize(): void { if (!this.environmentMainService.isBuilt) { return; // updates are never enabled when running out of sources } @@ -72,7 +72,7 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - const updateMode = await this.getUpdateMode(); + const updateMode = this.getUpdateMode(); const quality = this.getProductQuality(updateMode); if (!quality) { @@ -104,7 +104,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } } - protected async getUpdateMode(): Promise<'none' | 'manual' | 'start' | 'default'> { + protected getUpdateMode(): 'none' | 'manual' | 'start' | 'default' { return getMigratedSettingValue<'none' | 'manual' | 'start' | 'default'>(this.configurationService, 'update.mode', 'update.channel'); } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 688ea356ef1..0c008ea8504 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -38,8 +38,8 @@ export class DarwinUpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - override async initialize(): Promise { - await super.initialize(); + override initialize(): void { + super.initialize(); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index e3661ce4bbc..fa2490cdfca 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -47,14 +47,6 @@ function getUpdateType(): UpdateType { return _updateType; } -function validateUpdateModeValue(value: string | undefined): 'none' | 'manual' | 'start' | 'default' | undefined { - if (value === 'none' || value === 'manual' || value === 'start' || value === 'default') { - return value; - } else { - return undefined; - } -} - export class Win32UpdateService extends AbstractUpdateService { private availableUpdate: IAvailableUpdate | undefined; @@ -79,26 +71,6 @@ export class Win32UpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - protected override async getUpdateMode(): Promise<'none' | 'manual' | 'start' | 'default'> { - if (this.productService.win32RegValueName) { - const policyKey = `Software\\Policies\\Microsoft\\${this.productService.win32RegValueName}`; - const [hklm, hkcu] = await Promise.all([ - this.nativeHostMainService.windowsGetStringRegKey(undefined, 'HKEY_LOCAL_MACHINE', policyKey, 'UpdateMode').then(validateUpdateModeValue), - this.nativeHostMainService.windowsGetStringRegKey(undefined, 'HKEY_CURRENT_USER', policyKey, 'UpdateMode').then(validateUpdateModeValue) - ]); - - if (hklm) { - this.logService.info(`update#getUpdateMode: 'UpdateMode' policy defined in 'HKLM\\${policyKey}':`, hklm); - return hklm; - } else if (hkcu) { - this.logService.info(`update#getUpdateMode: 'UpdateMode' policy defined in 'HKCU\\${policyKey}':`, hkcu); - return hkcu; - } - } - - return await super.getUpdateMode(); - } - protected buildUpdateFeedUrl(quality: string): string | undefined { let platform = 'win32'; From 553fc81c7ecea8af02f6580a6d1a3d35941dad3a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 23 May 2022 16:06:50 +0200 Subject: [PATCH 211/350] Problems view - add toggle to view problems in a tree/table (#149943) --- src/vs/base/browser/ui/table/tableWidget.ts | 4 + .../contrib/markers/browser/constants.ts | 2 + .../markers/browser/markers.contribution.ts | 52 +- .../contrib/markers/browser/markers.ts | 2 + .../contrib/markers/browser/markersModel.ts | 14 + .../contrib/markers/browser/markersTable.ts | 527 +++++++++++++++ .../markers/browser/markersTreeViewer.ts | 38 +- .../contrib/markers/browser/markersView.ts | 605 +++++++++++------- .../contrib/markers/browser/media/markers.css | 84 +++ .../contrib/markers/browser/messages.ts | 1 + 10 files changed, 1094 insertions(+), 235 deletions(-) create mode 100644 src/vs/workbench/contrib/markers/browser/markersTable.ts diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index eb298a582c7..03f6f284785 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -341,6 +341,10 @@ export class Table implements ISpliceable, IThemable, IDisposable { return this.list.getFocusedElements(); } + getRelativeTop(index: number): number | null { + return this.list.getRelativeTop(index); + } + reveal(index: number, relativeTop?: number): void { this.list.reveal(index, relativeTop); } diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts index 82f1acb1779..a883b3bf148 100644 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ b/src/vs/workbench/contrib/markers/browser/constants.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; export default { MARKERS_CONTAINER_ID: 'workbench.panel.markers', @@ -23,6 +24,7 @@ export default { MARKER_SHOW_QUICK_FIX: 'problems.action.showQuickFixes', TOGGLE_MARKERS_VIEW_ACTION_ID: 'workbench.actions.view.toggleProblems', + MarkersViewModeContextKey: new RawContextKey('problemsViewMode', MarkersViewMode.Tree), MarkersViewSmallLayoutContextKey: new RawContextKey(`problemsView.smallLayout`, false), MarkersTreeVisibilityContextKey: new RawContextKey('problemsVisibility', false), MarkerFocusContextKey: new RawContextKey('problemFocus', false), diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index b17ef050f94..ffd15324657 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -11,7 +11,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; +import { MarkersView, MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -97,6 +97,12 @@ Registry.as(Extensions.Configuration).registerConfigurat 'type': 'boolean', 'default': true }, + 'problems.defaultViewMode': { + 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_VIEW_MODE, + 'type': 'string', + 'default': 'tree', + 'enum': ['table', 'tree'], + }, 'problems.showCurrentInStatus': { 'description': Messages.PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS, 'type': 'boolean', @@ -148,6 +154,48 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); // actions +registerAction2(class extends ViewAction { + constructor() { + super({ + id: `workbench.actions.table.${Constants.MARKERS_VIEW_ID}.viewAsTree`, + title: localize('viewAsTree', "View as Tree"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Table)), + group: 'navigation', + order: 3 + }, + icon: Codicon.listTree, + viewId: Constants.MARKERS_VIEW_ID + }); + } + + async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise { + view.setViewMode(MarkersViewMode.Tree); + } +}); + +registerAction2(class extends ViewAction { + constructor() { + super({ + id: `workbench.actions.table.${Constants.MARKERS_VIEW_ID}.viewAsTable`, + title: localize('viewAsTable', "View as Table"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), + group: 'navigation', + order: 3 + }, + icon: Codicon.listFlat, + viewId: Constants.MARKERS_VIEW_ID + }); + } + + async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise { + view.setViewMode(MarkersViewMode.Table); + } +}); + registerAction2(class extends Action2 { constructor() { super({ @@ -345,7 +393,7 @@ registerAction2(class extends ViewAction { title: localize('collapseAll', "Collapse All"), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), group: 'navigation', order: 2, }, diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index a62b4a553a4..c65df45418a 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -13,6 +13,7 @@ import { MarkersFilters } from 'vs/workbench/contrib/markers/browser/markersView import { Event } from 'vs/base/common/event'; import { IView } from 'vs/workbench/common/views'; import { MarkerElement, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; export interface IMarkersView extends IView { @@ -30,6 +31,7 @@ export interface IMarkersView extends IView { collapseAll(): void; setMultiline(multiline: boolean): void; + setViewMode(viewMode: MarkersViewMode): void; } export class ActivityUpdater extends Disposable implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/markers/browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts index c1155aa2391..d5c34bd8982 100644 --- a/src/vs/workbench/contrib/markers/browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -13,6 +13,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Hasher } from 'vs/base/common/hash'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { splitLines } from 'vs/base/common/strings'; +import { IMatch } from 'vs/base/common/filters'; export type MarkerElement = ResourceMarkers | Marker | RelatedInformation; @@ -117,6 +118,19 @@ export class Marker { } } +export class MarkerTableItem extends Marker { + constructor( + marker: Marker, + readonly sourceMatches?: IMatch[], + readonly codeMatches?: IMatch[], + readonly messageMatches?: IMatch[], + readonly fileMatches?: IMatch[], + readonly ownerMatches?: IMatch[], + ) { + super(marker.id, marker.marker, marker.relatedInformation); + } +} + export class RelatedInformation { constructor( diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts new file mode 100644 index 00000000000..0b807b99d6d --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -0,0 +1,527 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import * as DOM from 'vs/base/browser/dom'; +import * as network from 'vs/base/common/network'; +import { Event } from 'vs/base/common/event'; +import { ITableContextMenuEvent, ITableEvent, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenEvent, IWorkbenchTableOptions, WorkbenchTable } from 'vs/platform/list/browser/listService'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { Marker, MarkerTableItem, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; +import { Link } from 'vs/platform/opener/browser/link'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MarkersViewModel } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; +import { IAction } from 'vs/base/common/actions'; +import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; +import { DomEmitter } from 'vs/base/browser/event'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IProblemsWidget } from 'vs/workbench/contrib/markers/browser/markersView'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; + +const $ = DOM.$; + +interface IMarkerIconColumnTemplateData { + readonly icon: HTMLElement; + readonly actionBar: ActionBar; +} + +interface IMarkerCodeColumnTemplateData { + readonly codeColumn: HTMLElement; + readonly sourceLabel: HighlightedLabel; + readonly codeLabel: HighlightedLabel; + readonly codeLink: Link; +} + +interface IMarkerFileColumnTemplateData { + readonly fileLabel: HighlightedLabel; + readonly positionLabel: HighlightedLabel; +} + + +interface IMarkerHighlightedLabelColumnTemplateData { + readonly highlightedLabel: HighlightedLabel; +} + +class MarkerSeverityColumnRenderer implements ITableRenderer{ + + static readonly TEMPLATE_ID = 'severity'; + + readonly templateId: string = MarkerSeverityColumnRenderer.TEMPLATE_ID; + + constructor( + private readonly markersViewModel: MarkersViewModel, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + renderTemplate(container: HTMLElement): IMarkerIconColumnTemplateData { + const severityColumn = DOM.append(container, $('.severity')); + const icon = DOM.append(severityColumn, $('')); + + const actionBarColumn = DOM.append(container, $('.actions')); + const actionBar = new ActionBar(actionBarColumn, { + actionViewItemProvider: (action: IAction) => action.id === QuickFixAction.ID ? this.instantiationService.createInstance(QuickFixActionViewItem, action) : undefined, + animated: false + }); + + return { actionBar, icon }; + } + + renderElement(element: MarkerTableItem, index: number, templateData: IMarkerIconColumnTemplateData, height: number | undefined): void { + const toggleQuickFix = (enabled?: boolean) => { + if (!isUndefinedOrNull(enabled)) { + const container = DOM.findParentWithClass(templateData.icon, 'monaco-table-td')!; + container.classList.toggle('quickFix', enabled); + } + }; + + templateData.icon.className = `marker-icon codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; + + templateData.actionBar.clear(); + const viewModel = this.markersViewModel.getViewModel(element); + if (viewModel) { + const quickFixAction = viewModel.quickFixAction; + templateData.actionBar.push([quickFixAction], { icon: true, label: false }); + toggleQuickFix(viewModel.quickFixAction.enabled); + + quickFixAction.onDidChange(({ enabled }) => toggleQuickFix(enabled)); + quickFixAction.onShowQuickFixes(() => { + const quickFixActionViewItem = templateData.actionBar.viewItems[0]; + if (quickFixActionViewItem) { + quickFixActionViewItem.showQuickFixes(); + } + }); + } + } + + disposeTemplate(templateData: IMarkerIconColumnTemplateData): void { } +} + +class MarkerCodeColumnRenderer implements ITableRenderer { + static readonly TEMPLATE_ID = 'code'; + + readonly templateId: string = MarkerCodeColumnRenderer.TEMPLATE_ID; + + constructor( + @IOpenerService private readonly openerService: IOpenerService + ) { } + + renderTemplate(container: HTMLElement): IMarkerCodeColumnTemplateData { + const codeColumn = DOM.append(container, $('.code')); + + const sourceLabel = new HighlightedLabel(codeColumn); + sourceLabel.element.classList.add('source-label'); + + const codeLabel = new HighlightedLabel(codeColumn); + codeLabel.element.classList.add('code-label'); + + const codeLink = new Link(codeColumn, { href: '', label: '' }, {}, this.openerService); + + return { codeColumn, sourceLabel, codeLabel, codeLink }; + } + + renderElement(element: MarkerTableItem, index: number, templateData: IMarkerCodeColumnTemplateData, height: number | undefined): void { + if (element.marker.source && element.marker.code) { + templateData.codeColumn.classList.toggle('code-link', typeof element.marker.code !== 'string'); + + if (typeof element.marker.code === 'string') { + templateData.sourceLabel.set(element.marker.source, element.sourceMatches); + templateData.codeLabel.set(element.marker.code, element.codeMatches); + } else { + templateData.sourceLabel.set(element.marker.source, element.sourceMatches); + + const codeLinkLabel = new HighlightedLabel($('.code-link-label')); + codeLinkLabel.set(element.marker.code.value, element.codeMatches); + + templateData.codeLink.link = { + href: element.marker.code.target.toString(), + title: element.marker.code.target.toString(), + label: codeLinkLabel.element, + }; + } + } + } + + disposeTemplate(templateData: IMarkerCodeColumnTemplateData): void { } +} + +class MarkerMessageColumnRenderer implements ITableRenderer{ + + static readonly TEMPLATE_ID = 'message'; + + readonly templateId: string = MarkerMessageColumnRenderer.TEMPLATE_ID; + + renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData { + const fileColumn = DOM.append(container, $('.message')); + const highlightedLabel = new HighlightedLabel(fileColumn); + + return { highlightedLabel }; + } + + renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void { + templateData.highlightedLabel.set(element.marker.message, element.messageMatches); + } + + disposeTemplate(templateData: IMarkerHighlightedLabelColumnTemplateData): void { } +} + +class MarkerFileColumnRenderer implements ITableRenderer{ + + static readonly TEMPLATE_ID = 'file'; + + readonly templateId: string = MarkerFileColumnRenderer.TEMPLATE_ID; + + constructor( + @ILabelService private readonly labelService: ILabelService + ) { } + + renderTemplate(container: HTMLElement): IMarkerFileColumnTemplateData { + const fileColumn = DOM.append(container, $('.file')); + const fileLabel = new HighlightedLabel(fileColumn); + fileLabel.element.classList.add('file-label'); + const positionLabel = new HighlightedLabel(fileColumn); + positionLabel.element.classList.add('file-position'); + + return { fileLabel, positionLabel }; + } + + renderElement(element: MarkerTableItem, index: number, templateData: IMarkerFileColumnTemplateData, height: number | undefined): void { + templateData.fileLabel.set(this.labelService.getUriLabel(element.marker.resource, { relative: true }), element.fileMatches); + templateData.positionLabel.set(Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.marker.startLineNumber, element.marker.startColumn), undefined); + } + + disposeTemplate(templateData: IMarkerFileColumnTemplateData): void { } +} + +class MarkerOwnerColumnRenderer implements ITableRenderer{ + + static readonly TEMPLATE_ID = 'owner'; + + readonly templateId: string = MarkerOwnerColumnRenderer.TEMPLATE_ID; + + renderTemplate(container: HTMLElement): IMarkerHighlightedLabelColumnTemplateData { + const fileColumn = DOM.append(container, $('.owner')); + const highlightedLabel = new HighlightedLabel(fileColumn); + return { highlightedLabel }; + } + + renderElement(element: MarkerTableItem, index: number, templateData: IMarkerHighlightedLabelColumnTemplateData, height: number | undefined): void { + templateData.highlightedLabel.set(element.marker.owner, element.ownerMatches); + } + + disposeTemplate(templateData: IMarkerHighlightedLabelColumnTemplateData): void { } +} + +class MarkersTableVirtualDelegate implements ITableVirtualDelegate { + static readonly HEADER_ROW_HEIGHT = 24; + static readonly ROW_HEIGHT = 24; + readonly headerRowHeight = MarkersTableVirtualDelegate.HEADER_ROW_HEIGHT; + + getHeight(item: any) { + return MarkersTableVirtualDelegate.ROW_HEIGHT; + } +} + +export class MarkersTable extends Disposable implements IProblemsWidget { + + private _itemCount: number = 0; + private readonly table: WorkbenchTable; + + constructor( + private readonly container: HTMLElement, + private readonly markersViewModel: MarkersViewModel, + private resourceMarkers: ResourceMarkers[], + private filterOptions: FilterOptions, + options: IWorkbenchTableOptions, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILabelService private readonly labelService: ILabelService, + ) { + super(); + + this.table = this.instantiationService.createInstance(WorkbenchTable, + 'Markers', + this.container, + new MarkersTableVirtualDelegate(), + [ + { + label: '', + tooltip: '', + weight: 0, + minimumWidth: 36, + maximumWidth: 36, + templateId: MarkerSeverityColumnRenderer.TEMPLATE_ID, + project(row: Marker): Marker { return row; } + }, + { + label: localize('codeColumnLabel', "Code"), + tooltip: '', + weight: 1, + minimumWidth: 100, + maximumWidth: 200, + templateId: MarkerCodeColumnRenderer.TEMPLATE_ID, + project(row: Marker): Marker { return row; } + }, + { + label: localize('messageColumnLabel', "Message"), + tooltip: '', + weight: 4, + templateId: MarkerMessageColumnRenderer.TEMPLATE_ID, + project(row: Marker): Marker { return row; } + }, + { + label: localize('fileColumnLabel', "File"), + tooltip: '', + weight: 2, + templateId: MarkerFileColumnRenderer.TEMPLATE_ID, + project(row: Marker): Marker { return row; } + }, + { + label: localize('sourceColumnLabel', "Source"), + tooltip: '', + weight: 1, + minimumWidth: 100, + maximumWidth: 200, + templateId: MarkerOwnerColumnRenderer.TEMPLATE_ID, + project(row: Marker): Marker { return row; } + } + ], + [ + this.instantiationService.createInstance(MarkerSeverityColumnRenderer, this.markersViewModel), + this.instantiationService.createInstance(MarkerCodeColumnRenderer), + this.instantiationService.createInstance(MarkerMessageColumnRenderer), + this.instantiationService.createInstance(MarkerFileColumnRenderer), + this.instantiationService.createInstance(MarkerOwnerColumnRenderer), + ], + options + ) as WorkbenchTable; + + const list = this.table.domNode.querySelector('.monaco-list-rows')! as HTMLElement; + + // mouseover/mouseleave event handlers + const onRowHover = Event.chain(this._register(new DomEmitter(list, 'mouseover')).event) + .map(e => DOM.findParentWithClass(e.target as HTMLElement, 'monaco-list-row', 'monaco-list-rows')) + .filter(((e: HTMLElement | null) => !!e) as any) + .map(e => parseInt(e.getAttribute('data-index')!)) + .event; + + const onListLeave = Event.map(this._register(new DomEmitter(list, 'mouseleave')).event, () => -1); + + const onRowHoverOrLeave = Event.latch(Event.any(onRowHover, onListLeave)); + const onRowPermanentHover = Event.debounce(onRowHoverOrLeave, (_, e) => e, 500); + + this._register(onRowPermanentHover(e => { + if (e !== -1 && this.table.row(e)) { + this.markersViewModel.onMarkerMouseHover(this.table.row(e)); + } + })); + } + + get contextKeyService(): IContextKeyService { + return this.table.contextKeyService; + } + + get onContextMenu(): Event> { + return this.table.onContextMenu; + } + + get onDidOpen(): Event> { + return this.table.onDidOpen; + } + + get onDidChangeFocus(): Event> { + return this.table.onDidChangeFocus; + } + + get onDidChangeSelection(): Event> { + return this.table.onDidChangeSelection; + } + + collapseMarkers(): void { } + + domFocus(): void { + this.table.domFocus(); + } + + filterMarkers(resourceMarkers: ResourceMarkers[], filterOptions: FilterOptions): void { + this.filterOptions = filterOptions; + this.reset(resourceMarkers); + } + + getFocus(): (MarkerTableItem | null)[] { + const focus = this.table.getFocus(); + return focus.length > 0 ? [this.table.row(focus[0])] : []; + } + + getHTMLElement(): HTMLElement { + return this.table.getHTMLElement(); + } + + getRelativeTop(marker: MarkerTableItem | null): number | null { + return marker ? this.table.getRelativeTop(this.table.indexOf(marker)) : null; + } + + getSelection(): (MarkerTableItem | null)[] { + const selection = this.table.getSelection(); + return selection.length > 0 ? [this.table.row(selection[0])] : []; + } + + getVisibleItemCount(): number { + return this._itemCount; + } + + isVisible(): boolean { + return !this.container.classList.contains('hidden'); + } + + layout(height: number, width: number): void { + this.table.layout(height, width); + } + + reset(resourceMarkers: ResourceMarkers[]): void { + this.resourceMarkers = resourceMarkers; + + const items: MarkerTableItem[] = []; + for (const resourceMarker of this.resourceMarkers) { + for (const marker of resourceMarker.markers) { + if (marker.resource.scheme === network.Schemas.walkThrough || marker.resource.scheme === network.Schemas.walkThroughSnippet) { + continue; + } + + // Exclude pattern + if (this.filterOptions.excludesMatcher.matches(marker.resource)) { + continue; + } + + // Include pattern + if (this.filterOptions.includesMatcher.matches(marker.resource)) { + items.push(new MarkerTableItem(marker)); + continue; + } + + // Severity filter + const matchesSeverity = this.filterOptions.showErrors && MarkerSeverity.Error === marker.marker.severity || + this.filterOptions.showWarnings && MarkerSeverity.Warning === marker.marker.severity || + this.filterOptions.showInfos && MarkerSeverity.Info === marker.marker.severity; + + if (!matchesSeverity) { + continue; + } + + // Text filter + if (this.filterOptions.textFilter.text) { + const sourceMatches = marker.marker.source ? FilterOptions._filter(this.filterOptions.textFilter.text, marker.marker.source) ?? undefined : undefined; + const codeMatches = marker.marker.code ? FilterOptions._filter(this.filterOptions.textFilter.text, typeof marker.marker.code === 'string' ? marker.marker.code : marker.marker.code.value) ?? undefined : undefined; + const messageMatches = FilterOptions._messageFilter(this.filterOptions.textFilter.text, marker.marker.message) ?? undefined; + const fileMatches = FilterOptions._messageFilter(this.filterOptions.textFilter.text, this.labelService.getUriLabel(marker.resource, { relative: true })) ?? undefined; + const ownerMatches = FilterOptions._messageFilter(this.filterOptions.textFilter.text, marker.marker.owner) ?? undefined; + + const matched = sourceMatches || codeMatches || messageMatches || fileMatches || ownerMatches; + if ((matched && !this.filterOptions.textFilter.negate) || (!matched && this.filterOptions.textFilter.negate)) { + items.push(new MarkerTableItem(marker, sourceMatches, codeMatches, messageMatches, fileMatches, ownerMatches)); + } + + continue; + } + + items.push(new MarkerTableItem(marker)); + } + } + this._itemCount = items.length; + this.table.splice(0, Number.POSITIVE_INFINITY, items.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))); + } + + revealMarkers(activeResource: ResourceMarkers | null, focus: boolean, lastSelectedRelativeTop: number): void { + if (activeResource) { + const activeResourceIndex = this.resourceMarkers.indexOf(activeResource); + + if (activeResourceIndex !== -1) { + if (this.hasSelectedMarkerFor(activeResource)) { + const tableSelection = this.table.getSelection(); + this.table.reveal(tableSelection[0], lastSelectedRelativeTop); + + if (focus) { + this.table.setFocus(tableSelection); + } + } else { + this.table.reveal(activeResourceIndex, 0); + + if (focus) { + this.table.setFocus([activeResourceIndex]); + this.table.setSelection([activeResourceIndex]); + } + } + } + } else if (focus) { + this.table.setSelection([]); + this.table.focusFirst(); + } + } + + setAriaLabel(label: string): void { + this.table.domNode.ariaLabel = label; + } + + setMarkerSelection(marker?: Marker): void { + if (this.isVisible()) { + if (marker) { + const index = this.findMarkerIndex(marker); + if (index !== -1) { + this.table.setFocus([index]); + this.table.setSelection([index]); + } + } else if (this.getSelection().length === 0 && this.getVisibleItemCount() > 0) { + this.table.setFocus([0]); + this.table.setSelection([0]); + } + } + } + + toggleVisibility(hide: boolean): void { + this.container.classList.toggle('hidden', hide); + } + + update(resourceMarkers: ResourceMarkers[]): void { + for (const resourceMarker of resourceMarkers) { + const index = this.resourceMarkers.indexOf(resourceMarker); + this.resourceMarkers.splice(index, 1, resourceMarker); + } + this.reset(this.resourceMarkers); + } + + updateMarker(marker: Marker): void { + this.table.rerender(); + } + + private findMarkerIndex(marker: Marker): number { + for (let index = 0; index < this.table.length; index++) { + if (this.table.row(index).marker === marker.marker) { + return index; + } + } + + return -1; + } + + private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { + let selectedElement = this.getSelection(); + if (selectedElement && selectedElement.length > 0) { + if (selectedElement[0] instanceof Marker) { + if (resource.has((selectedElement[0]).marker.resource)) { + return true; + } + } + } + + return false; + } +} diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 5b9b0c78abf..f6e04799b34 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -10,7 +10,7 @@ import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { ResourceMarkers, Marker, RelatedInformation, MarkerElement } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { ResourceMarkers, Marker, RelatedInformation, MarkerElement, MarkerTableItem } from 'vs/workbench/contrib/markers/browser/markersModel'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -48,6 +48,9 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Link } from 'vs/platform/opener/browser/link'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; interface IResourceMarkersTemplateData { resourceLabel: IResourceLabel; @@ -65,7 +68,7 @@ interface IRelatedInformationTemplateData { description: HighlightedLabel; } -export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvider { +export class MarkersWidgetAccessibilityProvider implements IListAccessibilityProvider { constructor(@ILabelService private readonly labelService: ILabelService) { } @@ -73,12 +76,12 @@ export class MarkersTreeAccessibilityProvider implements IListAccessibilityProvi return localize('problemsView', "Problems View"); } - public getAriaLabel(element: MarkerElement): string | null { + public getAriaLabel(element: MarkerElement | MarkerTableItem): string | null { if (element instanceof ResourceMarkers) { const path = this.labelService.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length, element.name, paths.dirname(path)); } - if (element instanceof Marker) { + if (element instanceof Marker || element instanceof MarkerTableItem) { return Messages.MARKERS_TREE_ARIA_LABEL_MARKER(element); } if (element instanceof RelatedInformation) { @@ -693,6 +696,9 @@ export class MarkersViewModel extends Disposable { private readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChangeViewMode: Emitter = this._register(new Emitter()); + readonly onDidChangeViewMode: Event = this._onDidChangeViewMode.event; + private readonly markersViewStates: Map = new Map(); private readonly markersPerResource: Map = new Map(); @@ -700,13 +706,20 @@ export class MarkersViewModel extends Disposable { private hoveredMarker: Marker | null = null; private hoverDelayer: Delayer = new Delayer(300); + private viewModeContextKey: IContextKey; constructor( multiline: boolean = true, - @IInstantiationService private instantiationService: IInstantiationService + viewMode: MarkersViewMode = MarkersViewMode.Tree, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this._multiline = multiline; + this._viewMode = viewMode; + + this.viewModeContextKey = Constants.MarkersViewModeContextKey.bindTo(this.contextKeyService); + this.viewModeContextKey.set(viewMode); } add(marker: Marker): void { @@ -789,6 +802,21 @@ export class MarkersViewModel extends Disposable { } } + private _viewMode: MarkersViewMode = MarkersViewMode.Tree; + get viewMode(): MarkersViewMode { + return this._viewMode; + } + + set viewMode(value: MarkersViewMode) { + if (this._viewMode === value) { + return; + } + + this._viewMode = value; + this._onDidChangeViewMode.fire(value); + this.viewModeContextKey.set(value); + } + override dispose(): void { this.markersViewStates.forEach(({ disposables }) => dispose(disposables)); this.markersViewStates.clear(); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 17e438b53ce..b20df6c492a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -11,7 +11,7 @@ import { IAction, Action, Separator } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri, MarkerElement } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri, MarkerElement, MarkerTableItem } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -23,14 +23,14 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Iterable } from 'vs/base/common/iterator'; -import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeContextMenuEvent, ITreeRenderer, ITreeEvent } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; -import { WorkbenchObjectTree, IListService, IWorkbenchObjectTreeOptions } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, IListService, IWorkbenchObjectTreeOptions, IOpenEvent } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IExpression } from 'vs/base/common/glob'; import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, MarkersTreeAccessibilityProvider, MarkersViewModel } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; +import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, MarkersWidgetAccessibilityProvider, MarkersViewModel } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionBar, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -40,7 +40,7 @@ import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -57,6 +57,8 @@ import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/ed import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ResourceListDnDHandler } from 'vs/workbench/browser/dnd'; +import { ITableContextMenuEvent, ITableEvent } from 'vs/base/browser/ui/table/table'; +import { MarkersTable } from 'vs/workbench/contrib/markers/browser/markersTable'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { @@ -67,6 +69,38 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterab }); } +export const enum MarkersViewMode { + Table = 'table', + Tree = 'tree' +} + +export interface IProblemsWidget { + get contextKeyService(): IContextKeyService; + + get onContextMenu(): Event> | Event>; + get onDidChangeFocus(): Event> | Event>; + get onDidChangeSelection(): Event> | Event>; + get onDidOpen(): Event>; + + collapseMarkers(): void; + dispose(): void; + domFocus(): void; + filterMarkers(resourceMarkers: ResourceMarkers[], filterOptions: FilterOptions): void; + getFocus(): (MarkerElement | MarkerTableItem | null)[]; + getHTMLElement(): HTMLElement; + getRelativeTop(location: MarkerElement | MarkerTableItem | null): number | null; + getSelection(): (MarkerElement | MarkerTableItem | null)[]; + getVisibleItemCount(): number; + layout(height: number, width: number): void; + reset(resourceMarkers: ResourceMarkers[]): void; + revealMarkers(activeResource: ResourceMarkers | null, focus: boolean, lastSelectedRelativeTop: number): void; + setAriaLabel(label: string): void; + setMarkerSelection(marker?: Marker): void; + toggleVisibility(hide: boolean): void; + update(resourceMarkers: ResourceMarkers[]): void; + updateMarker(marker: Marker): void; +} + export class MarkersView extends ViewPane implements IMarkersView { private lastSelectedRelativeTop: number = 0; @@ -77,12 +111,18 @@ export class MarkersView extends ViewPane implements IMarkersView { private readonly filter: Filter; private readonly onVisibleDisposables = this._register(new DisposableStore()); - private tree: MarkersTree | undefined; + private widget!: IProblemsWidget; + private widgetDisposables = this._register(new DisposableStore()); + private widgetContainer!: HTMLElement; + private widgetIdentityProvider: IIdentityProvider; + private widgetAccessibilityProvider: MarkersWidgetAccessibilityProvider; private filterActionBar: ActionBar | undefined; private messageBoxContainer: HTMLElement | undefined; private ariaLabelElement: HTMLElement | undefined; readonly filters: MarkersFilters; + private currentHeight = 0; + private currentWidth = 0; private readonly panelState: MementoObject; private _onDidChangeFilterStats = this._register(new Emitter<{ total: number; filtered: number }>()); @@ -126,8 +166,12 @@ export class MarkersView extends ViewPane implements IMarkersView { this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER); this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); - this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); + this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'], this.panelState['viewMode'] ?? this.getDefaultViewMode())); this._register(this.onDidChangeVisibility(visible => this.onDidChangeMarkersViewVisibility(visible))); + this._register(this.markersViewModel.onDidChangeViewMode(_ => this.onDidChangeViewMode())); + + this.widgetAccessibilityProvider = instantiationService.createInstance(MarkersWidgetAccessibilityProvider); + this.widgetIdentityProvider = { getId(element: MarkerElement | MarkerTableItem) { return element.id; } }; this.setCurrentActiveEditor(); @@ -144,23 +188,38 @@ export class MarkersView extends ViewPane implements IMarkersView { activeFile: !!this.panelState['activeFile'], layout: new dom.Dimension(0, 0) })); + + // Update filter, whenever the "files.exclude" setting is changed + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (this.filters.excludedFiles && e.affectsConfiguration('files.exclude')) { + this.updateFilter(); + } + })); } public override renderBody(parent: HTMLElement): void { super.renderBody(parent); parent.classList.add('markers-panel'); + this._register(dom.addDisposableListener(parent, 'keydown', e => { + if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { + this.focusFilter(); + } + })); - const container = dom.append(parent, dom.$('.markers-panel-container')); + const panelContainer = dom.append(parent, dom.$('.markers-panel-container')); - this.createFilterActionBar(container); - this.createArialLabelElement(container); - this.createMessageBox(container); - this.createTree(container); + this.createArialLabelElement(panelContainer); + + this.createFilterActionBar(panelContainer); + this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`)); + + this.createMessageBox(panelContainer); + + this.widgetContainer = dom.append(panelContainer, dom.$('.widget-container')); + this.createWidget(this.widgetContainer); this.updateFilter(); - - this.filterActionBar!.push(new Action(`workbench.actions.treeView.${this.id}.filter`)); this.renderContent(); } @@ -168,7 +227,7 @@ export class MarkersView extends ViewPane implements IMarkersView { return Messages.MARKERS_PANEL_TITLE_PROBLEMS; } - public override layoutBody(height: number, width: number): void { + public override layoutBody(height: number = this.currentHeight, width: number = this.currentWidth): void { super.layoutBody(height, width); const wasSmallLayout = this.smallLayout; this.smallLayout = width < 600 && height > 100; @@ -178,25 +237,26 @@ export class MarkersView extends ViewPane implements IMarkersView { } } const contentHeight = this.smallLayout ? height - 44 : height; - if (this.tree) { - this.tree.layout(contentHeight, width); - } if (this.messageBoxContainer) { this.messageBoxContainer.style.height = `${contentHeight}px`; } + this.widget.layout(contentHeight, width); this.filters.layout = new dom.Dimension(this.smallLayout ? width : width - 200, height); + + this.currentHeight = height; + this.currentWidth = width; } public override focus(): void { - if (this.tree && this.tree.getHTMLElement() === document.activeElement) { + if (this.widget.getHTMLElement() === document.activeElement) { return; } - if (this.hasNoProblems() && this.messageBoxContainer) { - this.messageBoxContainer.focus(); - } else if (this.tree) { - this.tree.domFocus(); - this.setTreeSelection(); + if (this.hasNoProblems()) { + this.messageBoxContainer!.focus(); + } else { + this.widget.domFocus(); + this.widget.setMarkerSelection(); } } @@ -217,7 +277,9 @@ export class MarkersView extends ViewPane implements IMarkersView { public openFileAtElement(element: any, preserveFocus: boolean, sideByside: boolean, pinned: boolean): boolean { const { resource, selection } = element instanceof Marker ? { resource: element.resource, selection: element.range } : - element instanceof RelatedInformation ? { resource: element.raw.resource, selection: element.raw } : { resource: null, selection: null }; + element instanceof RelatedInformation ? { resource: element.raw.resource, selection: element.raw } : + 'marker' in element ? { resource: element.marker.resource, selection: element.marker.range } : + { resource: null, selection: null }; if (resource && selection) { this.editorService.openEditor({ resource, @@ -242,57 +304,36 @@ export class MarkersView extends ViewPane implements IMarkersView { } private refreshPanel(markerOrChange?: Marker | MarkerChangesEvent): void { - if (this.isVisible() && this.tree) { - const hasSelection = this.tree.getSelection().length > 0; - this.cachedFilterStats = undefined; + if (this.isVisible()) { + const hasSelection = this.widget.getSelection().length > 0; if (markerOrChange) { if (markerOrChange instanceof Marker) { - this.tree.rerender(markerOrChange); + this.widget.updateMarker(markerOrChange); } else { if (markerOrChange.added.size || markerOrChange.removed.size) { - // Reset complete tree - this.resetTree(); + // Reset complete widget + this.resetWidget(); } else { // Update resource - for (const updated of markerOrChange.updated) { - this.tree.setChildren(updated, createResourceMarkersIterator(updated), { - /* Pass Identitiy Provider only when updating while the tree is visible */ - diffIdentityProvider: { - getId(element: MarkerElement): string { return element.id; } - } - }); - this.tree.rerender(updated); - } + this.widget.update([...markerOrChange.updated]); } } } else { - // Reset complete tree - this.resetTree(); + // Reset complete widget + this.resetWidget(); } - const { total, filtered } = this.getFilterStats(); - this.tree.toggleVisibility(total === 0 || filtered === 0); - this.renderMessage(); - this._onDidChangeFilterStats.fire(this.getFilterStats()); - if (hasSelection) { - this.setTreeSelection(); + this.widget.setMarkerSelection(); } - } - } - private setTreeSelection(): void { - if (this.tree && this.tree.isVisible() && this.tree.getSelection().length === 0) { - const firstVisibleElement = this.tree.firstVisibleElement; - const marker = firstVisibleElement ? - firstVisibleElement instanceof ResourceMarkers ? firstVisibleElement.markers[0] : - firstVisibleElement instanceof Marker ? firstVisibleElement : undefined - : undefined; - if (marker) { - this.tree.setFocus([marker]); - this.tree.setSelection([marker]); - } + this.cachedFilterStats = undefined; + const { total, filtered } = this.getFilterStats(); + this.toggleVisibility(total === 0 || filtered === 0); + this.renderMessage(); + + this._onDidChangeFilterStats.fire(this.getFilterStats()); } } @@ -300,37 +341,31 @@ export class MarkersView extends ViewPane implements IMarkersView { this.refreshPanel(marker); } - private resetTree(): void { - if (!this.tree) { - return; - } - let resourceMarkers: ResourceMarkers[] = []; - if (this.filters.activeFile) { - if (this.currentActiveResource) { - const activeResourceMarkers = this.markersModel.getResourceMarkers(this.currentActiveResource); - if (activeResourceMarkers) { - resourceMarkers = [activeResourceMarkers]; - } - } - } else { - resourceMarkers = this.markersModel.resourceMarkers; - } - this.tree.setChildren(null, Iterable.map(resourceMarkers, m => ({ element: m, children: createResourceMarkersIterator(m) }))); + private resetWidget(): void { + this.widget.reset(this.getResourceMarkers()); } private updateFilter() { - this.cachedFilterStats = undefined; this.filter.options = new FilterOptions(this.filters.filterText, this.getFilesExcludeExpressions(), this.filters.showWarnings, this.filters.showErrors, this.filters.showInfos, this.uriIdentityService); - if (this.tree) { - this.tree.refilter(); - } - this._onDidChangeFilterStats.fire(this.getFilterStats()); + this.widget.filterMarkers(this.getResourceMarkers(), this.filter.options); + this.cachedFilterStats = undefined; const { total, filtered } = this.getFilterStats(); - if (this.tree) { - this.tree.toggleVisibility(total === 0 || filtered === 0); - } + this.toggleVisibility(total === 0 || filtered === 0); this.renderMessage(); + + this._onDidChangeFilterStats.fire(this.getFilterStats()); + } + + private getDefaultViewMode(): MarkersViewMode { + switch (this.configurationService.getValue('problems.defaultViewMode')) { + case 'table': + return MarkersViewMode.Table; + case 'tree': + return MarkersViewMode.Tree; + default: + return MarkersViewMode.Tree; + } } private getFilesExcludeExpressions(): { root: URI; expression: IExpression }[] | IExpression { @@ -348,6 +383,22 @@ export class MarkersView extends ViewPane implements IMarkersView { return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; } + private getResourceMarkers(): ResourceMarkers[] { + if (!this.filters.activeFile) { + return this.markersModel.resourceMarkers; + } + + let resourceMarkers: ResourceMarkers[] = []; + if (this.currentActiveResource) { + const activeResourceMarkers = this.markersModel.getResourceMarkers(this.currentActiveResource); + if (activeResourceMarkers) { + resourceMarkers = [activeResourceMarkers]; + } + } + + return resourceMarkers; + } + private createFilterActionBar(parent: HTMLElement): void { this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); this.filterActionBar.getContainer().classList.add('markers-panel-filter-container'); @@ -364,10 +415,59 @@ export class MarkersView extends ViewPane implements IMarkersView { this.ariaLabelElement.setAttribute('id', 'markers-panel-arialabel'); } - private createTree(parent: HTMLElement): void { + private createWidget(parent: HTMLElement): void { + this.widget = this.markersViewModel.viewMode === MarkersViewMode.Table ? this.createTable(parent) : this.createTree(parent); + this.widgetDisposables.add(this.widget); + + const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.widget.contextKeyService); + const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.widget.contextKeyService); + this.widgetDisposables.add(this.widget.onDidChangeFocus(focus => { + markerFocusContextKey.set(focus.elements.some(e => e instanceof Marker)); + relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); + })); + + this.widgetDisposables.add(Event.debounce(this.widget.onDidOpen, (last, event) => event, 75, true)(options => { + this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); + })); + + this.widgetDisposables.add(Event.any(this.widget.onDidChangeSelection, this.widget.onDidChangeFocus)(() => { + const elements = [...this.widget.getSelection(), ...this.widget.getFocus()]; + for (const element of elements) { + if (element instanceof Marker) { + const viewModel = this.markersViewModel.getViewModel(element); + if (viewModel) { + viewModel.showLightBulb(); + } + } + } + })); + + this.widgetDisposables.add(this.widget.onContextMenu(this.onContextMenu, this)); + this.widgetDisposables.add(this.widget.onDidChangeSelection(this.onSelected, this)); + } + + private createTable(parent: HTMLElement): IProblemsWidget { + const table = this.instantiationService.createInstance(MarkersTable, + dom.append(parent, dom.$('.markers-table-container')), + this.markersViewModel, + this.getResourceMarkers(), + this.filter.options, + { + accessibilityProvider: this.widgetAccessibilityProvider, + horizontalScrolling: false, + identityProvider: this.widgetIdentityProvider, + multipleSelectionSupport: false, + selectionNavigation: true + }, + ); + + return table; + } + + private createTree(parent: HTMLElement): IProblemsWidget { const onDidChangeRenderNodeCount = new Relay>(); - const treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + const treeLabels = this.instantiationService.createInstance(ResourceLabels, this); const virtualDelegate = new VirtualDelegate(this.markersViewModel); const renderers = [ @@ -375,23 +475,16 @@ export class MarkersView extends ViewPane implements IMarkersView { this.instantiationService.createInstance(MarkerRenderer, this.markersViewModel), this.instantiationService.createInstance(RelatedInformationRenderer) ]; - const accessibilityProvider = this.instantiationService.createInstance(MarkersTreeAccessibilityProvider); - const identityProvider = { - getId(element: MarkerElement) { - return element.id; - } - }; - - this.tree = this._register(this.instantiationService.createInstance(MarkersTree, + const tree = this.instantiationService.createInstance(MarkersTree, 'MarkersView', dom.append(parent, dom.$('.tree-container.show-file-icons')), virtualDelegate, renderers, { filter: this.filter, - accessibilityProvider, - identityProvider, + accessibilityProvider: this.widgetAccessibilityProvider, + identityProvider: this.widgetIdentityProvider, dnd: this.instantiationService.createInstance(ResourceListDnDHandler, (element) => { if (element instanceof ResourceMarkers) { return element.resource; @@ -411,65 +504,25 @@ export class MarkersView extends ViewPane implements IMarkersView { selectionNavigation: true, multipleSelectionSupport: true, }, - )); + ); - onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; + onDidChangeRenderNodeCount.input = tree.onDidChangeRenderNodeCount; - const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); - const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.tree.contextKeyService); - this._register(this.tree.onDidChangeFocus(focus => { - markerFocusContextKey.set(focus.elements.some(e => e instanceof Marker)); - relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); - })); - - this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(options => { - this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); - })); - - this._register(this.tree.onContextMenu(this.onContextMenu, this)); - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.filters.excludedFiles && e.affectsConfiguration('files.exclude')) { - this.updateFilter(); - } - })); - - // move focus to input, whenever a key is pressed in the panel container - this._register(dom.addDisposableListener(parent, 'keydown', e => { - if (this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.focusFilter(); - } - })); - - this._register(Event.any(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { - const elements = [...this.tree!.getSelection(), ...this.tree!.getFocus()]; - for (const element of elements) { - if (element instanceof Marker) { - const viewModel = this.markersViewModel.getViewModel(element); - if (viewModel) { - viewModel.showLightBulb(); - } - } - } - })); - - this._register(this.tree.onDidChangeSelection(() => this.onSelected())); + return tree; } collapseAll(): void { - if (this.tree) { - this.tree.collapseAll(); - this.tree.setSelection([]); - this.tree.setFocus([]); - this.tree.getHTMLElement().focus(); - this.tree.focusFirst(); - } + this.widget.collapseMarkers(); } setMultiline(multiline: boolean): void { this.markersViewModel.multiline = multiline; } + setViewMode(viewMode: MarkersViewMode): void { + this.markersViewModel.viewMode = viewMode; + } + private onDidChangeMarkersViewVisibility(visible: boolean): void { this.onVisibleDisposables.clear(); if (visible) { @@ -477,8 +530,6 @@ export class MarkersView extends ViewPane implements IMarkersView { this.onVisibleDisposables.add(disposable); } this.refreshPanel(); - } else if (this.tree) { - this.tree.toggleVisibility(true); } } @@ -546,6 +597,25 @@ export class MarkersView extends ViewPane implements IMarkersView { } } + private onDidChangeViewMode(): void { + if (this.widgetContainer && this.widget) { + this.widgetContainer.textContent = ''; + this.widgetDisposables.clear(); + } + + // Save selection + const selection = this.widget?.getSelection(); + + // Create new widget + this.createWidget(this.widgetContainer); + this.refreshPanel(); + + // Restore selection + if (selection && selection.length > 0 && (selection[0] instanceof Marker || selection[0] instanceof MarkerTableItem)) { + this.widget.setMarkerSelection(selection[0]); + } + } + private isCurrentResourceGotAddedToMarkersData(changedResources: URI[]) { const currentlyActiveResource = this.currentActiveResource; if (!currentlyActiveResource) { @@ -572,11 +642,9 @@ export class MarkersView extends ViewPane implements IMarkersView { } private onSelected(): void { - if (this.tree) { - let selection = this.tree.getSelection(); - if (selection && selection.length > 0) { - this.lastSelectedRelativeTop = this.tree!.getRelativeTop(selection[0]) || 0; - } + const selection = this.widget.getSelection(); + if (selection && selection.length > 0) { + this.lastSelectedRelativeTop = this.widget.getRelativeTop(selection[0]) || 0; } } @@ -587,10 +655,8 @@ export class MarkersView extends ViewPane implements IMarkersView { private renderContent(): void { this.cachedFilterStats = undefined; - this.resetTree(); - if (this.tree) { - this.tree.toggleVisibility(this.hasNoProblems()); - } + this.resetWidget(); + this.toggleVisibility(this.hasNoProblems()); this.renderMessage(); } @@ -663,9 +729,7 @@ export class MarkersView extends ViewPane implements IMarkersView { } private setAriaLabel(label: string): void { - if (this.tree) { - this.tree.ariaLabel = label; - } + this.widget.setAriaLabel(label); this.ariaLabelElement!.setAttribute('aria-label', label); } @@ -679,33 +743,13 @@ export class MarkersView extends ViewPane implements IMarkersView { private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on - if (this.filters.activeFile || !this.tree) { + if (this.filters.activeFile) { return; } - let autoReveal = this.configurationService.getValue('problems.autoReveal'); + const autoReveal = this.configurationService.getValue('problems.autoReveal'); if (typeof autoReveal === 'boolean' && autoReveal) { - let currentActiveResource = this.getResourceForCurrentActiveResource(); - if (currentActiveResource) { - if (this.tree.hasElement(currentActiveResource)) { - if (!this.tree.isCollapsed(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { - this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); - if (focus) { - this.tree.setFocus(this.tree.getSelection()); - } - } else { - this.tree.expand(currentActiveResource); - this.tree.reveal(currentActiveResource, 0); - - if (focus) { - this.tree.setFocus([currentActiveResource]); - this.tree.setSelection([currentActiveResource]); - } - } - } - } else if (focus) { - this.tree.setSelection([]); - this.tree.focusFirst(); - } + const currentActiveResource = this.getResourceForCurrentActiveResource(); + this.widget.revealMarkers(currentActiveResource, focus, this.lastSelectedRelativeTop); } } @@ -713,29 +757,15 @@ export class MarkersView extends ViewPane implements IMarkersView { return this.currentActiveResource ? this.markersModel.getResourceMarkers(this.currentActiveResource) : null; } - private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { - if (this.tree) { - let selectedElement = this.tree.getSelection(); - if (selectedElement && selectedElement.length > 0) { - if (selectedElement[0] instanceof Marker) { - if (resource.has((selectedElement[0]).marker.resource)) { - return true; - } - } - } - } - return false; - } - private updateRangeHighlights() { this.rangeHighlightDecorations.removeHighlightRange(); - if (this.tree && this.tree.getHTMLElement() === document.activeElement) { + if (this.widget.getHTMLElement() === document.activeElement) { this.highlightCurrentSelectedMarkerRange(); } } private highlightCurrentSelectedMarkerRange() { - const selections = this.tree ? this.tree.getSelection() : []; + const selections = this.widget.getSelection() ?? []; if (selections.length !== 1) { return; @@ -750,13 +780,18 @@ export class MarkersView extends ViewPane implements IMarkersView { this.rangeHighlightDecorations.highlightRange(selection); } - private onContextMenu(e: ITreeContextMenuEvent): void { + private onContextMenu(e: ITreeContextMenuEvent | ITableContextMenuEvent): void { + const element = e.element; + if (!element) { + return; + } + e.browserEvent.preventDefault(); e.browserEvent.stopPropagation(); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor!, - getActions: () => this.getMenuActions(e.element), + getActions: () => this.getMenuActions(element), getActionViewItem: (action) => { const keybinding = this.keybindingService.lookupKeybinding(action.id); if (keybinding) { @@ -766,7 +801,7 @@ export class MarkersView extends ViewPane implements IMarkersView { }, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree!.domFocus(); + this.widget.domFocus(); } } }); @@ -786,14 +821,14 @@ export class MarkersView extends ViewPane implements IMarkersView { } } - const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree!.contextKeyService); + const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.widget.contextKeyService); createAndFillInContextMenuActions(menu, undefined, result); menu.dispose(); return result; } public getFocusElement(): MarkerElement | undefined { - return this.tree?.getFocus()[0] || undefined; + return this.widget.getFocus()[0] ?? undefined; } public getFocusedSelectedElements(): MarkerElement[] | null { @@ -801,7 +836,7 @@ export class MarkersView extends ViewPane implements IMarkersView { if (!focus) { return null; } - const selection = this.tree!.getSelection(); + const selection = this.widget.getSelection(); if (selection.includes(focus)) { const result: MarkerElement[] = []; for (const selected of selection) { @@ -828,27 +863,18 @@ export class MarkersView extends ViewPane implements IMarkersView { getFilterStats(): { total: number; filtered: number } { if (!this.cachedFilterStats) { - this.cachedFilterStats = this.computeFilterStats(); + this.cachedFilterStats = { + total: this.markersModel.total, + filtered: this.widget?.getVisibleItemCount() ?? 0 + }; } return this.cachedFilterStats; } - private computeFilterStats(): { total: number; filtered: number } { - let filtered = 0; - if (this.tree) { - const root = this.tree.getNode(); - - for (const resourceMarkerNode of root.children) { - for (const markerNode of resourceMarkerNode.children) { - if (resourceMarkerNode.visible && markerNode.visible) { - filtered++; - } - } - } - } - - return { total: this.markersModel.total, filtered }; + private toggleVisibility(hide: boolean): void { + this.widget.toggleVisibility(hide); + this.layoutBody(); } override saveState(): void { @@ -860,6 +886,7 @@ export class MarkersView extends ViewPane implements IMarkersView { this.panelState['useFilesExclude'] = this.filters.excludedFiles; this.panelState['activeFile'] = this.filters.activeFile; this.panelState['multiline'] = this.markersViewModel.multiline; + this.panelState['viewMode'] = this.markersViewModel.viewMode; super.saveState(); } @@ -870,13 +897,13 @@ export class MarkersView extends ViewPane implements IMarkersView { } -class MarkersTree extends WorkbenchObjectTree { +class MarkersTree extends WorkbenchObjectTree implements IProblemsWidget { private readonly visibilityContextKey: IContextKey; constructor( user: string, - readonly container: HTMLElement, + private readonly container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], options: IWorkbenchObjectTreeOptions, @@ -891,9 +918,35 @@ class MarkersTree extends WorkbenchObjectTree { this.visibilityContextKey = Constants.MarkersTreeVisibilityContextKey.bindTo(contextKeyService); } - override layout(height: number, width: number): void { - this.container.style.height = `${height}px`; - super.layout(height, width); + collapseMarkers(): void { + this.collapseAll(); + this.setSelection([]); + this.setFocus([]); + this.getHTMLElement().focus(); + this.focusFirst(); + } + + filterMarkers(): void { + this.refilter(); + } + + getVisibleItemCount(): number { + let filtered = 0; + const root = this.getNode(); + + for (const resourceMarkerNode of root.children) { + for (const markerNode of resourceMarkerNode.children) { + if (resourceMarkerNode.visible && markerNode.visible) { + filtered++; + } + } + } + + return filtered; + } + + isVisible(): boolean { + return !this.container.classList.contains('hidden'); } toggleVisibility(hide: boolean): void { @@ -901,10 +954,106 @@ class MarkersTree extends WorkbenchObjectTree { this.container.classList.toggle('hidden', hide); } - isVisible(): boolean { - return !this.container.classList.contains('hidden'); + reset(resourceMarkers: ResourceMarkers[]): void { + this.setChildren(null, Iterable.map(resourceMarkers, m => ({ element: m, children: createResourceMarkersIterator(m) }))); } + revealMarkers(activeResource: ResourceMarkers | null, focus: boolean, lastSelectedRelativeTop: number): void { + if (activeResource) { + if (this.hasElement(activeResource)) { + if (!this.isCollapsed(activeResource) && this.hasSelectedMarkerFor(activeResource)) { + this.reveal(this.getSelection()[0], lastSelectedRelativeTop); + if (focus) { + this.setFocus(this.getSelection()); + } + } else { + this.expand(activeResource); + this.reveal(activeResource, 0); + + if (focus) { + this.setFocus([activeResource]); + this.setSelection([activeResource]); + } + } + } + } else if (focus) { + this.setSelection([]); + this.focusFirst(); + } + } + + setAriaLabel(label: string): void { + this.ariaLabel = label; + } + + setMarkerSelection(marker: Marker): void { + if (this.isVisible()) { + if (marker) { + const markerNode = this.findMarkerNode(marker); + + if (markerNode) { + this.setFocus([markerNode]); + this.setSelection([markerNode]); + } + } else if (this.getSelection().length === 0) { + const firstVisibleElement = this.firstVisibleElement; + const marker = firstVisibleElement ? + firstVisibleElement instanceof ResourceMarkers ? firstVisibleElement.markers[0] : + firstVisibleElement instanceof Marker ? firstVisibleElement : undefined + : undefined; + + if (marker) { + this.setFocus([marker]); + this.setSelection([marker]); + } + } + } + } + + update(resourceMarkers: ResourceMarkers[]): void { + for (const resourceMarker of resourceMarkers) { + this.setChildren(resourceMarker, createResourceMarkersIterator(resourceMarker)); + this.rerender(resourceMarker); + } + } + + updateMarker(marker: Marker): void { + this.rerender(marker); + } + + private findMarkerNode(marker: Marker) { + for (const resourceNode of this.getNode().children) { + for (const markerNode of resourceNode.children) { + if (markerNode.element instanceof Marker && markerNode.element.marker === marker.marker) { + return markerNode.element; + } + } + } + + return undefined; + } + + private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { + let selectedElement = this.getSelection(); + if (selectedElement && selectedElement.length > 0) { + if (selectedElement[0] instanceof Marker) { + if (resource.has((selectedElement[0]).marker.resource)) { + return true; + } + } + } + + return false; + } + + override dispose(): void { + super.dispose(); + } + + override layout(height: number, width: number): void { + this.container.style.height = `${height}px`; + super.layout(height, width); + } } registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 07bf1366e81..356ec95eab4 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -217,3 +217,87 @@ .markers-panel .monaco-tl-contents .actions .action-item.disabled { display: none; } + +/* Table */ + +.markers-panel .markers-table-container { + margin-top: 8px; + padding-left: 20px; +} + +.markers-panel .markers-table-container .monaco-table .monaco-table-th { + display: flex; + font-weight: 600; + align-items: center; + padding-left: 10px; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td { + display: flex; + align-items: center; + padding-left: 10px; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td .highlight { + font-weight: bold; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .message, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .owner { + overflow: hidden; + text-overflow: ellipsis; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .severity { + display: flex; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row.selected .monaco-table-tr > .monaco-table-td.quickFix > .severity, +.markers-panel .markers-table-container .monaco-table .monaco-list-row.focused .monaco-table-tr > .monaco-table-td.quickFix > .severity, +.markers-panel .markers-table-container .monaco-table .monaco-list-row:hover .monaco-table-tr > .monaco-table-td.quickFix > .severity { + display: none; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .actions { + margin-left: -3px; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .actions > .monaco-action-bar .action-item { + display: none; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row.selected .monaco-table-tr > .monaco-table-td.quickFix > .actions > .monaco-action-bar .action-item, +.markers-panel .markers-table-container .monaco-table .monaco-list-row.focused .monaco-table-tr > .monaco-table-td.quickFix > .actions > .monaco-action-bar .action-item, +.markers-panel .markers-table-container .monaco-table .monaco-list-row:hover .monaco-table-tr > .monaco-table-td.quickFix > .actions > .monaco-action-bar .action-item { + display: flex; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link::before, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label::before { + content: '('; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link::after, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label::after { + content: ')'; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .code-label { + display: none; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link { + display: inline; + text-decoration: underline; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link { + display: none; +} + +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file > .file-position { + margin-left: 6px; + opacity: 0.7; +} diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index e5d0ab10ba4..ed66fa20c78 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -15,6 +15,7 @@ export default class Messages { public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); + public static PROBLEMS_PANEL_CONFIGURATION_VIEW_MODE: string = nls.localize('problems.panel.configuration.viewMode', "Controls the default view mode of the Problems view."); public static PROBLEMS_PANEL_CONFIGURATION_SHOW_CURRENT_STATUS: string = nls.localize('problems.panel.configuration.showCurrentInStatus', "When enabled shows the current problem in the status bar."); public static PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER: string = nls.localize('problems.panel.configuration.compareOrder', "Controls the order in which problems are navigated."); public static PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER_SEVERITY: string = nls.localize('problems.panel.configuration.compareOrder.severity', "Navigate problems ordered by severity"); From 96479074a4c233d229567c8494bb78d32a9c80d0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 23 May 2022 07:22:15 -0700 Subject: [PATCH 212/350] Create bash explicitly on linux/mac in shell integration smoke tests Part of #149324 --- .../terminal/terminal-shellIntegration.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index b94b40045f7..fb3c40d1547 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, Terminal, SettingsEditor } from '../../../../automation'; +import { Application, Terminal, SettingsEditor, TerminalCommandIdWithValue } from '../../../../automation'; import { setTerminalTestSettings } from './terminal-helpers'; export function setup() { @@ -24,28 +24,31 @@ export function setup() { await settingsEditor.clearUserSettings(); }); + async function createShellIntegrationProfile() { + await terminal.runCommandWithValue(TerminalCommandIdWithValue.NewWithProfile, process.platform === 'win32' ? 'PowerShell' : 'bash'); + } + describe('Shell integration', function () { - // TODO: Fix on Linux, some distros use sh as the default shell in which case shell integration will fail - (process.platform === 'win32' || process.platform === 'linux' ? describe.skip : describe)('Decorations', function () { + (process.platform === 'win32' ? describe.skip : describe)('Decorations', function () { describe('Should show default icons', function () { it('Placeholder', async () => { - await terminal.createTerminal(); + await createShellIntegrationProfile(); await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 }); }); it('Success', async () => { - await terminal.createTerminal(); + await createShellIntegrationProfile(); await terminal.runCommandInTerminal(`ls`); await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 0 }); }); it('Error', async () => { - await terminal.createTerminal(); + await createShellIntegrationProfile(); await terminal.runCommandInTerminal(`fsdkfsjdlfksjdkf`); await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 1 }); }); }); describe('Custom configuration', function () { it('Should update and show custom icons', async () => { - await terminal.createTerminal(); + await createShellIntegrationProfile(); await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 }); await terminal.runCommandInTerminal(`ls`); await terminal.runCommandInTerminal(`fsdkfsjdlfksjdkf`); From 15e4ae374b642d60604b9ece83f5a077cad86278 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 23 May 2022 10:42:01 -0400 Subject: [PATCH 213/350] Bump distro (#150191) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad35220a1e4..3768321d2bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.68.0", - "distro": "19bbe01e3aa85625f35f1fb2df6818a8e491670f", + "distro": "36f3417e7298756bb2943725f4bce75f6096f246", "author": { "name": "Microsoft Corporation" }, From 89bff206a4325573abd69ba807debd645fb00e10 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Sun, 22 May 2022 14:48:10 +0100 Subject: [PATCH 214/350] feat: inlay hints padding option --- src/vs/editor/common/config/editorOptions.ts | 26 +++++++------------ .../browser/inlayHintsController.ts | 9 +++---- src/vs/monaco.d.ts | 7 +++-- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 022895e3e55..9df6bf50542 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2523,11 +2523,10 @@ export interface IEditorInlayHintsOptions { fontFamily?: string; /** - * The display style to render inlay hints with. - * Compact mode disables the borders and padding around the inlay hint. - * Defaults to 'standard'. + * Enables the padding around the inlay hint. + * Defaults to false. */ - displayStyle: 'standard' | 'compact'; + padding?: boolean; } /** @@ -2538,7 +2537,7 @@ export type EditorInlayHintsOptions = Readonly { constructor() { - const defaults: EditorInlayHintsOptions = { enabled: 'on', fontSize: 0, fontFamily: '', displayStyle: 'compact' }; + const defaults: EditorInlayHintsOptions = { enabled: 'on', fontSize: 0, fontFamily: '', padding: false }; super( EditorOption.inlayHints, 'inlayHints', defaults, { @@ -2564,16 +2563,11 @@ class EditorInlayHints extends BaseEditorOption(input.enabled, this.defaultValue.enabled, ['on', 'off', 'offUnlessPressed', 'onUnlessPressed']), fontSize: EditorIntOption.clampedInt(input.fontSize, this.defaultValue.fontSize, 0, 100), fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily), - displayStyle: stringSet<'standard' | 'compact'>(input.displayStyle, this.defaultValue.displayStyle, ['standard', 'compact']) + padding: boolean(input.padding, this.defaultValue.padding) }; } } diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 9044a21c70d..5a7289d812e 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -468,7 +468,7 @@ export class InlayHintsController implements IEditorContribution { // - const { fontSize, fontFamily, displayStyle } = this._getLayoutInfo(); + const { fontSize, fontFamily, padding } = this._getLayoutInfo(); const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); @@ -493,6 +493,7 @@ export class InlayHintsController implements IEditorContribution { const cssProperties: CssProperties = { fontSize: `${fontSize}px`, fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, + verticalAlign: 'middle', }; if (isNonEmptyArray(item.hint.textEdits)) { @@ -510,9 +511,7 @@ export class InlayHintsController implements IEditorContribution { } } - if (displayStyle === 'standard') { - cssProperties.verticalAlign = 'middle'; - + if (padding) { if (isFirst && isLast) { // only element cssProperties.padding = `1px ${Math.max(1, fontSize / 4) | 0}px`; @@ -594,7 +593,7 @@ export class InlayHintsController implements IEditorContribution { fontSize = editorFontSize; } const fontFamily = options.fontFamily || this._editor.getOption(EditorOption.fontFamily); - return { fontSize, fontFamily, displayStyle: options.displayStyle }; + return { fontSize, fontFamily, padding: options.padding }; } private _removeAllDecorations(): void { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ee1b68b4155..37a8579dcc5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3798,11 +3798,10 @@ declare namespace monaco.editor { */ fontFamily?: string; /** - * The display style to render inlay hints with. - * Compact mode disables the borders and padding around the inlay hint. - * Defaults to 'standard'. + * Enables the padding around the inlay hint. + * Defaults to false. */ - displayStyle: 'standard' | 'compact'; + padding?: boolean; } /** From 138f4e9abd2316a001687130068c519fbcf2621c Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 23 May 2022 11:59:00 -0700 Subject: [PATCH 215/350] Fix ignorefile parsing on windows (#150213) --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 2 +- src/vs/workbench/services/search/common/ignoreFile.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 73c8913b840..241b06a7fae 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -739,7 +739,7 @@ export class FilesFilter implements ITreeFilter { // Maybe we need a cancellation token here in case it's super long? const content = await this.fileService.readFile(ignoreFileResource); const ignoreParent = ignoreTree.findSubstr(dirUri); - const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path + path.sep, ignoreParent); + const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path, ignoreParent); ignoreTree.set(dirUri, ignoreFile); // If we haven't seen this resource before then we need to add it to the list of resources we're tracking if (!this.ignoreFileResourcesPerRoot.get(root)?.has(ignoreFileResource)) { diff --git a/src/vs/workbench/services/search/common/ignoreFile.ts b/src/vs/workbench/services/search/common/ignoreFile.ts index a1a0b181a01..35c4dea27ff 100644 --- a/src/vs/workbench/services/search/common/ignoreFile.ts +++ b/src/vs/workbench/services/search/common/ignoreFile.ts @@ -11,6 +11,12 @@ export class IgnoreFile { private isPathIgnored: (path: string, isDir: boolean, parent?: IgnoreFile) => boolean; constructor(contents: string, location: string, parent?: IgnoreFile) { + if (location[location.length - 1] === '\\') { + throw Error('Unexpected path format, do not use trailing backslashes'); + } + if (location[location.length - 1] !== '/') { + location += '/'; + } this.isPathIgnored = this.parseIgnoreFile(contents, location, parent); } From 99065b484f7fadddec2ad7c89028d92787e10224 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 23 May 2022 12:05:21 -0700 Subject: [PATCH 216/350] add tests --- .../contrib/tasks/common/taskConfiguration.ts | 26 ++-- .../tasks/test/common/configuration.test.ts | 126 +++++++++++++++++- 2 files changed, 137 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 9c6f0ba9e34..1fd6ca72d5b 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -14,8 +14,8 @@ import * as UUID from 'vs/base/common/uuid'; import { ValidationStatus, IProblemReporter as IProblemReporterBase } from 'vs/base/common/parsers'; import { - NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, - isNamedProblemMatcher, ProblemMatcherRegistry + NamedProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, + isNamedProblemMatcher, ProblemMatcherRegistry, ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; @@ -710,7 +710,7 @@ export namespace RunOptions { } } -interface ParseContext { +export interface ParseContext { workspaceFolder: IWorkspaceFolder; workspace: IWorkspace | undefined; problemReporter: IProblemReporter; @@ -1130,7 +1130,7 @@ namespace CommandConfiguration { } } -namespace ProblemMatcherConverter { +export namespace ProblemMatcherConverter { export function namedFrom(this: void, declares: ProblemMatcherConfig.NamedProblemMatcher[] | undefined, context: ParseContext): IStringDictionary { let result: IStringDictionary = Object.create(null); @@ -1389,7 +1389,7 @@ namespace ConfiguringTask { customize: string; } - export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource): Tasks.ConfiguringTask | undefined { + export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource, testTaskDefinition?: Tasks.TaskDefinition): Tasks.ConfiguringTask | undefined { if (!external) { return undefined; } @@ -1399,7 +1399,7 @@ namespace ConfiguringTask { context.problemReporter.error(nls.localize('ConfigurationParser.noTaskType', 'Error: tasks configuration must have a type property. The configuration will be ignored.\n{0}\n', JSON.stringify(external, null, 4))); return undefined; } - let typeDeclaration = type ? TaskDefinitionRegistry.get(type) : undefined; + let typeDeclaration = type ? testTaskDefinition || TaskDefinitionRegistry.get(type) : undefined; if (!typeDeclaration) { let message = nls.localize('ConfigurationParser.noTypeDefinition', 'Error: there is no registered task type \'{0}\'. Did you miss installing an extension that provides a corresponding task provider?', type); context.problemReporter.error(message); @@ -1654,12 +1654,12 @@ namespace CustomTask { } } -interface TaskParseResult { +export interface TaskParseResult { custom: Tasks.CustomTask[]; configured: Tasks.ConfiguringTask[]; } -namespace TaskParser { +export namespace TaskParser { function isCustomTask(value: CustomTask | ConfiguringTask): value is CustomTask { let type = value.type; @@ -1672,7 +1672,7 @@ namespace TaskParser { process: ProcessExecutionSupportedContext }; - export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource): TaskParseResult { + export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource, testTaskDefinition?: Tasks.TaskDefinition): TaskParseResult { let result: TaskParseResult = { custom: [], configured: [] }; if (!externals) { return result; @@ -1683,7 +1683,7 @@ namespace TaskParser { const baseLoadIssues = Objects.deepClone(context.taskLoadIssues); for (let index = 0; index < externals.length; index++) { let external = externals[index]; - const definition = external.type ? TaskDefinitionRegistry.get(external.type) : undefined; + const definition = external.type ? testTaskDefinition || TaskDefinitionRegistry.get(external.type) : undefined; let typeNotSupported: boolean = false; if (definition && definition.when && !context.contextKeyService.contextMatchesRules(definition.when)) { typeNotSupported = true; @@ -1743,7 +1743,7 @@ namespace TaskParser { result.custom.push(customTask); } } else { - let configuredTask = ConfiguringTask.from(external, context, index, source); + let configuredTask = ConfiguringTask.from(external, context, index, source, testTaskDefinition); if (configuredTask) { configuredTask.addTaskLoadMessages(context.taskLoadIssues); result.configured.push(configuredTask); @@ -1795,7 +1795,7 @@ namespace TaskParser { } } -interface Globals { +export interface Globals { command?: Tasks.CommandConfiguration; problemMatcher?: ProblemMatcher[]; promptOnClose?: boolean; @@ -1933,7 +1933,7 @@ export interface ParseResult { export interface IProblemReporter extends IProblemReporterBase { } -class UUIDMap { +export class UUIDMap { private last: IStringDictionary | undefined; private current: IStringDictionary; diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index c1ef36f8b17..577893066c8 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -10,14 +10,15 @@ import * as UUID from 'vs/base/common/uuid'; import * as Types from 'vs/base/common/types'; import * as Platform from 'vs/base/common/platform'; import { ValidationStatus } from 'vs/base/common/parsers'; -import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/contrib/tasks/common/problemMatcher'; +import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { WorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; -import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; +import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource, ParseContext, ProblemMatcherConverter, Globals, TaskParseResult, UUIDMap, TaskParser } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IContext } from 'vs/platform/contextkey/common/contextkey'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), @@ -1760,3 +1761,124 @@ suite('Bugs / regression tests', () => { testConfiguration(external, builder); }); }); + +class TestNamedProblemMatcher implements Partial { + +} + +class TestParseContext implements Partial { + +} + +suite('Task Configuration Test', () => { + let instantiationService: TestInstantiationService; + let parseContext: ParseContext; + let namedProblemMatcher: NamedProblemMatcher; + let problemReporter: ProblemReporter; + setup(() => { + instantiationService = new TestInstantiationService(); + namedProblemMatcher = instantiationService.createInstance(TestNamedProblemMatcher); + namedProblemMatcher.name = 'real'; + namedProblemMatcher.label = 'real label'; + problemReporter = new ProblemReporter(); + parseContext = instantiationService.createInstance(TestParseContext); + parseContext.problemReporter = problemReporter; + parseContext.namedProblemMatchers = { + 'real': namedProblemMatcher + }; + parseContext.uuidMap = new UUIDMap(); + }); + suite('ProblemMatcherConverter', () => { + test('returns [] and an error for an unknown problem matcher', () => { + const result = (ProblemMatcherConverter.from('$fake', parseContext)); + assert.deepEqual(result.value, []); + assert.strictEqual(result.errors?.length, 1); + }); + test('returns config for a known problem matcher', () => { + const result = (ProblemMatcherConverter.from('$real', parseContext)); + assert.strictEqual(result.errors?.length, 0); + assert.deepEqual(result.value, [{ "label": "real label" }]); + }); + test('returns config for a known problem matcher including applyTo', () => { + namedProblemMatcher.applyTo = ApplyToKind.closedDocuments; + const result = (ProblemMatcherConverter.from('$real', parseContext)); + assert.strictEqual(result.errors?.length, 0); + assert.deepEqual(result.value, [{ "label": "real label", "applyTo": ApplyToKind.closedDocuments }]); + }); + }); + suite('TaskParser from', () => { + suite('CustomTask', () => { + suite('incomplete config reports an appropriate error for missing', () => { + test('name', () => { + const result = TaskParser.from([{} as CustomTask], {} as Globals, parseContext, {} as TaskConfigSource); + assertTaskParseResult(result, undefined, problemReporter, 'Error: a task must provide a label property'); + }); + test('command', () => { + const result = TaskParser.from([{ taskName: 'task' } as CustomTask], {} as Globals, parseContext, {} as TaskConfigSource); + assertTaskParseResult(result, undefined, problemReporter, "Error: the task 'task' doesn't define a command"); + }); + }); + suite('returns expected result', () => { + test('single', () => { + const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); + assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); + }); + test('multiple', () => { + const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask, { taskName: 'task 2', command: 'echo test' } as CustomTask]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); + assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); + }); + }); + }); + suite('ConfiguredTask', () => { + suite('returns expected result', () => { + test('single', () => { + const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); + assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); + }); + test('multiple', () => { + const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }, { taskName: 'task 2', command: 'echo test', type: 'any', label: 'task 2' }]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); + assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); + }); + }); + }); + }); +}); + +function assertTaskParseResult(actual: TaskParseResult, expected: ITestTaskParseResult | undefined, problemReporter: ProblemReporter, expectedMessage?: string): void { + if (expectedMessage === undefined) { + assert.strictEqual(problemReporter.lastMessage, undefined); + } else { + assert.ok(problemReporter.lastMessage?.includes(expectedMessage)); + } + + assert.deepEqual(actual.custom.length, expected?.custom?.length || 0); + assert.deepEqual(actual.configured.length, expected?.configured?.length || 0); + + let index = 0; + if (expected?.configured) { + for (const taskParseResult of expected?.configured) { + assert.strictEqual(actual.configured[index]._label, taskParseResult.label); + index++; + } + } + index = 0; + if (expected?.custom) { + for (const taskParseResult of expected?.custom) { + assert.strictEqual(actual.custom[index]._label, taskParseResult.taskName); + index++; + } + } +} + +interface ITestTaskParseResult { + custom?: Partial[]; + configured?: Partial[]; +} + +interface TestConfiguringTask extends Partial { + label: string; +} From b27cfaccfd19f7bf0a27488c3af8784678c2c67d Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 23 May 2022 15:21:37 -0400 Subject: [PATCH 217/350] Close brace on redacted property (#150207) --- src/vs/platform/telemetry/common/telemetryService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index adcec1d16f6..f3dab6ded3f 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -209,7 +209,7 @@ export class TelemetryService implements ITelemetryService { // Check for common user data in the telemetry events for (const secretRegex of userDataRegexes) { if (secretRegex.regex.test(value)) { - return ``; } } From 977123e6d840c9a5df5611b4504bf46cf3606068 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 23 May 2022 12:38:51 -0700 Subject: [PATCH 218/350] Update src/vs/workbench/contrib/tasks/test/common/configuration.test.ts --- src/vs/workbench/contrib/tasks/test/common/configuration.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 577893066c8..56ffdbced3b 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -1763,7 +1763,6 @@ suite('Bugs / regression tests', () => { }); class TestNamedProblemMatcher implements Partial { - } class TestParseContext implements Partial { From 2f0de4444998f992763eb58b5ced1a6f5186594a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 23 May 2022 12:39:16 -0700 Subject: [PATCH 219/350] Update src/vs/workbench/contrib/tasks/test/common/configuration.test.ts --- src/vs/workbench/contrib/tasks/test/common/configuration.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 56ffdbced3b..083d47dcf28 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -1766,7 +1766,6 @@ class TestNamedProblemMatcher implements Partial { } class TestParseContext implements Partial { - } suite('Task Configuration Test', () => { From a48a9f8fc94c94a1c95104d2200852758db480cd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 23 May 2022 12:39:44 -0700 Subject: [PATCH 220/350] rename --- .../common/{configuration.test.ts => taskConfiguration.test.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/vs/workbench/contrib/tasks/test/common/{configuration.test.ts => taskConfiguration.test.ts} (99%) diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts similarity index 99% rename from src/vs/workbench/contrib/tasks/test/common/configuration.test.ts rename to src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 083d47dcf28..1353ab13768 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -1768,7 +1768,7 @@ class TestNamedProblemMatcher implements Partial { class TestParseContext implements Partial { } -suite('Task Configuration Test', () => { +suite('Problem matcher and task parser', () => { let instantiationService: TestInstantiationService; let parseContext: ParseContext; let namedProblemMatcher: NamedProblemMatcher; From 45aefc8f376223ce0eabd0d5641eca855973b226 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 23 May 2022 12:52:56 -0700 Subject: [PATCH 221/350] cleanup --- .../test/common/taskConfiguration.test.ts | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 1353ab13768..105d5d3dce6 100644 --- a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -59,6 +59,10 @@ class ProblemReporter implements IProblemReporter { this.receivedMessage = true; this.lastMessage = message; } + + public clearMessage(): void { + this.lastMessage = undefined; + } } class ConfiguationBuilder { @@ -1768,7 +1772,7 @@ class TestNamedProblemMatcher implements Partial { class TestParseContext implements Partial { } -suite('Problem matcher and task parser', () => { +suite.only('To task configuration from', () => { let instantiationService: TestInstantiationService; let parseContext: ParseContext; let namedProblemMatcher: NamedProblemMatcher; @@ -1781,12 +1785,10 @@ suite('Problem matcher and task parser', () => { problemReporter = new ProblemReporter(); parseContext = instantiationService.createInstance(TestParseContext); parseContext.problemReporter = problemReporter; - parseContext.namedProblemMatchers = { - 'real': namedProblemMatcher - }; + parseContext.namedProblemMatchers = { 'real': namedProblemMatcher }; parseContext.uuidMap = new UUIDMap(); }); - suite('ProblemMatcherConverter', () => { + suite('ProblemMatcher config', () => { test('returns [] and an error for an unknown problem matcher', () => { const result = (ProblemMatcherConverter.from('$fake', parseContext)); assert.deepEqual(result.value, []); @@ -1804,7 +1806,7 @@ suite('Problem matcher and task parser', () => { assert.deepEqual(result.value, [{ "label": "real label", "applyTo": ApplyToKind.closedDocuments }]); }); }); - suite('TaskParser from', () => { + suite('TaskParser external config', () => { suite('CustomTask', () => { suite('incomplete config reports an appropriate error for missing', () => { test('name', () => { @@ -1816,31 +1818,17 @@ suite('Problem matcher and task parser', () => { assertTaskParseResult(result, undefined, problemReporter, "Error: the task 'task' doesn't define a command"); }); }); - suite('returns expected result', () => { - test('single', () => { - const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); - assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); - }); - test('multiple', () => { - const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask, { taskName: 'task 2', command: 'echo test' } as CustomTask]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); - assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); - }); + test('returns expected result', () => { + const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask, { taskName: 'task 2', command: 'echo test' } as CustomTask]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); + assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); }); }); suite('ConfiguredTask', () => { - suite('returns expected result', () => { - test('single', () => { - const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); - assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); - }); - test('multiple', () => { - const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }, { taskName: 'task 2', command: 'echo test', type: 'any', label: 'task 2' }]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); - assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); - }); + test('returns expected result', () => { + const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }, { taskName: 'task 2', command: 'echo test', type: 'any', label: 'task 2' }]; + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); + assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); }); }); }); @@ -1870,6 +1858,7 @@ function assertTaskParseResult(actual: TaskParseResult, expected: ITestTaskParse index++; } } + problemReporter.clearMessage(); } interface ITestTaskParseResult { From 276e982310b5944b5bb4ba9bcc1340abf20695ed Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 23 May 2022 12:53:43 -0700 Subject: [PATCH 222/350] Update src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts --- .../contrib/tasks/test/common/taskConfiguration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 105d5d3dce6..95fb3eb7b55 100644 --- a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -1772,7 +1772,7 @@ class TestNamedProblemMatcher implements Partial { class TestParseContext implements Partial { } -suite.only('To task configuration from', () => { +suite('To task configuration from', () => { let instantiationService: TestInstantiationService; let parseContext: ParseContext; let namedProblemMatcher: NamedProblemMatcher; From f5f375d295f4ef7be4d8fdb398d22fccc08142ea Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 23 May 2022 14:39:33 -0700 Subject: [PATCH 223/350] Scrolling working --- .../browser/interactive.contribution.ts | 16 ++++ .../interactive/browser/interactiveEditor.ts | 8 +- .../browser/diff/notebookTextDiffEditor.ts | 3 + .../notebook/browser/notebookEditorWidget.ts | 12 ++- .../view/renderers/backLayerWebView.ts | 23 +++-- .../browser/view/renderers/webviewMessages.ts | 19 ++-- .../browser/view/renderers/webviewPreloads.ts | 87 +++++++++++-------- 7 files changed, 117 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 5559096223e..ddd9a857eaa 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -23,6 +23,7 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { EditorActivation } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -58,6 +59,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + Registry.as(EditorExtensions.EditorPane).registerEditorPane( EditorPaneDescriptor.create( InteractiveEditor, @@ -711,3 +713,17 @@ registerThemingParticipant((theme) => { // hc: Color.black // }, localize('interactive.inactiveCodeBackground', 'The backgorund color for the current interactive code cell when the editor does not have focus.')); }); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'interactive', + order: 100, + type: 'object', + 'properties': { + 'interactive.alwaysScrollOnNewCell': { + type: 'boolean', + default: true, + markdownDescription: localize('interactive.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") + }, + } +}); + diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 52fe78d35bb..9265c2fca79 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -125,7 +125,7 @@ export class InteractiveEditor extends EditorPane { @INotebookKernelService notebookKernelService: INotebookKernelService, @ILanguageService languageService: ILanguageService, @IKeybindingService keybindingService: IKeybindingService, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private configurationService: IConfigurationService, @IMenuService menuService: IMenuService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @@ -402,6 +402,12 @@ export class InteractiveEditor extends EditorPane { this.#notebookWidget.value!.setOptions({ isReadOnly: true }); + this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidResizeOutput((cvm) => { + // If we're already at the bottom or auto scroll is enabled, scroll to the bottom + if (this.configurationService.getValue('interactive.alwaysScrollOnNewCell') || this.#state === ScrollingState.StickyToBottom) { + this.#notebookWidget.value!.scrollToBottom(); + } + })); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); this.#widgetDisposableStore.add(model.notebook.onDidChangeContent(() => { (model as ComplexNotebookEditorModel).setDirty(false); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index a791887d639..61c5daf9949 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -202,6 +202,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD didDropMarkupCell(cellId: string) { // throw new Error('Method not implemented.'); } + didResizeOutput(cellId: string): void { + // throw new Error('Method not implemented.'); + } protected createEditor(parent: HTMLElement): void { this._rootElement = DOM.append(parent, DOM.$('.notebook-text-diff-editor')); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 7197952d03c..4bd1f431a7f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -239,6 +239,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidReceiveMessage: Event = this._onDidReceiveMessage.event; private readonly _onDidRenderOutput = this._register(new Emitter()); private readonly onDidRenderOutput = this._onDidRenderOutput.event; + private readonly _onDidResizeOutputEmitter = this._register(new Emitter()); + readonly onDidResizeOutput = this._onDidResizeOutputEmitter.event; //#endregion @@ -1396,7 +1398,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD didStartDragMarkupCell: that._didStartDragMarkupCell.bind(that), didDragMarkupCell: that._didDragMarkupCell.bind(that), didDropMarkupCell: that._didDropMarkupCell.bind(that), - didEndDragMarkupCell: that._didEndDragMarkupCell.bind(that) + didEndDragMarkupCell: that._didEndDragMarkupCell.bind(that), + didResizeOutput: that._didResizeOutput.bind(that) }, id, resource, { ...this._notebookOptions.computeWebviewOptions(), fontFamily: this._generateFontFamily() @@ -2980,6 +2983,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } + private _didResizeOutput(cellId: string): void { + const cell = this._getCellById(cellId); + if (cell) { + this._onDidResizeOutputEmitter.fire(cell); + } + } + //#endregion //#region Editor Contributions diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 59046e6159e..513f8b4856e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -36,7 +36,7 @@ import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; -import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, INotebookRendererInfo, NotebookCellExecutionState, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; @@ -77,6 +77,7 @@ export interface INotebookDelegateForWebview { didDragMarkupCell(cellId: string, event: { dragOffsetY: number }): void; didDropMarkupCell(cellId: string, event: { dragOffsetY: number; ctrlKey: boolean; altKey: boolean }): void; didEndDragMarkupCell(cellId: string): void; + didResizeOutput(cellId: string): void; setScrollTop(scrollTop: number): void; triggerScroll(event: IMouseWheelEvent): void; } @@ -178,12 +179,21 @@ export class BackLayerWebView extends Disposable { })); this._register(notebookExecutionStateService.onDidChangeCellExecution((e) => { - // If e.changed is undefined, it means notebook execution just finished. - if (e.changed === undefined && e.affectsNotebook(this.documentUri)) { + // If e.changed state is pending, it means notebook execution just started + if (e.changed && e.changed.state === NotebookCellExecutionState.Pending && e.affectsNotebook(this.documentUri)) { const cell = this.notebookEditor.getCellByHandle(e.cellHandle); if (cell) { this._sendMessageToWebview({ - type: 'trackFinalOutputRender', + type: 'startWatchingOutputResize', + cellId: cell.id + }); + } + // If e.changed is undefined, it means notebook execution just finished + } else if (e.changed === undefined && e.affectsNotebook(this.documentUri)) { + const cell = this.notebookEditor.getCellByHandle(e.cellHandle); + if (cell) { + this._sendMessageToWebview({ + type: 'stopWatchingOutputResize', cellId: cell.id }); } @@ -835,10 +845,9 @@ var requirejs = (function() { break; } - case 'didRenderFinalOutput': { - console.log(`Rendered final output for ${data.cellId}`); + case 'outputResized': + this.notebookEditor.didResizeOutput(data.cellId); break; - } } })); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index e37283b1809..9c0941b1ffc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -401,16 +401,22 @@ export interface IDidFindHighlightMessage extends BaseToWebviewMessage { readonly offset: number; } -export interface ITrackFinalOutputRenderMessage { - readonly type: 'trackFinalOutputRender'; +export interface IStartWatchingOutputMessage { + readonly type: 'startWatchingOutputResize'; readonly cellId: string; } -export interface IDidRenderFinalOutputMessage extends BaseToWebviewMessage { - readonly type: 'didRenderFinalOutput'; +export interface IStopWatchingOutputMessage { + readonly type: 'stopWatchingOutputResize'; readonly cellId: string; } +export interface IOutputResizedMessage extends BaseToWebviewMessage { + readonly type: 'outputResized'; + readonly cellId: string; +} + + export type FromWebviewMessage = WebviewInitialized | IDimensionMessage | IMouseEnterMessage | @@ -439,7 +445,7 @@ export type FromWebviewMessage = WebviewInitialized | IRenderedCellOutputMessage | IDidFindMessage | IDidFindHighlightMessage | - IDidRenderFinalOutputMessage; + IOutputResizedMessage; export type ToWebviewMessage = IClearMessage | IFocusOutputMessage | @@ -470,7 +476,8 @@ export type ToWebviewMessage = IClearMessage | IFindHighlightMessage | IFindUnHighlightMessage | IFindStopMessage | - ITrackFinalOutputRenderMessage; + IStartWatchingOutputMessage | + IStopWatchingOutputMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 786cc9377ce..41efb7a678f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -387,6 +387,7 @@ async function webviewPreloads(ctx: PreloadContext) { function createFocusSink(cellId: string, focusNext?: boolean) { const element = document.createElement('div'); + element.id = `focus-sink-${cellId}`; element.tabIndex = 0; element.addEventListener('focus', () => { postNotebookMessage('focus-editor', { @@ -1172,8 +1173,12 @@ async function webviewPreloads(ctx: PreloadContext) { _highlighter?.dispose(); break; } - case 'trackFinalOutputRender': { - viewModel.trackFinalOutput(event.data.cellId); + case 'startWatchingOutputResize': { + viewModel.startWatchingOutput(event.data.cellId); + break; + } + case 'stopWatchingOutputResize': { + viewModel.stopWatchingOutput(event.data.cellId); break; } } @@ -1398,7 +1403,9 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly _markupCells = new Map(); private readonly _outputCells = new Map(); - private _pendingFinalOutput: string | undefined; + private _trackingResize: string | undefined; + private _outputResizeObservers: ResizeObserver[] = []; + private _outputResizeTimer: any; public clearAll() { this._markupCells.clear(); @@ -1410,13 +1417,14 @@ async function webviewPreloads(ctx: PreloadContext) { this.renderOutputCells(); } - public trackFinalOutput(cellId: string) { - const cell = this._outputCells.get(cellId); - if (cell) { - cell.addFinalOutputTracker(); - } else { - this._pendingFinalOutput = cellId; - } + public startWatchingOutput(cellId: string) { + this._trackingResize = cellId; + this._outputResizeObservers.forEach(o => o.disconnect()); + this._outputResizeObservers = []; + } + + public stopWatchingOutput(cellId: string) { + this._trackingResize = undefined; } private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number, visible: boolean): Promise { @@ -1529,14 +1537,11 @@ async function webviewPreloads(ctx: PreloadContext) { const outputNode = cellOutput.createOutputElement(data.outputId, data.outputOffset, data.left); outputNode.render(data.content, preloadsAndErrors); + // Track resizes on this output + this.trackOutputResize(data.cellId, outputNode.element); + // don't hide until after this step so that the height is right cellOutput.element.style.visibility = data.initiallyHidden ? 'hidden' : 'visible'; - - // If notebook side has indicated we are done (and we haven't added it yet), stick in the final render - if (data.cellId === this._pendingFinalOutput) { - this._pendingFinalOutput = undefined; - cellOutput.addFinalOutputTracker(); - } } public ensureOutputCell(cellId: string, cellTop: number, skipCellTopUpdateIfExist: boolean): OutputCell { @@ -1586,6 +1591,33 @@ async function webviewPreloads(ctx: PreloadContext) { cell?.updateScroll(request); } } + + private outputResizeHandler(cellId: string) { + // If no longer tracking, disconnect. However + // send one more resize event. + if (this._trackingResize !== cellId) { + this._outputResizeObservers.forEach(o => o.disconnect()); + this._outputResizeObservers = []; + } + + // Debounce this callback to only happen after + // 250 ms. Don't need resize events that often. + clearTimeout(this._outputResizeTimer); + this._outputResizeTimer = setTimeout(() => { + postNotebookMessage('outputResized', { + cellId + }); + }, 250); + } + + private trackOutputResize(cellId: string, outputContainer: HTMLElement) { + if (this._trackingResize === cellId) { + const handler = this.outputResizeHandler.bind(this, cellId); + const observer = new ResizeObserver(handler); + this._outputResizeObservers.push(observer); + observer.observe(outputContainer); + } + } }(); class MarkdownCodeBlock { @@ -1819,6 +1851,7 @@ async function webviewPreloads(ctx: PreloadContext) { class OutputCell { public readonly element: HTMLElement; + public readonly bottomElement: HTMLElement; private readonly outputElements = new Map(); @@ -1838,8 +1871,8 @@ async function webviewPreloads(ctx: PreloadContext) { this.element = this.element; - const lowerWrapperElement = createFocusSink(cellId, true); - container.appendChild(lowerWrapperElement); + this.bottomElement = createFocusSink(cellId, true); + container.appendChild(this.bottomElement); // New thoughts. // Send message to indicate force scroll. @@ -1905,24 +1938,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.element.style.visibility = 'visible'; } } - - public addFinalOutputTracker() { - const id = `${this.element.id}-final-output-tracker`; - let tracker: HTMLImageElement | null = document.getElementById(id) as HTMLImageElement; - if (!tracker) { - tracker = document.createElement('img') as HTMLImageElement; - tracker.id = id; - tracker.style.height = '0px'; - tracker.src = ''; - tracker.addEventListener('error', (ev) => { - console.log(`Final cell output has rendered`); - - // Src is empty so this should fire as soon as it renders - postNotebookMessage('didRenderFinalOutput', { cellId: this.element.id }); - }); - this.element.appendChild(tracker); - } - } } class OutputContainer { From a6eb00568fb0ff6e509814108b75da18c27fe366 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 23 May 2022 23:59:17 +0200 Subject: [PATCH 224/350] - Add Show Extension option to Install Anyway dialog (#150218) - Provide Migrate extension action for deprecated extensions --- .../abstractExtensionManagementService.ts | 7 -- .../extensions/browser/extensionEditor.ts | 3 +- .../extensions/browser/extensionsActions.ts | 81 ++++++++++++++++--- .../browser/media/extensionActions.css | 1 + 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 94e9a31abc7..9279c520625 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -363,13 +363,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - const deprecated = report.deprecated[extension.identifier.id.toLowerCase()]; - if (deprecated?.disallowInstall) { - const message = deprecated.extension ? nls.localize('unsupported extension with alternative', "Can't install '{0}' extension because it is deprecated. Use {1} extension instead.", extension.identifier.id, deprecated.extension.displayName) - : nls.localize('unsupported extension without alternative and no message', "Can't install '{0}' extension because it is deprecated.", extension.identifier.id); - throw new ExtensionManagementError(message, ExtensionManagementErrorCode.Deprecated); - } - if (!await this.canInstall(extension)) { const targetPlatform = await this.getTargetPlatform(); throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index da2bc141afc..eb5589276f2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -29,7 +29,7 @@ import { UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, - InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction + InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtension } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -338,6 +338,7 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(InstallingLabelAction), this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.uninstall', UninstallAction.UninstallLabel, [ [ + this.instantiationService.createInstance(MigrateDeprecatedExtension, false), this.instantiationService.createInstance(UninstallAction), this.instantiationService.createInstance(InstallAnotherVersionAction), ] diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index e578b8359cb..7032389c964 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -276,13 +276,22 @@ export abstract class AbstractInstallAction extends ExtensionAction { } if (this.extension.deprecationInfo) { - const result = await this.dialogService.confirm({ - type: 'warning', - message: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), - detail: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), - primaryButton: localize('install anyway', "Install Anyway"), - }); - if (!result.confirmed) { + const result = await this.dialogService.show( + Severity.Warning, + localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), + [ + localize('install anyway', "Install Anyway"), + localize('open extension', "Open Extension"), + localize('cancel', "Cancel"), + ], + { + detail: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), + cancelId: 2, + }); + if (result.choice === 1) { + return this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.installPreReleaseVersion }); + } + if (result.choice === 2) { return; } } @@ -757,10 +766,15 @@ export class UpdateAction extends ExtensionAction { } private async computeAndUpdateEnablement(): Promise { + this.enabled = false; + this.class = UpdateAction.DisabledClass; + this.label = this.getLabel(); + if (!this.extension) { - this.enabled = false; - this.class = UpdateAction.DisabledClass; - this.label = this.getLabel(); + return; + } + + if (this.extension.deprecationInfo) { return; } @@ -800,6 +814,51 @@ export class UpdateAction extends ExtensionAction { } } +export class MigrateDeprecatedExtension extends ExtensionAction { + + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} prominent migrate`; + private static readonly DisabledClass = `${MigrateDeprecatedExtension.EnabledClass} disabled`; + + constructor( + private readonly small: boolean, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super('extensions.uninstall', localize('migrateExtension', "Migrate"), MigrateDeprecatedExtension.DisabledClass, false); + this.update(); + } + + update(): void { + this.enabled = false; + this.class = MigrateDeprecatedExtension.DisabledClass; + if (!this.extension?.local) { + return; + } + if (this.extension.state !== ExtensionState.Installed) { + return; + } + if (!this.extension.deprecationInfo?.extension) { + return; + } + const id = this.extension.deprecationInfo.extension.id; + if (this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, { id }))) { + return; + } + this.enabled = true; + this.class = MigrateDeprecatedExtension.EnabledClass; + this.tooltip = localize('migrate to', "Migrate to {0}", this.extension.deprecationInfo.extension.displayName); + this.label = this.small ? localize('migrate', "Migrate") : this.tooltip; + } + + override async run(): Promise { + if (!this.extension?.deprecationInfo?.extension) { + return; + } + await this.extensionsWorkbenchService.uninstall(this.extension); + const [extension] = await this.extensionsWorkbenchService.getExtensions([{ id: this.extension.deprecationInfo.extension.id, preRelease: this.extension.deprecationInfo?.extension?.preRelease }], CancellationToken.None); + await this.extensionsWorkbenchService.install(extension); + } +} + export class ExtensionActionWithDropdownActionViewItem extends ActionWithDropdownActionViewItem { constructor( @@ -1138,7 +1197,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && !!this.extension.local && !!this.extension.server && this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension && !this.extension.isBuiltin && !!this.extension.gallery && !!this.extension.local && !!this.extension.server && this.extension.state === ExtensionState.Installed && !this.extension.deprecationInfo; } override async run(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 75327c88344..13d0d87abca 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -52,6 +52,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling), .monaco-action-bar .action-item.disabled .action-label.extension-action.hide-when-disabled, .monaco-action-bar .action-item.disabled .action-label.extension-action.update, +.monaco-action-bar .action-item.disabled .action-label.extension-action.migrate, .monaco-action-bar .action-item.disabled .action-label.extension-action.theme, .monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync, .monaco-action-bar .action-item.action-dropdown-item.disabled, From b8420578fd0acf3c5593503b1f918c683d30304c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 23 May 2022 15:30:42 -0700 Subject: [PATCH 225/350] Add additional allowed tags in untrusted notebook markdown (#150228) This adds some extra allowed tags when rendering markdown in notebooks. Additions are: ``` "b", "br", "caption", "center", "col", "colgroup", "details", "em", "font", "i", "kbd", "sub", "summary", "sup", "table", "tbody", "tfoot", "thead", "td", "th", "tr", "tt", "u", "video" ``` --- .../notebook/index.ts | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 079ed928f3f..a036374fed1 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -9,7 +9,57 @@ import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { ActivationFunction } from 'vscode-notebook-renderer'; const sanitizerOptions: DOMPurify.Config = { - ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'], + ALLOWED_TAGS: [ + 'a', + 'b', + 'blockquote', + 'br', + 'button', + 'caption', + 'center', + 'code', + 'col', + 'colgroup', + 'details', + 'div', + 'em', + 'font', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'i', + 'img', + 'input', + 'kbd', + 'label', + 'li', + 'ol', + 'p', + 'pre', + 'select', + 'small', + 'span', + 'strong', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'tr', + 'tt', + 'u', + 'ul', + 'video', + ], }; export const activate: ActivationFunction = (ctx) => { From 033e4eadda87433c107b2ff106039101fc82dacd Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 23 May 2022 15:42:51 -0700 Subject: [PATCH 226/350] Always watch resize so can handle post execute resizes --- .../interactive/browser/interactiveEditor.ts | 10 +++-- .../view/renderers/backLayerWebView.ts | 24 +---------- .../browser/view/renderers/webviewMessages.ts | 14 +------ .../browser/view/renderers/webviewPreloads.ts | 42 +++++-------------- 4 files changed, 19 insertions(+), 71 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 9265c2fca79..5093e65b993 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -403,9 +403,13 @@ export class InteractiveEditor extends EditorPane { isReadOnly: true }); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidResizeOutput((cvm) => { - // If we're already at the bottom or auto scroll is enabled, scroll to the bottom - if (this.configurationService.getValue('interactive.alwaysScrollOnNewCell') || this.#state === ScrollingState.StickyToBottom) { - this.#notebookWidget.value!.scrollToBottom(); + // Ignore resizes on anything but the last cell. + const index = this.#notebookWidget.value!.getCellIndex(cvm); + if (index === this.#notebookWidget.value!.getLength() - 1) { + // If we're already at the bottom or auto scroll is enabled, scroll to the bottom + if (this.configurationService.getValue('interactive.alwaysScrollOnNewCell') || this.#state === ScrollingState.StickyToBottom) { + this.#notebookWidget.value!.scrollToBottom(); + } } })); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 513f8b4856e..af57430b06e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -36,7 +36,7 @@ import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; -import { CellUri, INotebookRendererInfo, NotebookCellExecutionState, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; @@ -177,28 +177,6 @@ export class BackLayerWebView extends Disposable { css: getTokenizationCss(), }); })); - - this._register(notebookExecutionStateService.onDidChangeCellExecution((e) => { - // If e.changed state is pending, it means notebook execution just started - if (e.changed && e.changed.state === NotebookCellExecutionState.Pending && e.affectsNotebook(this.documentUri)) { - const cell = this.notebookEditor.getCellByHandle(e.cellHandle); - if (cell) { - this._sendMessageToWebview({ - type: 'startWatchingOutputResize', - cellId: cell.id - }); - } - // If e.changed is undefined, it means notebook execution just finished - } else if (e.changed === undefined && e.affectsNotebook(this.documentUri)) { - const cell = this.notebookEditor.getCellByHandle(e.cellHandle); - if (cell) { - this._sendMessageToWebview({ - type: 'stopWatchingOutputResize', - cellId: cell.id - }); - } - } - })); } updateOptions(options: BacklayerWebviewOptions) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 9c0941b1ffc..4888d56bd8f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -401,16 +401,6 @@ export interface IDidFindHighlightMessage extends BaseToWebviewMessage { readonly offset: number; } -export interface IStartWatchingOutputMessage { - readonly type: 'startWatchingOutputResize'; - readonly cellId: string; -} - -export interface IStopWatchingOutputMessage { - readonly type: 'stopWatchingOutputResize'; - readonly cellId: string; -} - export interface IOutputResizedMessage extends BaseToWebviewMessage { readonly type: 'outputResized'; readonly cellId: string; @@ -475,9 +465,7 @@ export type ToWebviewMessage = IClearMessage | IFindMessage | IFindHighlightMessage | IFindUnHighlightMessage | - IFindStopMessage | - IStartWatchingOutputMessage | - IStopWatchingOutputMessage; + IFindStopMessage; export type AnyMessage = FromWebviewMessage | ToWebviewMessage; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 41efb7a678f..5aff1145fd8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1173,14 +1173,6 @@ async function webviewPreloads(ctx: PreloadContext) { _highlighter?.dispose(); break; } - case 'startWatchingOutputResize': { - viewModel.startWatchingOutput(event.data.cellId); - break; - } - case 'stopWatchingOutputResize': { - viewModel.stopWatchingOutput(event.data.cellId); - break; - } } }); @@ -1403,13 +1395,14 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly _markupCells = new Map(); private readonly _outputCells = new Map(); - private _trackingResize: string | undefined; private _outputResizeObservers: ResizeObserver[] = []; private _outputResizeTimer: any; public clearAll() { this._markupCells.clear(); this._outputCells.clear(); + this._outputResizeObservers.forEach(o => o.disconnect()); + this._outputResizeObservers = []; } public rerender() { @@ -1417,16 +1410,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.renderOutputCells(); } - public startWatchingOutput(cellId: string) { - this._trackingResize = cellId; - this._outputResizeObservers.forEach(o => o.disconnect()); - this._outputResizeObservers = []; - } - - public stopWatchingOutput(cellId: string) { - this._trackingResize = undefined; - } - private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number, visible: boolean): Promise { const existing = this._markupCells.get(init.cellId); if (existing) { @@ -1550,6 +1533,10 @@ async function webviewPreloads(ctx: PreloadContext) { if (!cell) { cell = new OutputCell(cellId); this._outputCells.set(cellId, cell); + + // New output cell, clear resize handlers + this._outputResizeObservers.forEach(o => o.disconnect); + this._outputResizeObservers = []; } if (existed && skipCellTopUpdateIfExist) { @@ -1593,13 +1580,6 @@ async function webviewPreloads(ctx: PreloadContext) { } private outputResizeHandler(cellId: string) { - // If no longer tracking, disconnect. However - // send one more resize event. - if (this._trackingResize !== cellId) { - this._outputResizeObservers.forEach(o => o.disconnect()); - this._outputResizeObservers = []; - } - // Debounce this callback to only happen after // 250 ms. Don't need resize events that often. clearTimeout(this._outputResizeTimer); @@ -1611,12 +1591,10 @@ async function webviewPreloads(ctx: PreloadContext) { } private trackOutputResize(cellId: string, outputContainer: HTMLElement) { - if (this._trackingResize === cellId) { - const handler = this.outputResizeHandler.bind(this, cellId); - const observer = new ResizeObserver(handler); - this._outputResizeObservers.push(observer); - observer.observe(outputContainer); - } + const handler = this.outputResizeHandler.bind(this, cellId); + const observer = new ResizeObserver(handler); + this._outputResizeObservers.push(observer); + observer.observe(outputContainer); } }(); From 7075f2db1eed67ec4b079bec9a97fee96788e16d Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Mon, 23 May 2022 16:05:31 -0700 Subject: [PATCH 227/350] Move out Settings editor indicator label impl (#150230) This PR reduces technical debt. --- .../preferences/browser/settingsEditor2.ts | 3 +- .../settingsEditorSettingIndicators.ts | 172 +++++++++++++++++ .../preferences/browser/settingsTree.ts | 178 ++---------------- 3 files changed, 188 insertions(+), 165 deletions(-) create mode 100644 src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 4bbcafeb7c2..76234de0e48 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -40,7 +40,7 @@ import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/co import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; +import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; @@ -60,6 +60,7 @@ import { Color } from 'vs/base/common/color'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { SettingsSearchFilterDropdownMenuActionViewItem } from 'vs/workbench/contrib/preferences/browser/settingsSearchMenu'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ISettingOverrideClickEvent } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; export const enum SettingsFocusContext { Search, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts new file mode 100644 index 00000000000..2f5cfdd2caa --- /dev/null +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; +import { Emitter } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; +import { getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; +import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; + +const $ = DOM.$; + +export interface ISettingOverrideClickEvent { + scope: string; + targetKey: string; +} + +/** + * Renders the indicators next to a setting, such as Sync Ignored, Also Modified In, etc. + */ +export class SettingsTreeIndicatorsLabel { + /** + * This element wraps around the other elements. + */ + private labelElement: HTMLElement; + private scopeOverridesElement: HTMLElement; + private syncIgnoredElement: HTMLElement; + private defaultOverrideIndicatorElement: HTMLElement; + private defaultOverrideIndicatorLabel: SimpleIconLabel; + + constructor(container: HTMLElement) { + this.labelElement = DOM.append(container, $('.misc-label')); + this.labelElement.style.display = 'inline'; + + this.scopeOverridesElement = this.createScopeOverridesElement(); + this.syncIgnoredElement = this.createSyncIgnoredElement(); + const { element: defaultOverrideElement, label: defaultOverrideLabel } = this.createDefaultOverrideIndicator(); + this.defaultOverrideIndicatorElement = defaultOverrideElement; + this.defaultOverrideIndicatorLabel = defaultOverrideLabel; + } + + private createScopeOverridesElement(): HTMLElement { + const otherOverridesElement = $('span.setting-item-overrides'); + return otherOverridesElement; + } + + private createSyncIgnoredElement(): HTMLElement { + const syncIgnoredElement = $('span.setting-item-ignored'); + const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); + syncIgnoredLabel.text = `$(sync-ignored) ${localize('extensionSyncIgnoredLabel', 'Sync: Ignored')}`; + syncIgnoredLabel.title = localize('syncIgnoredTitle', "Settings sync does not sync this setting"); + return syncIgnoredElement; + } + + private createDefaultOverrideIndicator(): { element: HTMLElement; label: SimpleIconLabel } { + const defaultOverrideIndicator = $('span.setting-item-default-overridden'); + const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); + return { element: defaultOverrideIndicator, label: defaultOverrideLabel }; + } + + private render() { + const elementsToShow = [this.scopeOverridesElement, this.syncIgnoredElement, this.defaultOverrideIndicatorElement].filter(element => { + return element.style.display !== 'none'; + }); + + this.labelElement.innerText = ''; + this.labelElement.style.display = 'none'; + if (elementsToShow.length) { + this.labelElement.style.display = 'inline'; + DOM.append(this.labelElement, $('span', undefined, '(')); + for (let i = 0; i < elementsToShow.length - 1; i++) { + DOM.append(this.labelElement, elementsToShow[i]); + DOM.append(this.labelElement, $('span.comma', undefined, ', ')); + } + DOM.append(this.labelElement, elementsToShow[elementsToShow.length - 1]); + DOM.append(this.labelElement, $('span', undefined, ')')); + } + } + + updateSyncIgnored(element: SettingsTreeSettingElement, ignoredSettings: string[]) { + this.syncIgnoredElement.style.display = ignoredSettings.includes(element.setting.key) ? 'inline' : 'none'; + this.render(); + } + + updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter) { + this.scopeOverridesElement.innerText = ''; + this.scopeOverridesElement.style.display = 'none'; + if (element.overriddenScopeList.length) { + this.scopeOverridesElement.style.display = 'inline'; + const otherOverridesLabel = element.isConfigured ? + localize('alsoConfiguredIn', "Also modified in") : + localize('configuredIn', "Modified in"); + + DOM.append(this.scopeOverridesElement, $('span', undefined, `${otherOverridesLabel}: `)); + + for (let i = 0; i < element.overriddenScopeList.length; i++) { + const view = DOM.append(this.scopeOverridesElement, $('a.modified-scope', undefined, element.overriddenScopeList[i])); + + if (i !== element.overriddenScopeList.length - 1) { + DOM.append(this.scopeOverridesElement, $('span', undefined, ', ')); + } + + elementDisposables.add( + DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { + onDidClickOverrideElement.fire({ + targetKey: element.setting.key, + scope: element.overriddenScopeList[i] + }); + e.preventDefault(); + e.stopPropagation(); + })); + } + } + this.render(); + } + + updateDefaultOverrideIndicator(element: SettingsTreeSettingElement) { + this.defaultOverrideIndicatorElement.style.display = 'none'; + const defaultValueSource = element.setting.defaultValueSource; + if (defaultValueSource) { + this.defaultOverrideIndicatorElement.style.display = 'inline'; + if (typeof defaultValueSource !== 'string' && defaultValueSource.id !== element.setting.extensionInfo?.id) { + const extensionSource = defaultValueSource.displayName ?? defaultValueSource.id; + this.defaultOverrideIndicatorLabel.title = localize('defaultOverriddenDetails', "Default setting value overridden by {0}", extensionSource); + this.defaultOverrideIndicatorLabel.text = localize('defaultOverrideLabelText', "$(replace) {0}", extensionSource); + } else if (typeof defaultValueSource === 'string') { + this.defaultOverrideIndicatorLabel.title = localize('defaultOverriddenDetails', "Default setting value overridden by {0}", defaultValueSource); + this.defaultOverrideIndicatorLabel.text = localize('defaultOverrideLabelText', "$(replace) {0}", defaultValueSource); + } + } + this.render(); + } +} + +export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IConfigurationService): string { + const ariaLabelSections: string[] = []; + + // Add other overrides text + const otherOverridesStart = element.isConfigured ? + localize('alsoConfiguredIn', "Also modified in") : + localize('configuredIn', "Modified in"); + const otherOverridesList = element.overriddenScopeList.join(', '); + if (element.overriddenScopeList.length) { + ariaLabelSections.push(`${otherOverridesStart} ${otherOverridesList}`); + } + + // Add sync ignored text + const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), configurationService); + if (ignoredSettings.includes(element.setting.key)) { + ariaLabelSections.push(localize('syncIgnoredTitle', "Settings sync does not sync this setting")); + } + + // Add default override indicator text + if (element.setting.defaultValueSource) { + const defaultValueSource = element.setting.defaultValueSource; + if (typeof defaultValueSource !== 'string' && defaultValueSource.id !== element.setting.extensionInfo?.id) { + const extensionSource = defaultValueSource.displayName ?? defaultValueSource.id; + ariaLabelSections.push(localize('defaultOverriddenDetails', "Default setting value overridden by {0}", extensionSource)); + } else if (typeof defaultValueSource === 'string') { + ariaLabelSections.push(localize('defaultOverriddenDetails', "Default setting value overridden by {0}", defaultValueSource)); + } + } + + const ariaLabel = ariaLabelSections.join('. '); + return ariaLabel; +} diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 2e2ee4e7257..5a9aa77dcf2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -62,6 +62,7 @@ import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/prefere import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { focusedRowBackground, focusedRowBorder, rowHoverBackground, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; +import { getIndicatorsLabelAriaLabel, ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; const $ = DOM.$; @@ -588,7 +589,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { descriptionElement: HTMLElement; controlElement: HTMLElement; deprecationWarningElement: HTMLElement; - miscLabel: SettingsTreeMiscLabel; + indicatorsLabel: SettingsTreeIndicatorsLabel; toolbar: ToolBar; elementDisposables: DisposableStore; } @@ -666,11 +667,6 @@ export interface ISettingLinkClickEvent { targetKey: string; } -export interface ISettingOverrideClickEvent { - scope: string; - targetKey: string; -} - function removeChildrenFromTabOrder(node: Element): void { const focusableElements = node.querySelectorAll(` [tabindex="0"], @@ -783,7 +779,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category')); const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label')); const labelElement = new SimpleIconLabel(labelElementContainer); - const miscLabel = new SettingsTreeMiscLabel(titleElement); + const indicatorsLabel = new SettingsTreeIndicatorsLabel(titleElement); + const descriptionElement = DOM.append(container, $('.setting-item-description')); const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope."); @@ -810,7 +807,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre descriptionElement, controlElement, deprecationWarningElement, - miscLabel, + indicatorsLabel, toolbar }; @@ -914,7 +911,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.descriptionElement.innerText = element.description; } - template.miscLabel.updateOtherOverrides(element, template.elementDisposables, this._onDidClickOverrideElement); + template.indicatorsLabel.updateScopeOverrides(element, template.elementDisposables, this._onDidClickOverrideElement); const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType, manualReset: false }); const deprecationText = element.setting.deprecationMessage || ''; @@ -931,10 +928,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre this.renderValue(element, template, onChange); - template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); - template.miscLabel.updateDefaultOverrideIndicator(element); + template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings); + template.indicatorsLabel.updateDefaultOverrideIndicator(element); template.elementDisposables.add(this.onDidChangeIgnoredSettings(() => { - template.miscLabel.updateSyncIgnored(element, this.ignoredSettings); + template.indicatorsLabel.updateSyncIgnored(element, this.ignoredSettings); })); template.policyWarningElement.hidden = !element.hasPolicyValue; @@ -1819,7 +1816,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const categoryElement = DOM.append(titleElement, $('span.setting-item-category')); const labelElementContainer = DOM.append(titleElement, $('span.setting-item-label')); const labelElement = new SimpleIconLabel(labelElementContainer); - const miscLabel = new SettingsTreeMiscLabel(titleElement); + const indicatorsLabel = new SettingsTreeIndicatorsLabel(titleElement); const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description')); const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); @@ -1870,7 +1867,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre policyWarningElement, descriptionElement, deprecationWarningElement, - miscLabel, + indicatorsLabel, toolbar }; @@ -2142,120 +2139,6 @@ function escapeInvisibleChars(enumValue: string): string { .replace(/\r/g, '\\r'); } -/** - * Controls logic and rendering of the label next to each setting header. - * For example, the "Modified by" and "Overridden by" labels go here. - */ -class SettingsTreeMiscLabel { - private labelElement: HTMLElement; - private otherOverridesElement: HTMLElement; - private syncIgnoredElement: HTMLElement; - private defaultOverrideIndicatorElement: HTMLElement; - private defaultOverrideIndicatorLabel: SimpleIconLabel; - - constructor(container: HTMLElement) { - this.labelElement = DOM.append(container, $('.misc-label')); - this.labelElement.style.display = 'inline'; - - this.otherOverridesElement = this.createOtherOverridesElement(); - this.syncIgnoredElement = this.createSyncIgnoredElement(); - const { element, label } = this.createDefaultOverrideIndicator(); - this.defaultOverrideIndicatorElement = element; - this.defaultOverrideIndicatorLabel = label; - } - - private createOtherOverridesElement(): HTMLElement { - const otherOverridesElement = $('span.setting-item-overrides'); - return otherOverridesElement; - } - - private createSyncIgnoredElement(): HTMLElement { - const syncIgnoredElement = $('span.setting-item-ignored'); - const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); - syncIgnoredLabel.text = `$(sync-ignored) ${localize('extensionSyncIgnoredLabel', 'Sync: Ignored')}`; - syncIgnoredLabel.title = localize('syncIgnoredTitle', "Settings sync does not sync this setting"); - return syncIgnoredElement; - } - - private createDefaultOverrideIndicator(): { element: HTMLElement; label: SimpleIconLabel } { - const defaultOverrideIndicator = $('span.setting-item-default-overridden'); - const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); - return { element: defaultOverrideIndicator, label: defaultOverrideLabel }; - } - - private render() { - const elementsToShow = [this.otherOverridesElement, this.syncIgnoredElement, this.defaultOverrideIndicatorElement].filter(element => { - return element.style.display !== 'none'; - }); - - this.labelElement.innerText = ''; - this.labelElement.style.display = 'none'; - if (elementsToShow.length) { - this.labelElement.style.display = 'inline'; - DOM.append(this.labelElement, $('span', undefined, '(')); - for (let i = 0; i < elementsToShow.length - 1; i++) { - DOM.append(this.labelElement, elementsToShow[i]); - DOM.append(this.labelElement, $('span.comma', undefined, ', ')); - } - DOM.append(this.labelElement, elementsToShow[elementsToShow.length - 1]); - DOM.append(this.labelElement, $('span', undefined, ')')); - } - } - - updateSyncIgnored(element: SettingsTreeSettingElement, ignoredSettings: string[]) { - this.syncIgnoredElement.style.display = ignoredSettings.includes(element.setting.key) ? 'inline' : 'none'; - this.render(); - } - - updateOtherOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter) { - this.otherOverridesElement.innerText = ''; - this.otherOverridesElement.style.display = 'none'; - if (element.overriddenScopeList.length) { - this.otherOverridesElement.style.display = 'inline'; - const otherOverridesLabel = element.isConfigured ? - localize('alsoConfiguredIn', "Also modified in") : - localize('configuredIn', "Modified in"); - - DOM.append(this.otherOverridesElement, $('span', undefined, `${otherOverridesLabel}: `)); - - for (let i = 0; i < element.overriddenScopeList.length; i++) { - const view = DOM.append(this.otherOverridesElement, $('a.modified-scope', undefined, element.overriddenScopeList[i])); - - if (i !== element.overriddenScopeList.length - 1) { - DOM.append(this.otherOverridesElement, $('span', undefined, ', ')); - } - - elementDisposables.add( - DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { - onDidClickOverrideElement.fire({ - targetKey: element.setting.key, - scope: element.overriddenScopeList[i] - }); - e.preventDefault(); - e.stopPropagation(); - })); - } - } - this.render(); - } - - updateDefaultOverrideIndicator(element: SettingsTreeSettingElement) { - this.defaultOverrideIndicatorElement.style.display = 'none'; - if (element.setting.defaultValueSource) { - this.defaultOverrideIndicatorElement.style.display = 'inline'; - const defaultValueSource = element.setting.defaultValueSource; - if (typeof defaultValueSource !== 'string' && defaultValueSource.id !== element.setting.extensionInfo?.id) { - const extensionSource = defaultValueSource.displayName ?? defaultValueSource.id; - this.defaultOverrideIndicatorLabel.title = localize('defaultOverriddenDetails', "Default value overridden by {0}", extensionSource); - this.defaultOverrideIndicatorLabel.text = localize('defaultOverrideLabelText', "$(wrench) Overridden by: {0}", extensionSource); - } else if (typeof defaultValueSource === 'string') { - this.defaultOverrideIndicatorLabel.title = localize('defaultOverriddenDetails', "Default value overridden by {0}", defaultValueSource); - this.defaultOverrideIndicatorLabel.text = localize('defaultOverrideLabelText', "$(wrench) Overridden by: {0}", defaultValueSource); - } - } - this.render(); - } -} export class SettingsTreeFilter implements ITreeFilter { constructor( @@ -2419,9 +2302,9 @@ class SettingsTreeAccessibilityProvider implements IListAccessibilityProvider { From 45304da73d51e8df7ef4da00347db0ca3e5c263f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 23 May 2022 16:27:17 -0700 Subject: [PATCH 228/350] Finalize NotebookEditor api proposal (#149767) * Finalize NotebookEditor api proposal Fixes #149271 This finalizes most parts of the NotebookEditor api proposal. I haven't removed the proposal entirely as there are still a few parts being left behind: - The deprecated properties/functions - A few contribution points such as `notebook/cell/executePrimary` * remove extra quote --- extensions/ipynb/package.json | 1 - extensions/ipynb/tsconfig.json | 1 - extensions/notebook-renderers/tsconfig.json | 1 - extensions/vscode-api-tests/package.json | 1 - extensions/vscode-notebook-tests/package.json | 1 - .../workbench/api/common/extHost.api.impl.ts | 11 +- .../api/common/extHostNotebookRenderers.ts | 18 +- src/vscode-dts/vscode.d.ts | 168 +++++++++++++++++ .../vscode.proposed.notebookEditor.d.ts | 173 +----------------- 9 files changed, 176 insertions(+), 199 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 901f65e56e1..1e251a5a19b 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -9,7 +9,6 @@ "vscode": "^1.57.0" }, "enabledApiProposals": [ - "notebookEditor", "notebookWorkspaceEdit" ], "activationEvents": [ diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index 918db6ea6c2..a8006017458 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -9,7 +9,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", "../../src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts" ] } diff --git a/extensions/notebook-renderers/tsconfig.json b/extensions/notebook-renderers/tsconfig.json index 2032bf87b0d..23609811f3a 100644 --- a/extensions/notebook-renderers/tsconfig.json +++ b/extensions/notebook-renderers/tsconfig.json @@ -9,7 +9,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", "../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts", ] } diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 0f7f928dc81..8b2dab07a14 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -24,7 +24,6 @@ "notebookControllerKind", "notebookDebugOptions", "notebookDeprecated", - "notebookEditor", "notebookEditorDecorationType", "notebookEditorEdit", "notebookLiveShare", diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 8bb9eb8d3b3..bd3f3586db0 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -15,7 +15,6 @@ "notebookControllerKind", "notebookDebugOptions", "notebookDeprecated", - "notebookEditor", "notebookEditorDecorationType", "notebookLiveShare", "notebookMessaging", diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 313a9aadfd1..1c1d4cbcaea 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -759,31 +759,28 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWebviewViews.registerWebviewViewProvider(extension, viewId, provider, options?.webviewOptions); }, get activeNotebookEditor(): vscode.NotebookEditor | undefined { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.activeNotebookEditor; }, onDidChangeActiveNotebookEditor(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables); }, get visibleNotebookEditors() { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.visibleNotebookEditors; }, get onDidChangeVisibleNotebookEditors() { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebook.onDidChangeVisibleNotebookEditors; }, onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookEditors.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables); }, onDidChangeNotebookEditorVisibleRanges(listener, thisArgs?, disposables?) { - checkProposedApiEnabled(extension, 'notebookEditor'); return extHostNotebookEditors.onDidChangeNotebookEditorVisibleRanges(listener, thisArgs, disposables); }, showNotebookDocument(uriOrDocument, options?) { - checkProposedApiEnabled(extension, 'notebookEditor'); + if (URI.isUri(uriOrDocument)) { + extHostApiDeprecation.report('window.showNotebookDocument(uri)', extension, + `Please use 'window.openNotebookDocument' and 'window.showTextDocument'`); + } return extHostNotebook.showNotebookDocument(uriOrDocument, options); }, registerExternalUriOpener(id: string, opener: vscode.ExternalUriOpener, metadata: vscode.ExternalUriOpenerMetadata) { diff --git a/src/vs/workbench/api/common/extHostNotebookRenderers.ts b/src/vs/workbench/api/common/extHostNotebookRenderers.ts index 1b8925f8757..d3a3e0dd18e 100644 --- a/src/vs/workbench/api/common/extHostNotebookRenderers.ts +++ b/src/vs/workbench/api/common/extHostNotebookRenderers.ts @@ -8,7 +8,6 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ExtHostNotebookRenderersShape, IMainContext, MainContext, MainThreadNotebookRenderersShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostNotebookEditor } from 'vs/workbench/api/common/extHostNotebookEditor'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as vscode from 'vscode'; @@ -30,29 +29,16 @@ export class ExtHostNotebookRenderers implements ExtHostNotebookRenderersShape { throw new Error(`Extensions may only call createRendererMessaging() for renderers they contribute (got ${rendererId})`); } - // In the stable API, the editor is given as an empty object, and this map - // is used to maintain references. This can be removed after editor finalization. - const notebookEditorVisible = isProposedApiEnabled(manifest, 'notebookEditor'); - const notebookEditorAliases = new WeakMap<{}, vscode.NotebookEditor>(); - const messaging: vscode.NotebookRendererMessaging = { onDidReceiveMessage: (listener, thisArg, disposables) => { - const wrappedListener = notebookEditorVisible ? listener : (evt: { editor: vscode.NotebookEditor; message: any }) => { - const obj = {}; - notebookEditorAliases.set(obj, evt.editor); - listener({ editor: obj as vscode.NotebookEditor, message: evt.message }); - }; - - return this.getOrCreateEmitterFor(rendererId).event(wrappedListener, thisArg, disposables); + return this.getOrCreateEmitterFor(rendererId).event(listener, thisArg, disposables); }, postMessage: (message, editorOrAlias) => { if (ExtHostNotebookEditor.apiEditorsToExtHost.has(message)) { // back compat for swapped args [message, editorOrAlias] = [editorOrAlias, message]; } - - const editor = notebookEditorVisible ? editorOrAlias : notebookEditorAliases.get(editorOrAlias!); - const extHostEditor = editor && ExtHostNotebookEditor.apiEditorsToExtHost.get(editor); + const extHostEditor = editorOrAlias && ExtHostNotebookEditor.apiEditorsToExtHost.get(editorOrAlias); return this.proxy.$postMessage(extHostEditor?.id, rendererId, message); }, }; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index a34508112e7..3d7b87599f1 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -777,6 +777,67 @@ declare module 'vscode' { selection?: Range; } + /** + * Represents an event describing the change in a {@link NotebookEditor.selections notebook editor's selections}. + */ + export interface NotebookEditorSelectionChangeEvent { + /** + * The {@link NotebookEditor notebook editor} for which the selections have changed. + */ + readonly notebookEditor: NotebookEditor; + + /** + * The new value for the {@link NotebookEditor.selections notebook editor's selections}. + */ + readonly selections: readonly NotebookRange[]; + } + + /** + * Represents an event describing the change in a {@link NotebookEditor.visibleRanges notebook editor's visibleRanges}. + */ + export interface NotebookEditorVisibleRangesChangeEvent { + /** + * The {@link NotebookEditor notebook editor} for which the visible ranges have changed. + */ + readonly notebookEditor: NotebookEditor; + + /** + * The new value for the {@link NotebookEditor.visibleRanges notebook editor's visibleRanges}. + */ + readonly visibleRanges: readonly NotebookRange[]; + } + + /** + * Represents options to configure the behavior of showing a {@link NotebookDocument notebook document} in an {@link NotebookEditor notebook editor}. + */ + export interface NotebookDocumentShowOptions { + /** + * An optional view column in which the {@link NotebookEditor notebook editor} should be shown. + * The default is the {@link ViewColumn.Active active}, other values are adjusted to + * be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is + * not adjusted. Use {@linkcode ViewColumn.Beside} to open the + * editor to the side of the currently active one. + */ + readonly viewColumn?: ViewColumn; + + /** + * An optional flag that when `true` will stop the {@link NotebookEditor notebook editor} from taking focus. + */ + readonly preserveFocus?: boolean; + + /** + * An optional flag that controls if an {@link NotebookEditor notebook editor}-tab shows as preview. Preview tabs will + * be replaced and reused until set to stay - either explicitly or through editing. The default behaviour depends + * on the `workbench.editor.enablePreview`-setting. + */ + readonly preview?: boolean; + + /** + * An optional selection to apply for the document in the {@link NotebookEditor notebook editor}. + */ + readonly selections?: readonly NotebookRange[]; + } + /** * A reference to one of the workbench colors as defined in https://code.visualstudio.com/docs/getstarted/theme-color-reference. * Using a theme color is preferred over a custom color as it gives theme authors and users the possibility to change the color. @@ -9121,6 +9182,43 @@ declare module 'vscode' { */ export const onDidChangeTextEditorViewColumn: Event; + /** + * The currently visible {@link NotebookEditor notebook editors} or an empty array. + */ + export const visibleNotebookEditors: readonly NotebookEditor[]; + + /** + * An {@link Event} which fires when the {@link window.visibleNotebookEditors visible notebook editors} + * has changed. + */ + export const onDidChangeVisibleNotebookEditors: Event; + + /** + * The currently active {@link NotebookEditor notebook editor} or `undefined`. The active editor is the one + * that currently has focus or, when none has focus, the one that has changed + * input most recently. + */ + export const activeNotebookEditor: NotebookEditor | undefined; + + /** + * An {@link Event} which fires when the {@link window.activeNotebookEditor active notebook editor} + * has changed. *Note* that the event also fires when the active editor changes + * to `undefined`. + */ + export const onDidChangeActiveNotebookEditor: Event; + + /** + * An {@link Event} which fires when the {@link NotebookEditor.selections notebook editor selections} + * have changed. + */ + export const onDidChangeNotebookEditorSelection: Event; + + /** + * An {@link Event} which fires when the {@link NotebookEditor.visibleRanges notebook editor visible ranges} + * have changed. + */ + export const onDidChangeNotebookEditorVisibleRanges: Event; + /** * The currently opened terminals or an empty array. */ @@ -9200,6 +9298,16 @@ declare module 'vscode' { */ export function showTextDocument(uri: Uri, options?: TextDocumentShowOptions): Thenable; + /** + * Show the given {@link NotebookDocument} in a {@link NotebookEditor notebook editor}. + * + * @param document A text document to be shown. + * @param options {@link NotebookDocumentShowOptions Editor options} to configure the behavior of showing the {@link NotebookEditor notebook editor}. + * + * @return A promise that resolves to an {@link NotebookEditor notebook editor}. + */ + export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable; + /** * Create a TextEditorDecorationType that can be used to add decorations to text editors. * @@ -12482,6 +12590,32 @@ declare module 'vscode' { } + /** + * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. + */ + export enum NotebookEditorRevealType { + /** + * The range will be revealed with as little scrolling as possible. + */ + Default = 0, + + /** + * The range will always be revealed in the center of the viewport. + */ + InCenter = 1, + + /** + * If the range is outside the viewport, it will be revealed in the center of the viewport. + * Otherwise, it will be revealed with as little scrolling as possible. + */ + InCenterIfOutsideViewport = 2, + + /** + * The range will always be revealed at the top of the viewport. + */ + AtTop = 3 + } + /** * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. * Additional properties of the NotebookEditor are available in the proposed @@ -12489,6 +12623,40 @@ declare module 'vscode' { */ export interface NotebookEditor { + /** + * The {@link NotebookDocument notebook document} associated with this notebook editor. + */ + readonly notebook: NotebookDocument; + + /** + * The primary selection in this notebook editor. + */ + selection: NotebookRange; + + /** + * All selections in this notebook editor. + * + * The primary selection (or focused range) is `selections[0]`. When the document has no cells, the primary selection is empty `{ start: 0, end: 0 }`; + */ + selections: readonly NotebookRange[]; + + /** + * The current visible ranges in the editor (vertically). + */ + readonly visibleRanges: readonly NotebookRange[]; + + /** + * The column in which this editor shows. + */ + readonly viewColumn?: ViewColumn; + + /** + * Scroll as indicated by `revealType` in order to reveal the given range. + * + * @param range A range. + * @param revealType The scrolling strategy for revealing `range`. + */ + revealRange(range: NotebookRange, revealType?: NotebookEditorRevealType): void; } /** diff --git a/src/vscode-dts/vscode.proposed.notebookEditor.d.ts b/src/vscode-dts/vscode.proposed.notebookEditor.d.ts index fd1d91db119..af681fc77ee 100644 --- a/src/vscode-dts/vscode.proposed.notebookEditor.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookEditor.d.ts @@ -7,35 +7,9 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/149271 - /** - * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. - */ - export enum NotebookEditorRevealType { - /** - * The range will be revealed with as little scrolling as possible. - */ - Default = 0, + // ❗️ Important: The main NotebookEditor api has been finalized. + // This file only contains deprecated properties/functions from the proposal. - /** - * The range will always be revealed in the center of the viewport. - */ - InCenter = 1, - - /** - * If the range is outside the viewport, it will be revealed in the center of the viewport. - * Otherwise, it will be revealed with as little scrolling as possible. - */ - InCenterIfOutsideViewport = 2, - - /** - * The range will always be revealed at the top of the viewport. - */ - AtTop = 3 - } - - /** - * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. - */ export interface NotebookEditor { /** * The document associated with this notebook editor. @@ -43,152 +17,9 @@ declare module 'vscode' { * @deprecated Use {@linkcode NotebookEditor.notebook} instead. */ readonly document: NotebookDocument; - - /** - * The {@link NotebookDocument notebook document} associated with this notebook editor. - */ - readonly notebook: NotebookDocument; - - /** - * The primary selection in this notebook editor. - */ - selection: NotebookRange; - - /** - * All selections in this notebook editor. - * - * The primary selection (or focused range) is `selections[0]`. When the document has no cells, the primary selection is empty `{ start: 0, end: 0 }`; - */ - selections: readonly NotebookRange[]; - - /** - * The current visible ranges in the editor (vertically). - */ - readonly visibleRanges: readonly NotebookRange[]; - - /** - * The column in which this editor shows. - */ - readonly viewColumn?: ViewColumn; - - /** - * Scroll as indicated by `revealType` in order to reveal the given range. - * - * @param range A range. - * @param revealType The scrolling strategy for revealing `range`. - */ - revealRange(range: NotebookRange, revealType?: NotebookEditorRevealType): void; - } - - /** - * Represents an event describing the change in a {@link NotebookEditor.selections notebook editor's selections}. - */ - export interface NotebookEditorSelectionChangeEvent { - /** - * The {@link NotebookEditor notebook editor} for which the selections have changed. - */ - readonly notebookEditor: NotebookEditor; - - /** - * The new value for the {@link NotebookEditor.selections notebook editor's selections}. - */ - readonly selections: readonly NotebookRange[]; - } - - /** - * Represents an event describing the change in a {@link NotebookEditor.visibleRanges notebook editor's visibleRanges}. - */ - export interface NotebookEditorVisibleRangesChangeEvent { - /** - * The {@link NotebookEditor notebook editor} for which the visible ranges have changed. - */ - readonly notebookEditor: NotebookEditor; - - /** - * The new value for the {@link NotebookEditor.visibleRanges notebook editor's visibleRanges}. - */ - readonly visibleRanges: readonly NotebookRange[]; - } - - /** - * Represents options to configure the behavior of showing a {@link NotebookDocument notebook document} in an {@link NotebookEditor notebook editor}. - */ - export interface NotebookDocumentShowOptions { - /** - * An optional view column in which the {@link NotebookEditor notebook editor} should be shown. - * The default is the {@link ViewColumn.Active active}, other values are adjusted to - * be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is - * not adjusted. Use {@linkcode ViewColumn.Beside} to open the - * editor to the side of the currently active one. - */ - readonly viewColumn?: ViewColumn; - - /** - * An optional flag that when `true` will stop the {@link NotebookEditor notebook editor} from taking focus. - */ - readonly preserveFocus?: boolean; - - /** - * An optional flag that controls if an {@link NotebookEditor notebook editor}-tab shows as preview. Preview tabs will - * be replaced and reused until set to stay - either explicitly or through editing. The default behaviour depends - * on the `workbench.editor.enablePreview`-setting. - */ - readonly preview?: boolean; - - /** - * An optional selection to apply for the document in the {@link NotebookEditor notebook editor}. - */ - readonly selections?: readonly NotebookRange[]; } export namespace window { - /** - * The currently visible {@link NotebookEditor notebook editors} or an empty array. - */ - export const visibleNotebookEditors: readonly NotebookEditor[]; - - /** - * An {@link Event} which fires when the {@link window.visibleNotebookEditors visible notebook editors} - * has changed. - */ - export const onDidChangeVisibleNotebookEditors: Event; - - /** - * The currently active {@link NotebookEditor notebook editor} or `undefined`. The active editor is the one - * that currently has focus or, when none has focus, the one that has changed - * input most recently. - */ - export const activeNotebookEditor: NotebookEditor | undefined; - - /** - * An {@link Event} which fires when the {@link window.activeNotebookEditor active notebook editor} - * has changed. *Note* that the event also fires when the active editor changes - * to `undefined`. - */ - export const onDidChangeActiveNotebookEditor: Event; - - /** - * An {@link Event} which fires when the {@link NotebookEditor.selections notebook editor selections} - * have changed. - */ - export const onDidChangeNotebookEditorSelection: Event; - - /** - * An {@link Event} which fires when the {@link NotebookEditor.visibleRanges notebook editor visible ranges} - * have changed. - */ - export const onDidChangeNotebookEditorVisibleRanges: Event; - - /** - * Show the given {@link NotebookDocument} in a {@link NotebookEditor notebook editor}. - * - * @param document A text document to be shown. - * @param options {@link NotebookDocumentShowOptions Editor options} to configure the behavior of showing the {@link NotebookEditor notebook editor}. - * - * @return A promise that resolves to an {@link NotebookEditor notebook editor}. - */ - export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable; - /** * A short-hand for `openNotebookDocument(uri).then(document => showNotebookDocument(document, options))`. * From 91923bab48edf9511b00b903302c80dcf098f520 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 23 May 2022 16:28:02 -0700 Subject: [PATCH 229/350] Cleanup data transfer types (#149774) This change attempts to clean up our internal data transfer types - Replace `IDataTransfer` (which was just an alias for map) with a `VSDataTransfer` helper class. This lets us add helpers and also restrict what you can do on a datatransfer - Move `DataTransferDTO` types into `protocol` - Move `DataTransferTypeConverter` into `typeConvert` - Don't return internal types to ext host callers For example, previously we leaked `IDataTransfer` out into providers / controllers. Instead we should always return an instance of `vscode.DataTransfer` to extensions --- src/vs/base/common/dataTransfer.ts | 49 +++++++++++++- src/vs/editor/common/languages.ts | 4 +- .../browser/dropIntoEditorContribution.ts | 39 +++-------- .../api/browser/mainThreadLanguageFeatures.ts | 8 +-- .../api/browser/mainThreadTreeViews.ts | 21 +++--- .../workbench/api/common/extHost.protocol.ts | 15 ++++- .../api/common/extHostLanguageFeatures.ts | 7 +- .../workbench/api/common/extHostTreeViews.ts | 35 +++------- .../api/common/extHostTypeConverters.ts | 50 ++++++++++++++ src/vs/workbench/api/common/extHostTypes.ts | 13 ++-- .../api/common/shared/dataTransfer.ts | 66 ------------------- .../api/common/shared/dataTransferCache.ts | 4 +- src/vs/workbench/browser/dnd.ts | 4 +- .../workbench/browser/parts/views/treeView.ts | 19 ++---- src/vs/workbench/common/views.ts | 6 +- .../views/browser/treeViewsService.ts | 4 +- 16 files changed, 176 insertions(+), 168 deletions(-) delete mode 100644 src/vs/workbench/api/common/shared/dataTransfer.ts diff --git a/src/vs/base/common/dataTransfer.ts b/src/vs/base/common/dataTransfer.ts index 748362a176a..1f56e79f751 100644 --- a/src/vs/base/common/dataTransfer.ts +++ b/src/vs/base/common/dataTransfer.ts @@ -17,4 +17,51 @@ export interface IDataTransferItem { value: any; } -export type IDataTransfer = Map; +export class VSDataTransfer { + + private readonly _data = new Map(); + + public get size(): number { + return this._data.size; + } + + public has(mimeType: string): boolean { + return this._data.has(mimeType); + } + + public get(mimeType: string): IDataTransferItem | undefined { + return this._data.get(mimeType); + } + + public set(mimeType: string, value: IDataTransferItem): void { + this._data.set(mimeType, value); + } + + public setString(mimeType: string, stringOrPromise: string | Promise) { + this.set(mimeType, { + asString: async () => stringOrPromise, + asFile: () => undefined, + value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined, + }); + } + + public setFile(mimeType: string, fileName: string, uri: URI | undefined, data: () => Promise) { + this.set(mimeType, { + asString: async () => '', + asFile: () => ({ name: fileName, uri, data }), + value: undefined, + }); + } + + public entries(): IterableIterator<[string, IDataTransferItem]> { + return this._data.entries(); + } + + public values(): IterableIterator { + return this._data.values(); + } + + public forEach(f: (value: IDataTransferItem, key: string) => void) { + this._data.forEach(f); + } +} diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index b5c7361ce88..1c366d79a5a 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -6,7 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon, CSSIcon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -1991,5 +1991,5 @@ export enum ExternalUriOpenerPriority { * @internal */ export interface DocumentOnDropEditProvider { - provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IDataTransfer, token: CancellationToken): ProviderResult; + provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult; } diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index d113f9e513c..edbc29db56e 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -5,7 +5,7 @@ import { distinct } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { relativePath } from 'vs/base/common/resources'; @@ -93,8 +93,8 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } } - public async extractDataTransferData(dragEvent: DragEvent): Promise { - const textEditorDataTransfer: IDataTransfer = new Map(); + public async extractDataTransferData(dragEvent: DragEvent): Promise { + const textEditorDataTransfer = new VSDataTransfer(); if (!dragEvent.dataTransfer) { return textEditorDataTransfer; } @@ -103,27 +103,13 @@ export class DropIntoEditorController extends Disposable implements IEditorContr const type = item.type; if (item.kind === 'string') { const asStringValue = new Promise(resolve => item.getAsString(resolve)); - textEditorDataTransfer.set(type, { - asString: () => asStringValue, - asFile: () => undefined, - value: undefined - }); + textEditorDataTransfer.setString(type, asStringValue); } else if (item.kind === 'file') { const file = item.getAsFile(); if (file) { - textEditorDataTransfer.set(type, { - asString: () => Promise.resolve(''), - asFile: () => { - const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; - return { - name: file.name, - uri: uri, - data: async () => { - return new Uint8Array(await file.arrayBuffer()); - }, - }; - }, - value: undefined + const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; + textEditorDataTransfer.setFile(type, file.name, uri, async () => { + return new Uint8Array(await file.arrayBuffer()); }); } } @@ -135,14 +121,9 @@ export class DropIntoEditorController extends Disposable implements IEditorContr .map(input => input.resource!.toString()); if (editorData.length) { - const added: IDataTransfer = new Map(); - + const added = new VSDataTransfer(); const str = distinct(editorData).join('\n'); - added.set(Mimes.uriList.toLowerCase(), { - asFile: () => undefined, - asString: async () => str, - value: str, - }); + added.setString(Mimes.uriList.toLowerCase(), str); return added; } } @@ -157,7 +138,7 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { } - async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IDataTransfer, _token: CancellationToken): Promise { + async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise { const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); const urlListEntry = dataTransfer.get('text/uri-list'); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 16fada27b5b..ff3bd15f82a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -24,13 +24,13 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache'; -import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -892,10 +892,10 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd private readonly _proxy: ExtHostLanguageFeaturesShape, ) { } - async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: IDataTransfer, token: CancellationToken): Promise { + async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise { const request = this.dataTransfers.add(dataTransfer); try { - const dataTransferDto = await DataTransferConverter.toDataTransferDTO(dataTransfer); + const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); return await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token); } finally { request.dispose(); diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 6e96ff8161f..f0923d95701 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -13,11 +13,11 @@ import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { VSBuffer } from 'vs/base/common/buffer'; import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -203,25 +203,30 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController { readonly hasWillDrop: boolean, private readonly _proxy: ExtHostTreeViewsShape) { } - async handleDrop(dataTransfer: IDataTransfer, targetTreeItem: ITreeItem | undefined, token: CancellationToken, + async handleDrop(dataTransfer: VSDataTransfer, targetTreeItem: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise { const request = this.dataTransfersCache.add(dataTransfer); try { - return await this._proxy.$handleDrop(this.treeViewId, request.id, await DataTransferConverter.toDataTransferDTO(dataTransfer), targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles); + return await this._proxy.$handleDrop(this.treeViewId, request.id, await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer), targetTreeItem?.handle, token, operationUuid, sourceTreeId, sourceTreeItemHandles); } finally { request.dispose(); } } - async handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise { + async handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise { if (!this.hasWillDrop) { return; } - const additionalTransferItems = await this._proxy.$handleDrag(this.treeViewId, sourceTreeItemHandles, operationUuid, token); - if (!additionalTransferItems) { + const additionalDataTransferDTO = await this._proxy.$handleDrag(this.treeViewId, sourceTreeItemHandles, operationUuid, token); + if (!additionalDataTransferDTO) { return; } - return DataTransferConverter.toDataTransfer(additionalTransferItems, () => { throw new Error('not supported'); }); + + const additionalDataTransfer = new VSDataTransfer(); + additionalDataTransferDTO.items.forEach(([type, item]) => { + additionalDataTransfer.setString(type, item.asString); + }); + return additionalDataTransfer; } public resolveDropFileData(requestId: number, dataItemIndex: number): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 6d936351c35..9ae91a955be 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -44,7 +44,6 @@ import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel'; import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; -import { DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; 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'; @@ -1369,6 +1368,20 @@ export interface ExtHostDocumentsAndEditorsShape { $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void; } +export interface IDataTransferFileDTO { + readonly name: string; + readonly uri?: UriComponents; +} + +export interface DataTransferItemDTO { + readonly asString: string; + readonly fileData: IDataTransferFileDTO | undefined; +} + +export interface DataTransferDTO { + readonly items: Array<[/* type */string, DataTransferItemDTO]>; +} + export interface ExtHostTreeViewsShape { $getChildren(treeViewId: string, treeItemHandle?: string): Promise; $handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 3b6b789641a..53f5ad08081 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -34,7 +34,6 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { isCancellationError } from 'vs/base/common/errors'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; // --- adapter @@ -1754,10 +1753,10 @@ class DocumentOnDropEditAdapter { private readonly _handle: number, ) { } - async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined> { + async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise | undefined> { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); - const dataTransfer = DataTransferConverter.toDataTransfer(dataTransferDto, async (index) => { + const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer; }); @@ -2409,7 +2408,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise | undefined> { + $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise | undefined> { return this._withAdapter(handle, DocumentOnDropEditAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index b984ae6569e..fdbb535e422 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -5,11 +5,12 @@ import { localize } from 'vs/nls'; import type * as vscode from 'vscode'; +import * as types from './extHostTypes'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; +import { DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { asPromise } from 'vs/base/common/async'; @@ -18,14 +19,12 @@ import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { MarkdownString, ViewBadge } from 'vs/workbench/api/common/extHostTypeConverters'; +import { MarkdownString, ViewBadge, DataTransfer } from 'vs/workbench/api/common/extHostTypeConverters'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Command } from 'vs/editor/common/languages'; -import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; type TreeItemHandle = string; @@ -151,7 +150,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', destinationViewId))); } - const treeDataTransfer = DataTransferConverter.toDataTransfer(treeDataTransferDTO, async dataItemIndex => { + const treeDataTransfer = DataTransfer.toDataTransfer(treeDataTransferDTO, async dataItemIndex => { return (await this._proxy.$resolveDropFileData(destinationViewId, requestId, dataItemIndex)).buffer; }); if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) { @@ -160,27 +159,13 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.onDrop(treeDataTransfer, targetItemHandle, token); } - private async addAdditionalTransferItems(treeDataTransfer: IDataTransfer, treeView: ExtHostTreeView, - sourceTreeItemHandles: string[], token: CancellationToken, operationUuid?: string): Promise { + private async addAdditionalTransferItems(treeDataTransfer: vscode.DataTransfer, treeView: ExtHostTreeView, + sourceTreeItemHandles: string[], token: CancellationToken, operationUuid?: string): Promise { const existingTransferOperation = this.treeDragAndDropService.removeDragOperationTransfer(operationUuid); if (existingTransferOperation) { (await existingTransferOperation)?.forEach((value, key) => { if (value) { - const file = value.asFile(); - treeDataTransfer.set(key, { - value: value.value, - asString: value.asString, - asFile() { - if (!file) { - return undefined; - } - return { - name: file.name, - uri: file.uri, - data: async () => await file.data() - }; - }, - }); + treeDataTransfer.set(key, value); } }); } else if (operationUuid && treeView.handleDrag) { @@ -197,12 +182,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', sourceViewId))); } - const treeDataTransfer = await this.addAdditionalTransferItems(new Map(), treeView, sourceTreeItemHandles, token, operationUuid); + const treeDataTransfer = await this.addAdditionalTransferItems(new types.DataTransfer(), treeView, sourceTreeItemHandles, token, operationUuid); if (!treeDataTransfer) { return; } - return DataTransferConverter.toDataTransferDTO(treeDataTransfer); + return DataTransfer.toDataTransferDTO(treeDataTransfer); } async $hasResolve(treeViewId: string): Promise { @@ -472,7 +457,7 @@ class ExtHostTreeView extends Disposable { } } - async handleDrag(sourceTreeItemHandles: TreeItemHandle[], treeDataTransfer: IDataTransfer, token: CancellationToken): Promise { + async handleDrag(sourceTreeItemHandles: TreeItemHandle[], treeDataTransfer: vscode.DataTransfer, token: CancellationToken): Promise { const extensionTreeItems: T[] = []; for (const sourceHandle of sourceTreeItemHandles) { const extensionItem = this.getExtensionElement(sourceHandle); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 979eebec86c..94226d271f2 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -38,6 +38,7 @@ import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGro import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import type * as vscode from 'vscode'; import * as types from './extHostTypes'; +import { once } from 'vs/base/common/functional'; export namespace Command { @@ -1956,3 +1957,52 @@ export namespace ViewBadge { }; } } + +export namespace DataTransferItem { + export function toDataTransferItem(item: extHostProtocol.DataTransferItemDTO, resolveFileData: () => Promise): types.DataTransferItem { + const file = item.fileData; + if (file) { + return new class extends types.DataTransferItem { + override asFile(): vscode.DataTransferFile { + return { + name: file.name, + uri: URI.revive(file.uri), + data: once(() => resolveFileData()), + }; + } + }(''); + } else { + return new types.DataTransferItem(item.asString); + } + } +} + +export namespace DataTransfer { + export function toDataTransfer(value: extHostProtocol.DataTransferDTO, resolveFileData: (dataItemIndex: number) => Promise): types.DataTransfer { + const newDataTransfer = new types.DataTransfer(); + value.items.forEach(([type, item], index) => { + newDataTransfer.set(type, DataTransferItem.toDataTransferItem(item, () => resolveFileData(index))); + }); + return newDataTransfer; + } + + export async function toDataTransferDTO(value: vscode.DataTransfer): Promise { + const newDTO: extHostProtocol.DataTransferDTO = { items: [] }; + + const promises: Promise[] = []; + value.forEach((value, key) => { + promises.push((async () => { + const stringValue = await value.asString(); + const fileValue = value.asFile(); + newDTO.items.push([key, { + asString: stringValue, + fileData: fileValue ? { name: fileValue.name, uri: fileValue.uri } : undefined, + }]); + })()); + }); + + await Promise.all(promises); + + return newDTO; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index dc08ede3d98..76ae9232db9 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2442,7 +2442,7 @@ export class DataTransferItem { return typeof this.value === 'string' ? this.value : JSON.stringify(this.value); } - asFile(): undefined { + asFile(): undefined | vscode.DataTransferFile { return undefined; } @@ -2451,15 +2451,18 @@ export class DataTransferItem { @es5ClassCompat export class DataTransfer { - private readonly _items: Map = new Map(); + #items = new Map(); + get(mimeType: string): DataTransferItem | undefined { - return this._items.get(mimeType); + return this.#items.get(mimeType); } + set(mimeType: string, value: DataTransferItem): void { - this._items.set(mimeType, value); + this.#items.set(mimeType, value); } + forEach(callbackfn: (value: DataTransferItem, key: string) => void): void { - this._items.forEach(callbackfn); + this.#items.forEach(callbackfn); } } diff --git a/src/vs/workbench/api/common/shared/dataTransfer.ts b/src/vs/workbench/api/common/shared/dataTransfer.ts deleted file mode 100644 index a16dfe65f96..00000000000 --- a/src/vs/workbench/api/common/shared/dataTransfer.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { once } from 'vs/base/common/functional'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; - -export interface IDataTransferFileDTO { - readonly name: string; - readonly uri?: UriComponents; -} - -interface DataTransferItemDTO { - readonly asString: string; - readonly fileData: IDataTransferFileDTO | undefined; -} - -export interface DataTransferDTO { - readonly types: string[]; - readonly items: DataTransferItemDTO[]; -} - -export namespace DataTransferConverter { - export function toDataTransfer(value: DataTransferDTO, resolveFileData: (dataItemIndex: number) => Promise): IDataTransfer { - const newDataTransfer: IDataTransfer = new Map(); - value.types.forEach((type, index) => { - newDataTransfer.set(type, { - asString: async () => value.items[index].asString, - asFile: () => { - const file = value.items[index].fileData; - if (!file) { - return undefined; - } - return { - name: file.name, - uri: URI.revive(file.uri), - data: once(() => resolveFileData(index)), - }; - }, - value: undefined - }); - }); - return newDataTransfer; - } - - export async function toDataTransferDTO(value: IDataTransfer): Promise { - const newDTO: DataTransferDTO = { - types: [], - items: [] - }; - - const entries = Array.from(value.entries()); - for (const entry of entries) { - newDTO.types.push(entry[0]); - const stringValue = await entry[1].asString(); - const fileValue = entry[1].asFile(); - newDTO.items.push({ - asString: stringValue, - fileData: fileValue ? { name: fileValue.name, uri: fileValue.uri } : undefined, - }); - } - return newDTO; - } -} diff --git a/src/vs/workbench/api/common/shared/dataTransferCache.ts b/src/vs/workbench/api/common/shared/dataTransferCache.ts index 02184d68d18..6cc8a36b6e6 100644 --- a/src/vs/workbench/api/common/shared/dataTransferCache.ts +++ b/src/vs/workbench/api/common/shared/dataTransferCache.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from 'vs/base/common/buffer'; -import { IDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer, IDataTransferItem } from 'vs/base/common/dataTransfer'; export class DataTransferCache { private requestIdPool = 0; private readonly dataTransfers = new Map>(); - public add(dataTransfer: IDataTransfer): { id: number; dispose: () => void } { + public add(dataTransfer: VSDataTransfer): { id: number; dispose: () => void } { const requestId = this.requestIdPool++; this.dataTransfers.set(requestId, [...dataTransfer.values()]); return { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 63cfe74d6de..6a79fc3c337 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -10,7 +10,7 @@ import { IListDragAndDrop } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { coalesce } from 'vs/base/common/arrays'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { stringify } from 'vs/base/common/marshalling'; @@ -52,7 +52,7 @@ export class DraggedTreeItemsIdentifier { constructor(readonly identifier: string) { } } -export async function extractTreeDropData(dataTransfer: IDataTransfer): Promise> { +export async function extractTreeDropData(dataTransfer: VSDataTransfer): Promise> { const editors: IDraggedResourceEditorInput[] = []; const resourcesKey = Mimes.uriList.toLowerCase(); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 9d2c5d0dab0..50da8ebb647 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -31,7 +31,7 @@ import { isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/views'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Command } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -1361,7 +1361,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { this.dndController = controller; } - private handleDragAndLog(dndController: ITreeViewDragAndDropController, itemHandles: string[], uuid: string, dragCancellationToken: CancellationToken): Promise { + private handleDragAndLog(dndController: ITreeViewDragAndDropController, itemHandles: string[], uuid: string, dragCancellationToken: CancellationToken): Promise { return dndController.handleDrag(itemHandles, uuid, dragCancellationToken).then(additionalDataTransfer => { if (additionalDataTransfer) { const unlistedTypes: string[] = []; @@ -1501,7 +1501,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (!originalEvent.dataTransfer || !dndController) { return; } - const treeDataTransfer: IDataTransfer = new Map(); + const treeDataTransfer = new VSDataTransfer(); const uris: URI[] = []; let itemsCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => { if ((current.kind === 'string') || (current.kind === 'file')) { @@ -1521,11 +1521,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (itemsCount === 0) { // Check if there are uris to add and add them if (uris.length) { - treeDataTransfer.set(Mimes.uriList, { - asString: () => Promise.resolve(uris.map(uri => uri.toString()).join('\n')), - asFile: () => undefined, - value: undefined - }); + treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); } resolve(); } @@ -1547,11 +1543,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } if (dataValue) { const converted = this.convertKnownMimes(type, kind, dataValue); - treeDataTransfer.set(converted.type, { - asString: () => Promise.resolve(converted.value!), - asFile: () => undefined, - value: undefined - }); + treeDataTransfer.setString(converted.type, converted.value + ''); } decrementStringCount(); }); @@ -1580,7 +1572,6 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } return dndController.handleDrop(treeDataTransfer, targetNode, new CancellationTokenSource().token, willDropUuid, treeSourceInfo?.id, treeSourceInfo?.itemHandles); }); - } onDragEnd(originalEvent: DragEvent): void { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5c9c7d93a9d..ac9b39c0c21 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -27,7 +27,7 @@ import { mixin } from 'vs/base/common/objects'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.')); @@ -831,8 +831,8 @@ export interface ITreeViewDataProvider { export interface ITreeViewDragAndDropController { readonly dropMimeTypes: string[]; readonly dragMimeTypes: string[]; - handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; - handleDrop(elements: IDataTransfer, target: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; + handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; + handleDrop(elements: VSDataTransfer, target: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; } export interface IEditableData { diff --git a/src/vs/workbench/services/views/browser/treeViewsService.ts b/src/vs/workbench/services/views/browser/treeViewsService.ts index 93d426deed5..052cc97c7e0 100644 --- a/src/vs/workbench/services/views/browser/treeViewsService.ts +++ b/src/vs/workbench/services/views/browser/treeViewsService.ts @@ -5,10 +5,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDataTransfer } from 'vs/base/common/dataTransfer'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { ITreeItem } from 'vs/workbench/common/views'; import { ITreeViewsService as ITreeViewsServiceCommon, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; -export interface ITreeViewsService extends ITreeViewsServiceCommon { } +export interface ITreeViewsService extends ITreeViewsServiceCommon { } export const ITreeViewsService = createDecorator('treeViewsService'); registerSingleton(ITreeViewsService, TreeviewsService); From e2816721e14e8e69affdd0334690e66bd7266e64 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 23 May 2022 16:50:27 -0700 Subject: [PATCH 230/350] Fix scrolling to happen on execution too --- .../browser/interactive.contribution.ts | 8 +- .../interactive/browser/interactiveEditor.ts | 132 +++--------------- .../contrib/notebook/common/notebookCommon.ts | 3 +- 3 files changed, 26 insertions(+), 117 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index ddd9a857eaa..b276b2a4771 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -49,7 +49,7 @@ import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbe import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -715,14 +715,14 @@ registerThemingParticipant((theme) => { }); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'interactive', + id: 'notebook', order: 100, type: 'object', 'properties': { - 'interactive.alwaysScrollOnNewCell': { + [NotebookSetting.interactiveWindowAlwaysScrollOnNewCell]: { type: 'boolean', default: true, - markdownDescription: localize('interactive.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") + markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") }, } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 5093e65b993..c6e561c2c4d 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -22,7 +22,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; -import { CodeCellLayoutChangeEvent, IActiveNotebookEditorDelegate, ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; @@ -35,14 +35,13 @@ import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; -import { NotebookCellExecutionState, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IAction } from 'vs/base/common/actions'; -import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; @@ -62,11 +61,6 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; -const enum ScrollingState { - Initial = 0, - StickyToBottom = 1 -} - const INPUT_CELL_VERTICAL_PADDING = 8; const INPUT_CELL_HORIZONTAL_PADDING_RIGHT = 10; const INPUT_EDITOR_PADDING = 8; @@ -154,6 +148,12 @@ export class InteractiveEditor extends EditorPane { codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); this._register(this.#keybindingService.onDidUpdateKeybindings(this.#updateInputDecoration, this)); + this._register(this.#notebookExecutionStateService.onDidChangeCellExecution((e) => { + const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle); + if (cell && e.changed?.state) { + this.#scrollIfNecessary(cell); + } + })); } get #inputCellContainerHeight() { @@ -403,14 +403,7 @@ export class InteractiveEditor extends EditorPane { isReadOnly: true }); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidResizeOutput((cvm) => { - // Ignore resizes on anything but the last cell. - const index = this.#notebookWidget.value!.getCellIndex(cvm); - if (index === this.#notebookWidget.value!.getLength() - 1) { - // If we're already at the bottom or auto scroll is enabled, scroll to the bottom - if (this.configurationService.getValue('interactive.alwaysScrollOnNewCell') || this.#state === ScrollingState.StickyToBottom) { - this.#notebookWidget.value!.scrollToBottom(); - } - } + this.#scrollIfNecessary(cvm); })); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); this.#widgetDisposableStore.add(model.notebook.onDidChangeContent(() => { @@ -468,10 +461,6 @@ export class InteractiveEditor extends EditorPane { } })); - if (this.#notebookWidget.value?.hasModel()) { - this.#registerExecutionScrollListener(this.#notebookWidget.value); - } - const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this.#contextKeyService); if (input.resource && input.historyService.has(input.resource)) { cursorAtBoundaryContext.set('top'); @@ -521,107 +510,26 @@ export class InteractiveEditor extends EditorPane { } } - #lastCell: ICellViewModel | undefined = undefined; - #lastCellDisposable = new DisposableStore(); - #state: ScrollingState = ScrollingState.Initial; - - #cellAtBottom(widget: IActiveNotebookEditorDelegate, cell: ICellViewModel): boolean { - const visibleRanges = widget.visibleRanges; - const cellIndex = widget.getCellIndex(cell); - if (cellIndex === Math.max(...visibleRanges.map(range => range.end))) { + #cellAtBottom(cell: ICellViewModel): boolean { + const visibleRanges = this.#notebookWidget.value?.visibleRanges || []; + const cellIndex = this.#notebookWidget.value?.getCellIndex(cell); + if (cellIndex === Math.max(...visibleRanges.map(range => range.end - 1))) { return true; } return false; } - /** - * - Init state: 0 - * - Will cell insertion: check if the last cell is at the bottom, false, stay 0 - * if true, state 1 (ready for auto reveal) - * - receive a scroll event (scroll even already happened). If the last cell is at bottom, false, 0, true, state 1 - * - height change of the last cell, if state 0, do nothing, if state 1, scroll the last cell fully into view - */ - #registerExecutionScrollListener(widget: NotebookEditorWidget & IActiveNotebookEditorDelegate) { - this.#widgetDisposableStore.add(widget.textModel.onWillAddRemoveCells(e => { - const lastViewCell = widget.cellAt(widget.getLength() - 1); - - // check if the last cell is at the bottom - if (lastViewCell && this.#cellAtBottom(widget, lastViewCell)) { - this.#state = ScrollingState.StickyToBottom; - } else { - this.#state = ScrollingState.Initial; + #scrollIfNecessary(cvm: ICellViewModel) { + const index = this.#notebookWidget.value!.getCellIndex(cvm); + if (index === this.#notebookWidget.value!.getLength() - 1) { + // If we're already at the bottom or auto scroll is enabled, scroll to the bottom + if (this.configurationService.getValue(NotebookSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) { + this.#notebookWidget.value!.scrollToBottom(); } - })); - - this.#widgetDisposableStore.add(widget.onDidScroll(() => { - const lastViewCell = widget.cellAt(widget.getLength() - 1); - - // check if the last cell is at the bottom - if (lastViewCell && this.#cellAtBottom(widget, lastViewCell)) { - this.#state = ScrollingState.StickyToBottom; - } else { - this.#state = ScrollingState.Initial; - } - })); - - this.#widgetDisposableStore.add(widget.textModel.onDidChangeContent(e => { - for (let i = 0; i < e.rawEvents.length; i++) { - const event = e.rawEvents[i]; - - if (event.kind === NotebookCellsChangeType.ModelChange && this.#notebookWidget.value?.hasModel()) { - const lastViewCell = this.#notebookWidget.value.cellAt(this.#notebookWidget.value.getLength() - 1); - if (lastViewCell !== this.#lastCell) { - this.#lastCellDisposable.clear(); - this.#lastCell = lastViewCell; - this.#registerListenerForCell(); - } - } - } - })); - } - - #registerListenerForCell() { - if (!this.#lastCell) { - return; } - - this.#lastCellDisposable.add(this.#lastCell.onDidChangeLayout((e) => { - if (e.totalHeight === undefined) { - // not cell height change - return; - } - - if (!this.#notebookWidget.value) { - return; - } - - if (this.#lastCell instanceof CodeCellViewModel && (e as CodeCellLayoutChangeEvent).outputHeight === undefined && !this.#notebookWidget.value.isScrolledToBottom()) { - return; - } - - if (this.#state !== ScrollingState.StickyToBottom) { - return; - } - - if (this.#lastCell) { - const runState = this.#notebookExecutionStateService.getCellExecution(this.#lastCell.uri)?.state; - if (runState === NotebookCellExecutionState.Executing) { - return; - } - - } - - // scroll to bottom - // postpone to next tick as the list view might not process the output height change yet - // e.g., when we register this listener later than the list view - this.#lastCellDisposable.add(DOM.scheduleAtNextAnimationFrame(() => { - if (this.#state === ScrollingState.StickyToBottom) { - this.#notebookWidget.value!.scrollToBottom(); - } - })); - })); } + #syncWithKernel() { const notebook = this.#notebookWidget.value?.textModel; const textModel = this.#codeEditorWidget.getModel(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 40b6b6a2074..e678891f59a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -915,7 +915,8 @@ export const NotebookSetting = { interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode', outputLineHeight: 'notebook.outputLineHeight', outputFontSize: 'notebook.outputFontSize', - outputFontFamily: 'notebook.outputFontFamily' + outputFontFamily: 'notebook.outputFontFamily', + interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell' } as const; export const enum CellStatusbarAlignment { From 6a9108155f39539c6bd0a2f74421a2d541bdec19 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 23 May 2022 16:57:06 -0700 Subject: [PATCH 231/350] Finalize some interactive contribution points (#150234) Follow up on #149271 Finalizing these two contributions based on https://github.com/microsoft/vscode/pull/149767#issuecomment-1135164600 We're leaving the `notebook/cell/executePrimary` point for now as we don't plan to finalize it --- src/vs/workbench/services/actions/common/menusExtensionPoint.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 7e06c120f95..216840ff6a1 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -191,13 +191,11 @@ const apiMenus: IAPIMenu[] = [ key: 'interactive/toolbar', id: MenuId.InteractiveToolbar, description: localize('interactive.toolbar', "The contributed interactive toolbar menu"), - proposed: 'notebookEditor' }, { key: 'interactive/cell/title', id: MenuId.InteractiveCellTitle, description: localize('interactive.cell.title', "The contributed interactive cell title menu"), - proposed: 'notebookEditor' }, { key: 'testing/item/context', From 1c7991f1e8d3794a0688580bdeee469f88571917 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 23 May 2022 17:07:15 -0700 Subject: [PATCH 232/350] Markdown ignoreLink should apply to paths in header links (#150223) This change lets you ignore all links to a given file, even if those links include a header. For example: `[text](/path/to/file#some-header)` can be ignored using `/path/to/file` --- .../src/languageFeatures/diagnostics.ts | 2 +- .../src/test/diagnostic.test.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 89e6b29f240..10547f2000e 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -494,7 +494,7 @@ export class DiagnosticComputer { if (fragmentLinks.length) { const toc = await TableOfContents.create(this.engine, hrefDoc); for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.text)) { + if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) { const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.text)); } diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index 1490df29279..3d0ffd06a6e 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -256,4 +256,16 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); } }); + + test('ignoreLinks should support ignore header links if file is ignored', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](/doc2.md#no-such)`, + )); + const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); + + const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); + const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); }); From 5d8bd237564650c7257b65b6839597b7150ffe1a Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Mon, 23 May 2022 17:25:00 -0700 Subject: [PATCH 233/350] Add a test --- .../interactiveWindow.test.ts | 65 +++++++++++++++++++ .../src/singlefolder-tests/notebook.test.ts | 4 +- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts new file mode 100644 index 00000000000..eafc18804fd --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.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 * as assert from 'assert'; +import 'mocha'; +import * as vscode from 'vscode'; +import { disposeAll } from '../utils'; +import { Kernel, saveAllFilesAndCloseAll } from './notebook.test'; + +export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor }; + +async function createInteractiveWindow(kernel: Kernel) { + const { notebookEditor } = (await vscode.commands.executeCommand( + 'interactive.open', + // Keep focus on the owning file if there is one + { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false }, + undefined, + kernel.controller.id, + undefined + )) as unknown as INativeInteractiveWindow; + + return notebookEditor; +} + + +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Interactive Window', function () { + + const testDisposables: vscode.Disposable[] = []; + let defaultKernel: Kernel; + + setup(async function () { + // there should be ONE default kernel in this suite + defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); + testDisposables.push(defaultKernel.controller); + await saveAllFilesAndCloseAll(); + }); + + teardown(async function () { + disposeAll(testDisposables); + testDisposables.length = 0; + await saveAllFilesAndCloseAll(); + }); + + test('Can open an interactive window', async () => { + assert.ok(vscode.workspace.workspaceFolders); + const notebookEditor = await createInteractiveWindow(defaultKernel); + assert.ok(notebookEditor); + + // Try adding a cell and running it. + const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print foo', 'typescript'); + const edit = vscode.NotebookEdit.insertCells(0, [cell]); + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.set(notebookEditor.notebook.uri, [edit]); + await vscode.workspace.applyEdit(workspaceEdit); + + assert.strictEqual(notebookEditor.notebook.cellCount, 1); + assert.strictEqual(notebookEditor.notebook.cellAt(0).kind, vscode.NotebookCellKind.Code); + + await vscode.commands.executeCommand('notebook.execute'); + assert.strictEqual(notebookEditor.notebook.cellAt(0).outputs.length, 1, 'should execute'); + + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 462e3113698..b68eb587922 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -18,7 +18,7 @@ async function openRandomNotebookDocument() { return vscode.workspace.openNotebookDocument(uri); } -async function saveAllFilesAndCloseAll() { +export async function saveAllFilesAndCloseAll() { await saveAllEditors(); await closeAllEditors(); } @@ -29,7 +29,7 @@ async function withEvent(event: vscode.Event, callback: (e: Promise) => } -class Kernel { +export class Kernel { readonly controller: vscode.NotebookController; From 11d269da26315e9469fa86b77ae18a6f3c70c3c2 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 23 May 2022 17:27:21 -0700 Subject: [PATCH 234/350] update tests --- .../notebook/browser/notebookKernelServiceImpl.ts | 4 ++-- .../browser/notebookExecutionStateService.test.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 8466386bbdb..ab92e2777bd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, ISelectedNotebooksChangeEvent, INotebookKernelMatchResult, INotebookKernelService, INotebookTextModelLike, ISourceAction } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { LRUCache, ResourceMap } from 'vs/base/common/map'; @@ -164,7 +164,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel override dispose() { this._kernels.clear(); - this._sourceActions.forEach(a => a[1].dispose()); + dispose(this._sourceActions.map(a => a[1])); super.dispose(); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 887a4a05443..0b05037e51e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -10,6 +10,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -47,6 +48,16 @@ suite('NotebookExecutionStateService', () => { } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); + kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); instantiationService.set(INotebookExecutionService, instantiationService.createInstance(NotebookExecutionService)); From 568d036b38b5a8925854f71726de4831d23177b8 Mon Sep 17 00:00:00 2001 From: rebornix Date: Mon, 23 May 2022 17:50:03 -0700 Subject: [PATCH 235/350] shim for menu service in more tests. --- .../test/browser/notebookExecutionService.test.ts | 11 +++++++++++ .../test/browser/notebookKernelService.test.ts | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index b0a288f1e8e..e249f73080a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { assertThrowsAsync } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { insertCellAtIndex } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -42,6 +43,16 @@ suite('NotebookExecutionService', () => { override getNotebookTextModels() { return []; } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); + kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index b1ebabc1db2..1b03a84c623 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -16,6 +16,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { DisposableStore } from 'vs/base/common/lifecycle'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; suite('NotebookKernelService', () => { @@ -37,6 +38,15 @@ suite('NotebookKernelService', () => { override onWillRemoveNotebookDocument = Event.None; override getNotebookTextModels() { return []; } }); + instantiationService.stub(IMenuService, new class extends mock() { + override createMenu() { + return new class extends mock() { + override onDidChange = Event.None; + override getActions() { return []; } + override dispose() { } + }; + } + }); kernelService = instantiationService.createInstance(NotebookKernelService); instantiationService.set(INotebookKernelService, kernelService); }); From 300060066d4c63d558a75280f55ff9bab8e87ccb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 23 May 2022 18:01:54 -0700 Subject: [PATCH 236/350] Show watch value in expression tooltip (#150237) Fixes #150081 --- .../contrib/debug/browser/watchExpressionsView.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 231269cfc63..05642d306ff 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -274,7 +274,16 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { const text = typeof expression.value === 'string' ? `${expression.name}:` : expression.name; - data.label.set(text, highlights, expression.type ? expression.type : expression.value); + let title: string; + if (expression.type) { + title = expression.type === expression.value ? + expression.type : + `${expression.type}: ${expression.value}`; + } else { + title = expression.value; + } + + data.label.set(text, highlights, title); renderExpressionValue(expression, data.value, { showChanged: true, maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET, From 6099f88ce75a807021ebe5c56df953be0e52ac93 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 24 May 2022 09:15:40 +0200 Subject: [PATCH 237/350] fine tune message (#150251) --- .../workbench/contrib/extensions/browser/extensionsActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 7032389c964..ee17618c958 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2233,7 +2233,7 @@ export class ExtensionStatusAction extends ExtensionAction { if (this.extension.deprecationInfo) { if (this.extension.deprecationInfo.extension) { const link = `[${this.extension.deprecationInfo.extension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.extension.id]))}`)})`; - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension has been deprecated. Use {0} extension instead.", link)) }, true); + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension has been deprecated. Use {0} instead.", link)) }, true); } else if (this.extension.deprecationInfo.settings) { const link = `[${localize('settings', "settings")}](${URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' ')]))}`)})`; this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated and has become a native feature in VS Code. Configure these {0} instead.", link)) }, true); From ebcbd3d6368442a6ed40872e410d5d8090c56f5b Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 09:38:47 +0200 Subject: [PATCH 238/350] well defined order for command center bwd/fwd buttons This isn't https://github.com/microsoft/vscode/issues/150208 but was seen in its screen capture --- src/vs/workbench/browser/parts/editor/editor.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 8e0c175392e..1d6605352df 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -314,8 +314,8 @@ if (isMacintosh) { }); } -MenuRegistry.appendMenuItem(MenuId.TitleMenu, { command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); -MenuRegistry.appendMenuItem(MenuId.TitleMenu, { command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); +MenuRegistry.appendMenuItem(MenuId.TitleMenu, { order: 1, command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); +MenuRegistry.appendMenuItem(MenuId.TitleMenu, { order: 2, command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); // Empty Editor Group Toolbar MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); From faa736ed0ef26b9a0bd3f74e11307e9b2b810636 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 24 May 2022 12:22:02 +0200 Subject: [PATCH 239/350] Fixes #150177: Extract `LinePartMetadata` to a separate file (#150259) --- .../common/viewLayout/lineDecorations.ts | 2 +- src/vs/editor/common/viewLayout/linePart.ts | 36 +++++++++++++++++++ .../common/viewLayout/viewLineRenderer.ts | 33 +---------------- 3 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 src/vs/editor/common/viewLayout/linePart.ts diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index 36519fa2875..381e8cfb4b1 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -5,8 +5,8 @@ import * as strings from 'vs/base/common/strings'; import { Constants } from 'vs/base/common/uint'; +import { LinePartMetadata } from 'vs/editor/common/viewLayout/linePart'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; -import { LinePartMetadata } from 'vs/editor/common/viewLayout/viewLineRenderer'; export class LineDecoration { _lineDecorationBrand: void = undefined; diff --git a/src/vs/editor/common/viewLayout/linePart.ts b/src/vs/editor/common/viewLayout/linePart.ts new file mode 100644 index 00000000000..a179915fca6 --- /dev/null +++ b/src/vs/editor/common/viewLayout/linePart.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. + *--------------------------------------------------------------------------------------------*/ + +export const enum LinePartMetadata { + IS_WHITESPACE = 1, + PSEUDO_BEFORE = 2, + PSEUDO_AFTER = 4, + + IS_WHITESPACE_MASK = 0b001, + PSEUDO_BEFORE_MASK = 0b010, + PSEUDO_AFTER_MASK = 0b100, +} + +export class LinePart { + _linePartBrand: void = undefined; + + constructor( + /** + * last char index of this token (not inclusive). + */ + public readonly endIndex: number, + public readonly type: string, + public readonly metadata: number, + public readonly containsRTL: boolean + ) { } + + public isWhitespace(): boolean { + return (this.metadata & LinePartMetadata.IS_WHITESPACE_MASK ? true : false); + } + + public isPseudoAfter(): boolean { + return (this.metadata & LinePartMetadata.PSEUDO_AFTER_MASK ? true : false); + } +} diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 1bb33b6cc48..0dc97db2ede 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -9,6 +9,7 @@ import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecorationType } from 'vs/editor/common/viewModel'; +import { LinePart, LinePartMetadata } from 'vs/editor/common/viewLayout/linePart'; export const enum RenderWhitespace { None = 0, @@ -18,38 +19,6 @@ export const enum RenderWhitespace { All = 4 } -export const enum LinePartMetadata { - IS_WHITESPACE = 1, - PSEUDO_BEFORE = 2, - PSEUDO_AFTER = 4, - - IS_WHITESPACE_MASK = 0b001, - PSEUDO_BEFORE_MASK = 0b010, - PSEUDO_AFTER_MASK = 0b100, -} - -class LinePart { - _linePartBrand: void = undefined; - - constructor( - /** - * last char index of this token (not inclusive). - */ - public readonly endIndex: number, - public readonly type: string, - public readonly metadata: number, - public readonly containsRTL: boolean - ) { } - - public isWhitespace(): boolean { - return (this.metadata & LinePartMetadata.IS_WHITESPACE_MASK ? true : false); - } - - public isPseudoAfter(): boolean { - return (this.metadata & LinePartMetadata.PSEUDO_AFTER_MASK ? true : false); - } -} - export class LineRange { /** * Zero-based offset on which the range starts, inclusive. From 9ef07308ea7885dcfb3f21076dc09dc9884a5fec Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 24 May 2022 12:52:19 +0200 Subject: [PATCH 240/350] Finalizes inline completions --- .../workbench/api/common/extHost.api.impl.ts | 1 - .../actions/common/menusExtensionPoint.ts | 2 +- .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 149 ++++++++++++++++ .../vscode.proposed.inlineCompletions.d.ts | 162 ------------------ 5 files changed, 150 insertions(+), 165 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.inlineCompletions.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 1cac3df2253..364272a3ecd 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -524,7 +524,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.registerCompletionItemProvider(extension, checkSelector(selector), provider, triggerCharacters); }, registerInlineCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable { - checkProposedApiEnabled(extension, 'inlineCompletions'); if (provider.handleDidShowCompletionItem) { checkProposedApiEnabled(extension, 'inlineCompletionsAdditions'); } diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 19955341627..36560ff0d77 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -254,7 +254,7 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.InlineCompletionsActions, description: localize('inlineCompletions.actions', "The actions shown when hovering on an inline completion"), supportsSubmenus: false, - proposed: 'inlineCompletions' + proposed: 'inlineCompletionsAdditions' }, ]; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index d5ded005d72..99311836683 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -26,7 +26,6 @@ export const allApiProposals = Object.freeze({ findTextInFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts', - inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts', inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts', inlineCompletionsNew: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts', ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 3d7b87599f1..8b4e5197a27 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -12588,6 +12588,18 @@ declare module 'vscode' { */ export function setLanguageConfiguration(language: string, configuration: LanguageConfiguration): Disposable; + /** + * Registers an inline completion provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inline completion provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; } /** @@ -15936,6 +15948,143 @@ declare module 'vscode' { */ close(tabGroup: TabGroup | readonly TabGroup[], preserveFocus?: boolean): Thenable; } + + /** + * The inline completion item provider interface defines the contract between extensions and + * the inline completion feature. + * + * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. + */ + export interface InlineCompletionItemProvider { + + /** + * Provides inline completion items for the given position and document. + * If inline completions are enabled, this method will be called whenever the user stopped typing. + * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. + * In that case, all available inline completions should be returned. + * `context.triggerKind` can be used to distinguish between these scenarios. + * + * @param document The document inline completions are requested for. + * @param position The position inline completions are requested for. + * @param context A context object with additional information. + * @param token A cancellation token. + * @return An array of completion items or a thenable that resolves to an array of completion items. + */ + provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + } + + /** + * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented + * in the editor. + */ + export class InlineCompletionList { + /** + * The inline completion items. + */ + items: InlineCompletionItem[]; + + /** + * Creates a new list of inline completion items. + */ + constructor(items: InlineCompletionItem[]); + } + + /** + * Provides information about the context in which an inline completion was requested. + */ + export interface InlineCompletionContext { + /** + * Describes how the inline completion was triggered. + */ + readonly triggerKind: InlineCompletionTriggerKind; + + /** + * Provides information about the currently selected item in the autocomplete widget if it is visible. + * + * If set, provided inline completions must extend the text of the selected item + * and use the same range, otherwise they are not shown as preview. + * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, + * the inline completion must also replace `.` and start with `.log`, for example `.log()`. + * + * Inline completion providers are requested again whenever the selected item changes. + */ + readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; + } + + /** + * Describes the currently selected completion item. + */ + export interface SelectedCompletionInfo { + /** + * The range that will be replaced if this completion item is accepted. + */ + readonly range: Range; + + /** + * The text the range will be replaced with if this completion is accepted. + */ + readonly text: string; + } + + /** + * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. + */ + export enum InlineCompletionTriggerKind { + /** + * Completion was triggered explicitly by a user gesture. + * Return multiple completion items to enable cycling through them. + */ + Invoke = 0, + + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 1, + } + + /** + * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. + * + * @see {@link InlineCompletionItemProvider.provideInlineCompletionItems} + */ + export class InlineCompletionItem { + /** + * The text to replace the range with. Must be set. + * Is used both for the preview and the accept operation. + */ + insertText: string | SnippetString; + + /** + * A text that is used to decide if this inline completion should be shown. When `falsy` + * the {@link InlineCompletionItem.insertText} is used. + * + * An inline completion is shown if the text to replace is a prefix of the filter text. + */ + filterText?: string; + + /** + * The range to replace. + * Must begin and end on the same line. + * + * Prefer replacements over insertions to provide a better experience when the user deletes typed text. + */ + range?: Range; + + /** + * An optional {@link Command} that is executed *after* inserting this completion. + */ + command?: Command; + + /** + * Creates a new inline completion item. + * + * @param insertText The text to replace the range with. + * @param range The range to replace. If not set, the word at the requested position will be used. + * @param command An optional {@link Command} that is executed *after* inserting this completion. + */ + constructor(insertText: string | SnippetString, range?: Range, command?: Command); + } } /** diff --git a/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts deleted file mode 100644 index c070547c967..00000000000 --- a/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts +++ /dev/null @@ -1,162 +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/124024 @hediet @alexdima - - export namespace languages { - - /** - * Registers an inline completion provider. - * - * Multiple providers can be registered for a language. In that case providers are asked in - * parallel and the results are merged. A failing provider (rejected promise or exception) will - * not cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An inline completion provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; - } - - /** - * The inline completion item provider interface defines the contract between extensions and - * the inline completion feature. - * - * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. - */ - export interface InlineCompletionItemProvider { - - /** - * Provides inline completion items for the given position and document. - * If inline completions are enabled, this method will be called whenever the user stopped typing. - * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. - * In that case, all available inline completions should be returned. - * `context.triggerKind` can be used to distinguish between these scenarios. - * - * @param document The document inline completions are requested for. - * @param position The position inline completions are requested for. - * @param context A context object with additional information. - * @param token A cancellation token. - * @return An array of completion items or a thenable that resolves to an array of completion items. - */ - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; - } - - /** - * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented - * in the editor. - */ - export class InlineCompletionList { - /** - * The inline completion items. - */ - items: InlineCompletionItem[]; - - /** - * Creates a new list of inline completion items. - */ - constructor(items: InlineCompletionItem[]); - } - - /** - * Provides information about the context in which an inline completion was requested. - */ - export interface InlineCompletionContext { - /** - * Describes how the inline completion was triggered. - */ - readonly triggerKind: InlineCompletionTriggerKind; - - /** - * Provides information about the currently selected item in the autocomplete widget if it is visible. - * - * If set, provided inline completions must extend the text of the selected item - * and use the same range, otherwise they are not shown as preview. - * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, - * the inline completion must also replace `.` and start with `.log`, for example `.log()`. - * - * Inline completion providers are requested again whenever the selected item changes. - */ - readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; - } - - /** - * Describes the currently selected completion item. - */ - export interface SelectedCompletionInfo { - /** - * The range that will be replaced if this completion item is accepted. - */ - readonly range: Range; - - /** - * The text the range will be replaced with if this completion is accepted. - */ - readonly text: string; - } - - /** - * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. - */ - export enum InlineCompletionTriggerKind { - /** - * Completion was triggered explicitly by a user gesture. - * Return multiple completion items to enable cycling through them. - */ - Invoke = 0, - - /** - * Completion was triggered automatically while editing. - * It is sufficient to return a single completion item in this case. - */ - Automatic = 1, - } - - /** - * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. - * - * @see {@link InlineCompletionItemProvider.provideInlineCompletionItems} - */ - export class InlineCompletionItem { - /** - * The text to replace the range with. Must be set. - * Is used both for the preview and the accept operation. - */ - insertText: string | SnippetString; - - /** - * A text that is used to decide if this inline completion should be shown. When `falsy` - * the {@link InlineCompletionItem.insertText} is used. - * - * An inline completion is shown if the text to replace is a prefix of the filter text. - */ - filterText?: string; - - /** - * The range to replace. - * Must begin and end on the same line. - * - * Prefer replacements over insertions to provide a better experience when the user deletes typed text. - */ - range?: Range; - - /** - * An optional {@link Command} that is executed *after* inserting this completion. - */ - command?: Command; - - /** - * Creates a new inline completion item. - * - * @param insertText The text to replace the range with. - * @param range The range to replace. If not set, the word at the requested position will be used. - * @param command An optional {@link Command} that is executed *after* inserting this completion. - */ - constructor(insertText: string | SnippetString, range?: Range, command?: Command); - } -} From 957a9dc194c23b193fd1d613d61afcaa960dd730 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 12:53:25 +0200 Subject: [PATCH 241/350] rename `titleMenu` to `commandCenter` This changes hows types and variables are called but also theme colors and the setting name are changing --- src/vs/platform/actions/common/actions.ts | 2 +- .../browser/actions/quickAccessActions.ts | 2 +- src/vs/workbench/browser/layout.ts | 2 +- .../parts/editor/editor.contribution.ts | 4 +- ...MenuControl.ts => commandCenterControl.ts} | 38 +++++++++---------- .../parts/titlebar/media/titlebarpart.css | 32 ++++++++-------- .../browser/parts/titlebar/titlebarPart.ts | 20 +++++----- .../browser/workbench.contribution.ts | 4 +- .../services/title/common/titleService.ts | 2 +- 9 files changed, 53 insertions(+), 53 deletions(-) rename src/vs/workbench/browser/parts/titlebar/{titleMenuControl.ts => commandCenterControl.ts} (87%) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index beff01998fa..881043379fc 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -67,7 +67,7 @@ export class MenuId { static readonly ExplorerContext = new MenuId('ExplorerContext'); static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); - static readonly TitleMenu = new MenuId('TitleMenu'); + static readonly CommandCenter = new MenuId('CommandCenter'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); static readonly LayoutControlMenu = new MenuId('LayoutControlMenu'); static readonly MenubarMainMenu = new MenuId('MenubarMainMenu'); diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index 8c08c8e8057..6cda33c142c 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -141,7 +141,7 @@ registerAction2(class QuickAccessAction extends Action2 { }, f1: true, menu: { - id: MenuId.TitleMenu, + id: MenuId.CommandCenter, order: 100 } }); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 2740f42fde9..10a0a5e03ec 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -148,7 +148,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi let quickPickTop = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { top = this.getPart(Parts.TITLEBAR_PART).maximumHeight; - quickPickTop = this.titleService.titleMenuVisible ? 0 : top; + quickPickTop = this.titleService.isCommandCenterVisible ? 0 : top; } return { top, quickPickTop }; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 1d6605352df..ef757abd139 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -314,8 +314,8 @@ if (isMacintosh) { }); } -MenuRegistry.appendMenuItem(MenuId.TitleMenu, { order: 1, command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); -MenuRegistry.appendMenuItem(MenuId.TitleMenu, { order: 2, command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); +MenuRegistry.appendMenuItem(MenuId.CommandCenter, { order: 1, command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); +MenuRegistry.appendMenuItem(MenuId.CommandCenter, { order: 2, command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); // Empty Editor Group Toolbar MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); diff --git a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts similarity index 87% rename from src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts rename to src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 63dd190ea82..c0728f1c334 100644 --- a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -27,7 +27,7 @@ import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; import { MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_FOREGROUND, TITLE_BAR_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -export class TitleMenuControl { +export class CommandCenterControl { private readonly _disposables = new DisposableStore(); @@ -47,7 +47,7 @@ export class TitleMenuControl { @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, ) { - this.element.classList.add('title-menu'); + this.element.classList.add('command-center'); const hoverDelegate = new class implements IHoverDelegate { @@ -134,16 +134,16 @@ export class TitleMenuControl { return createActionViewItem(instantiationService, action, { hoverDelegate }); } }); - const titleMenu = this._disposables.add(menuService.createMenu(MenuId.TitleMenu, contextKeyService)); - const titleMenuDisposables = this._disposables.add(new DisposableStore()); - const updateTitleMenu = () => { - titleMenuDisposables.clear(); + const menu = this._disposables.add(menuService.createMenu(MenuId.CommandCenter, contextKeyService)); + const menuDisposables = this._disposables.add(new DisposableStore()); + const menuUpdater = () => { + menuDisposables.clear(); const actions: IAction[] = []; - titleMenuDisposables.add(createAndFillInContextMenuActions(titleMenu, undefined, actions)); + menuDisposables.add(createAndFillInContextMenuActions(menu, undefined, actions)); titleToolbar.setActions(actions); }; - updateTitleMenu(); - this._disposables.add(titleMenu.onDidChange(updateTitleMenu)); + menuUpdater(); + this._disposables.add(menu.onDidChange(menuUpdater)); this._disposables.add(quickInputService.onShow(this._setVisibility.bind(this, false))); this._disposables.add(quickInputService.onHide(this._setVisibility.bind(this, true))); } @@ -162,34 +162,34 @@ export class TitleMenuControl { // foreground (inactive and active) colors.registerColor( - 'titleMenu.foreground', + 'commandCenter.foreground', { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, - localize('titleMenu-foreground', "Foreground color of the title menu"), + localize('commandCenter-foreground', "Foreground color of the command center"), false ); colors.registerColor( - 'titleMenu.activeForeground', + 'commandCenter.activeForeground', { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, - localize('titleMenu-activeForeground', "Active foreground color of the title menu"), + localize('commandCenter-activeForeground', "Active foreground color of the command center"), false ); // background (inactive and active) colors.registerColor( - 'titleMenu.background', + 'commandCenter.background', { dark: null, hcDark: null, light: null, hcLight: null }, - localize('titleMenu-background', "Background color of the title menu"), + localize('commandCenter-background', "Background color of the command center"), false ); const activeBackground = colors.registerColor( - 'titleMenu.activeBackground', + 'commandCenter.activeBackground', { dark: MENUBAR_SELECTION_BACKGROUND, hcDark: MENUBAR_SELECTION_BACKGROUND, light: MENUBAR_SELECTION_BACKGROUND, hcLight: MENUBAR_SELECTION_BACKGROUND }, - localize('titleMenu-activeBackground', "Active background color of the title menu"), + localize('commandCenter-activeBackground', "Active background color of the command center"), false ); // border: defaults to active background colors.registerColor( - 'titleMenu.border', + 'commandCenter.border', { dark: activeBackground, hcDark: colors.inputBorder, light: activeBackground, hcLight: colors.inputBorder }, - localize('titleMenu-border', "Border color of the title menu"), + localize('commandCenter-border', "Border color of the command center"), false ); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 1a1d9490d5c..77abc653614 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -80,21 +80,21 @@ } /* Window Title Menu */ -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center { z-index: 2550; -webkit-app-region: no-drag; padding: 0 8px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu.hide { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center.hide { display: none; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen { display: flex; - color: var(--vscode-titleMenu-foreground); - background-color: var(--vscode-titleMenu-background); - border: 1px solid var(--vscode-titleMenu-border); + color: var(--vscode-commandCenter-foreground); + background-color: var(--vscode-commandCenter-background); + border: 1px solid var(--vscode-commandCenter-border); border-radius: 5px; height: 20px; line-height: 18px; @@ -106,18 +106,18 @@ overflow: hidden; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen:HOVER { - color: var(--vscode-titleMenu-activeForeground); - background-color: var(--vscode-titleMenu-activeBackground); +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen:HOVER { + color: var(--vscode-commandCenter-activeForeground); + background-color: var(--vscode-commandCenter-activeBackground); line-height: 18px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu:HOVER .quickopen .action-label { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .quickopen .action-label { background-color: transparent !important; outline-color: transparent !important; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen>.action-label.search { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search { display: inline-flex; text-align: center; font-size: 12px; @@ -127,18 +127,18 @@ width: calc(100% - 19px); } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen>.action-label.search>.search-icon { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-icon { font-size: 14px; opacity: .8; padding: 0 3px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen>.action-label.search>.search-label { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-label { overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu .action-item.quickopen>.all-options>.action-label { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.all-options>.action-label { text-align: center; font-size: 12px; width: 16px; @@ -147,8 +147,8 @@ padding-right: 0; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu:HOVER .action-item.quickopen>.all-options>.action-label { - border-color: var(--vscode-titleMenu-border); +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .action-item.quickopen>.all-options>.action-label { + border-color: var(--vscode-commandCenter-border); } /* Menubar */ diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index e35c289cb8e..e549362ba2d 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -33,11 +33,11 @@ import { Codicon } from 'vs/base/common/codicons'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; -import { TitleMenuControl } from 'vs/workbench/browser/parts/titlebar/titleMenuControl'; +import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/commandCenterControl'; export class TitlebarPart extends Part implements ITitleService { - private static readonly configTitleMenu = 'window.experimental.titleMenu'; + private static readonly configCommandCenter = 'window.experimental.commandCenter'; declare readonly _serviceBrand: undefined; @@ -102,8 +102,8 @@ export class TitlebarPart extends Part implements ITitleService { this.windowTitle.updateProperties(properties); } - get titleMenuVisible() { - return this.configurationService.getValue(TitlebarPart.configTitleMenu); + get isCommandCenterVisible() { + return this.configurationService.getValue(TitlebarPart.configCommandCenter); } private registerListeners(): void { @@ -137,7 +137,7 @@ export class TitlebarPart extends Part implements ITitleService { this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled); } - if (event.affectsConfiguration(TitlebarPart.configTitleMenu)) { + if (event.affectsConfiguration(TitlebarPart.configCommandCenter)) { this.updateTitle(); this.adjustTitleMarginToCenter(); this._onDidChangeTitleMenuVisibility.fire(); @@ -183,7 +183,7 @@ export class TitlebarPart extends Part implements ITitleService { private updateTitle(): void { this.titleDisposables.clear(); - if (!this.titleMenuVisible) { + if (!this.isCommandCenterVisible) { // Text Title this.title.innerText = this.windowTitle.value; this.titleDisposables.add(this.windowTitle.onDidChange(() => { @@ -192,10 +192,10 @@ export class TitlebarPart extends Part implements ITitleService { })); } else { // Menu Title - const titleMenu = this.instantiationService.createInstance(TitleMenuControl, this.windowTitle); - reset(this.title, titleMenu.element); - this.titleDisposables.add(titleMenu); - this.titleDisposables.add(titleMenu.onDidChangeVisibility(this.adjustTitleMarginToCenter, this)); + const commandCenter = this.instantiationService.createInstance(CommandCenterControl, this.windowTitle); + reset(this.title, commandCenter.element); + this.titleDisposables.add(commandCenter); + this.titleDisposables.add(commandCenter.onDidChangeVisibility(this.adjustTitleMarginToCenter, this)); } } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index a0adbe40cb2..1e654bb15a1 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -538,10 +538,10 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': isMacintosh ? ' \u2014 ' : ' - ', 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.") }, - 'window.experimental.titleMenu': { + 'window.experimental.commandCenter': { type: 'boolean', default: false, - description: localize('window.experimental.titleMenu', "Show window title as menu") + description: localize('window.experimental.commandCenter', "Show command launcher together with the window title.") }, 'window.menuBarVisibility': { 'type': 'string', diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index a89bb2f096a..92aca9f92cd 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -26,7 +26,7 @@ export interface ITitleService { /** * Title menu is visible */ - readonly titleMenuVisible: boolean; + readonly isCommandCenterVisible: boolean; /** * An event when the title menu is enabled/disabled From 67c79ededdfe616c32509f6b7e46f4c0758b50bb Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 12:57:38 +0200 Subject: [PATCH 242/350] clarify when/how setting is effective --- src/vs/workbench/browser/workbench.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 1e654bb15a1..36dc8265806 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -541,7 +541,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'window.experimental.commandCenter': { type: 'boolean', default: false, - description: localize('window.experimental.commandCenter', "Show command launcher together with the window title.") + markdownDescription: localize('window.experimental.commandCenter', "Show command launcher together with the window title. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.") }, 'window.menuBarVisibility': { 'type': 'string', From 753fc8a044784de7d5f36fe849a1ccfb26d417ed Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 24 May 2022 13:16:43 +0200 Subject: [PATCH 243/350] Fixes bracket fixing bug for inline completions. --- .../bracketPairsTextModelPart/bracketPairsTree/brackets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts index 280cb3b8deb..2673aeacb66 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts @@ -85,7 +85,7 @@ export class BracketTokens { findClosingTokenText(openingBracketIds: SmallImmutableSet): string | undefined { for (const [closingText, info] of this.map) { - if (info.bracketIds.intersects(openingBracketIds)) { + if (info.kind === TokenKind.ClosingBracket && info.bracketIds.intersects(openingBracketIds)) { return closingText; } } From 34a75f45d688ec88f1635670b783e805a1778db4 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 14:45:09 +0200 Subject: [PATCH 244/350] use CSS variables for colors --- .../contrib/mergeEditor/browser/media/mergeEditor.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index 0b9caf7faf8..abae99ac16c 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -56,7 +56,7 @@ } .merge-accept-gutter-marker.multi-line .background { - border: 2px solid #323232FF; + border: 2px solid var(--vscode-checkbox-border); border-right: 0; left: 8px; width: 10px; @@ -74,5 +74,5 @@ .merge-accept-gutter-marker .checkbox .accept-conflict-group.monaco-custom-toggle.monaco-checkbox { margin: 0; padding: 0; - background-color: #3C3C3CFF; + background-color: var(--vscode-checkbox-border); } From 3ab95815b5b3adb4a7029e2095f6fa1d5720d5df Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 24 May 2022 14:57:08 +0200 Subject: [PATCH 245/350] Fixes nits. --- src/vscode-dts/vscode.d.ts | 301 +++++++++++++++++++------------------ 1 file changed, 151 insertions(+), 150 deletions(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 8b4e5197a27..292d369a557 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -4498,6 +4498,144 @@ declare module 'vscode' { resolveCompletionItem?(item: T, token: CancellationToken): ProviderResult; } + + /** + * The inline completion item provider interface defines the contract between extensions and + * the inline completion feature. + * + * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. + */ + export interface InlineCompletionItemProvider { + + /** + * Provides inline completion items for the given position and document. + * If inline completions are enabled, this method will be called whenever the user stopped typing. + * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. + * In that case, all available inline completions should be returned. + * `context.triggerKind` can be used to distinguish between these scenarios. + * + * @param document The document inline completions are requested for. + * @param position The position inline completions are requested for. + * @param context A context object with additional information. + * @param token A cancellation token. + * @return An array of completion items or a thenable that resolves to an array of completion items. + */ + provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + } + + /** + * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented + * in the editor. + */ + export class InlineCompletionList { + /** + * The inline completion items. + */ + items: InlineCompletionItem[]; + + /** + * Creates a new list of inline completion items. + */ + constructor(items: InlineCompletionItem[]); + } + + /** + * Provides information about the context in which an inline completion was requested. + */ + export interface InlineCompletionContext { + /** + * Describes how the inline completion was triggered. + */ + readonly triggerKind: InlineCompletionTriggerKind; + + /** + * Provides information about the currently selected item in the autocomplete widget if it is visible. + * + * If set, provided inline completions must extend the text of the selected item + * and use the same range, otherwise they are not shown as preview. + * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, + * the inline completion must also replace `.` and start with `.log`, for example `.log()`. + * + * Inline completion providers are requested again whenever the selected item changes. + */ + readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; + } + + /** + * Describes the currently selected completion item. + */ + export interface SelectedCompletionInfo { + /** + * The range that will be replaced if this completion item is accepted. + */ + readonly range: Range; + + /** + * The text the range will be replaced with if this completion is accepted. + */ + readonly text: string; + } + + /** + * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. + */ + export enum InlineCompletionTriggerKind { + /** + * Completion was triggered explicitly by a user gesture. + * Return multiple completion items to enable cycling through them. + */ + Invoke = 0, + + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 1, + } + + /** + * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. + * + * @see {@link InlineCompletionItemProvider.provideInlineCompletionItems} + */ + export class InlineCompletionItem { + /** + * The text to replace the range with. Must be set. + * Is used both for the preview and the accept operation. + */ + insertText: string | SnippetString; + + /** + * A text that is used to decide if this inline completion should be shown. When `falsy` + * the {@link InlineCompletionItem.insertText} is used. + * + * An inline completion is shown if the text to replace is a prefix of the filter text. + */ + filterText?: string; + + /** + * The range to replace. + * Must begin and end on the same line. + * + * Prefer replacements over insertions to provide a better experience when the user deletes typed text. + */ + range?: Range; + + /** + * An optional {@link Command} that is executed *after* inserting this completion. + */ + command?: Command; + + /** + * Creates a new inline completion item. + * + * @param insertText The text to replace the range with. + * @param range The range to replace. If not set, the word at the requested position will be used. + * @param command An optional {@link Command} that is executed *after* inserting this completion. + */ + constructor(insertText: string | SnippetString, range?: Range, command?: Command); + } + /** * A document link is a range in a text document that links to an internal or external resource, like another * text document or a web site. @@ -12202,6 +12340,19 @@ declare module 'vscode' { */ export function registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable; + /** + * Registers an inline completion provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inline completion provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; + /** * Register a code action provider. * @@ -12587,19 +12738,6 @@ declare module 'vscode' { * @return A {@link Disposable} that unsets this configuration. */ export function setLanguageConfiguration(language: string, configuration: LanguageConfiguration): Disposable; - - /** - * Registers an inline completion provider. - * - * Multiple providers can be registered for a language. In that case providers are asked in - * parallel and the results are merged. A failing provider (rejected promise or exception) will - * not cause a failure of the whole operation. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An inline completion provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; } /** @@ -15948,143 +16086,6 @@ declare module 'vscode' { */ close(tabGroup: TabGroup | readonly TabGroup[], preserveFocus?: boolean): Thenable; } - - /** - * The inline completion item provider interface defines the contract between extensions and - * the inline completion feature. - * - * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. - */ - export interface InlineCompletionItemProvider { - - /** - * Provides inline completion items for the given position and document. - * If inline completions are enabled, this method will be called whenever the user stopped typing. - * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. - * In that case, all available inline completions should be returned. - * `context.triggerKind` can be used to distinguish between these scenarios. - * - * @param document The document inline completions are requested for. - * @param position The position inline completions are requested for. - * @param context A context object with additional information. - * @param token A cancellation token. - * @return An array of completion items or a thenable that resolves to an array of completion items. - */ - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; - } - - /** - * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented - * in the editor. - */ - export class InlineCompletionList { - /** - * The inline completion items. - */ - items: InlineCompletionItem[]; - - /** - * Creates a new list of inline completion items. - */ - constructor(items: InlineCompletionItem[]); - } - - /** - * Provides information about the context in which an inline completion was requested. - */ - export interface InlineCompletionContext { - /** - * Describes how the inline completion was triggered. - */ - readonly triggerKind: InlineCompletionTriggerKind; - - /** - * Provides information about the currently selected item in the autocomplete widget if it is visible. - * - * If set, provided inline completions must extend the text of the selected item - * and use the same range, otherwise they are not shown as preview. - * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, - * the inline completion must also replace `.` and start with `.log`, for example `.log()`. - * - * Inline completion providers are requested again whenever the selected item changes. - */ - readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; - } - - /** - * Describes the currently selected completion item. - */ - export interface SelectedCompletionInfo { - /** - * The range that will be replaced if this completion item is accepted. - */ - readonly range: Range; - - /** - * The text the range will be replaced with if this completion is accepted. - */ - readonly text: string; - } - - /** - * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. - */ - export enum InlineCompletionTriggerKind { - /** - * Completion was triggered explicitly by a user gesture. - * Return multiple completion items to enable cycling through them. - */ - Invoke = 0, - - /** - * Completion was triggered automatically while editing. - * It is sufficient to return a single completion item in this case. - */ - Automatic = 1, - } - - /** - * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. - * - * @see {@link InlineCompletionItemProvider.provideInlineCompletionItems} - */ - export class InlineCompletionItem { - /** - * The text to replace the range with. Must be set. - * Is used both for the preview and the accept operation. - */ - insertText: string | SnippetString; - - /** - * A text that is used to decide if this inline completion should be shown. When `falsy` - * the {@link InlineCompletionItem.insertText} is used. - * - * An inline completion is shown if the text to replace is a prefix of the filter text. - */ - filterText?: string; - - /** - * The range to replace. - * Must begin and end on the same line. - * - * Prefer replacements over insertions to provide a better experience when the user deletes typed text. - */ - range?: Range; - - /** - * An optional {@link Command} that is executed *after* inserting this completion. - */ - command?: Command; - - /** - * Creates a new inline completion item. - * - * @param insertText The text to replace the range with. - * @param range The range to replace. If not set, the word at the requested position will be used. - * @param command An optional {@link Command} that is executed *after* inserting this completion. - */ - constructor(insertText: string | SnippetString, range?: Range, command?: Command); - } } /** From 0e8b6a43b0deb31e8c23d0c3b1c23f218f2c593f Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 15:00:51 +0200 Subject: [PATCH 246/350] show correct description for input2 --- src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index f46dce24ff1..0f0ef7c4cba 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -184,7 +184,7 @@ export class MergeEditor extends EditorPane { const model = await input.resolve(); this.input1View.setModel(model, model.input1, localize('yours', 'Yours'), model.input1Detail, model.input1Description); - this.input2View.setModel(model, model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input1Description); + this.input2View.setModel(model, model.input2, localize('theirs', 'Theirs',), model.input2Detail, model.input2Description); this.inputResultView.setModel(model, model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); // TODO: Update editor options! From 3e83fa95b06af67b9e0e3bbffda3f93e9fafe090 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 24 May 2022 15:06:34 +0200 Subject: [PATCH 247/350] Fix #150266 (#150267) --- extensions/git/src/repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c7962fb1a62..d3626bc0de3 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -546,7 +546,7 @@ class DotGitWatcher implements IFileWatcher { // Ignore changes to the "index.lock" file, and watchman fsmonitor hook (https://git-scm.com/docs/githooks#_fsmonitor_watchman) cookie files. // Watchman creates a cookie file inside the git directory whenever a query is run (https://facebook.github.io/watchman/docs/cookies.html). - const filteredRootWatcher = filterEvent(rootWatcher.event, uri => !/\/\.git(\/index\.lock)?$|\/\.watchman-cookie-/.test(uri.path)); + const filteredRootWatcher = filterEvent(rootWatcher.event, uri => uri.scheme === 'file' && !/\/\.git(\/index\.lock)?$|\/\.watchman-cookie-/.test(uri.path)); this.event = anyEvent(filteredRootWatcher, this.emitter.event); repository.onDidRunGitStatus(this.updateTransientWatchers, this, this.disposables); From 903c5c6cc9a85744e16580695a0b4994431bd39c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 24 May 2022 15:50:36 +0200 Subject: [PATCH 248/350] - Remove Show Extension button (#150274) - Add Open Alternative Extension or Configure Settings button --- .../extensions/browser/extensionsActions.ts | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ee17618c958..86f03bfe8fb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -65,6 +65,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; import { fromNow } from 'vs/base/common/date'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export class PromptExtensionInstallFailureAction extends Action { @@ -250,6 +251,7 @@ export abstract class AbstractInstallAction extends ExtensionAction { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @ILabelService private readonly labelService: ILabelService, @IDialogService private readonly dialogService: IDialogService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(id, localize('install', "Install"), cssClass, false); this.update(); @@ -276,20 +278,33 @@ export abstract class AbstractInstallAction extends ExtensionAction { } if (this.extension.deprecationInfo) { + let detail = localize('deprecated message', "This extension is no longer being maintained and is deprecated."); + let action: () => Promise = async () => undefined; + const buttons = [ + localize('install anyway', "Install Anyway"), + localize('cancel', "Cancel"), + ]; + + if (this.extension.deprecationInfo.extension) { + detail = localize('deprecated with alternate extension message', "This extension has been deprecated. Use {0} instead.", this.extension.deprecationInfo.extension.displayName); + buttons.splice(1, 0, localize('Show alternate extension', "Open {0}", this.extension.deprecationInfo.extension.displayName)); + const alternateExtension = this.extension.deprecationInfo.extension; + action = () => this.extensionsWorkbenchService.getExtensions([{ id: alternateExtension.id, preRelease: alternateExtension.preRelease }], CancellationToken.None) + .then(([extension]) => this.extensionsWorkbenchService.open(extension)); + } else if (this.extension.deprecationInfo.settings) { + detail = localize('deprecated with alternate settings message', "This extension is deprecated and has become a native feature in VS Code."); + buttons.splice(1, 0, localize('configure in settings', "Configure Settings")); + const settings = this.extension.deprecationInfo.settings; + action = () => this.preferencesService.openSettings({ query: settings.map(setting => `@id:${setting}`).join(' ') }); + } + const result = await this.dialogService.show( Severity.Warning, localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), - [ - localize('install anyway', "Install Anyway"), - localize('open extension', "Open Extension"), - localize('cancel', "Cancel"), - ], - { - detail: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), - cancelId: 2, - }); + buttons, + { detail, cancelId: buttons.length - 1 }); if (result.choice === 1) { - return this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.installPreReleaseVersion }); + return action(); } if (result.choice === 2) { return; @@ -396,12 +411,13 @@ export class InstallAction extends AbstractInstallAction { @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, @IDialogService dialogService: IDialogService, + @IPreferencesService preferencesService: IPreferencesService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IWorkbenchExtensionManagementService private readonly workbenchExtensioManagementService: IWorkbenchExtensionManagementService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super(`extensions.install`, installPreReleaseVersion, InstallAction.Class, - extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService, preferencesService); this.updateLabel(); this._register(labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this._register(Event.any(userDataSyncEnablementService.onDidChangeEnablement, @@ -462,11 +478,12 @@ export class InstallAndSyncAction extends AbstractInstallAction { @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @ILabelService labelService: ILabelService, @IDialogService dialogService: IDialogService, + @IPreferencesService preferencesService: IPreferencesService, @IProductService productService: IProductService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { super('extensions.installAndSync', installPreReleaseVersion, AbstractInstallAction.Class, - extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService); + extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService, dialogService, preferencesService); this.tooltip = localize({ key: 'install everywhere tooltip', comment: ['Placeholder is the name of the product. Eg: Visual Studio Code or Visual Studio Code - Insiders'] }, "Install this extension in all your synced {0} instances", productService.nameLong); this._register(Event.any(userDataSyncEnablementService.onDidChangeEnablement, Event.filter(userDataSyncEnablementService.onDidChangeResourceEnablement, e => e[0] === SyncResource.Extensions))(() => this.update())); From 339c3a9b60594411c62ea3ed9123a0b26cf722e9 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 24 May 2022 15:51:23 +0200 Subject: [PATCH 249/350] Fixes #150176: Extract code related to encoded token attributes to a separate file (#150275) --- .../browser/controller/textAreaHandler.ts | 3 +- .../browser/viewParts/minimap/minimap.ts | 2 +- .../editor/common/encodedTokenAttributes.ts | 193 +++++++++++++++++ src/vs/editor/common/languages.ts | 197 +----------------- .../common/languages/languageConfiguration.ts | 2 +- .../editor/common/languages/nullTokenize.ts | 3 +- src/vs/editor/common/languages/supports.ts | 2 +- .../common/languages/supports/tokenization.ts | 2 +- .../common/languages/textToHtmlTokenizer.ts | 3 +- .../bracketPairsTree/tokenizer.ts | 2 +- src/vs/editor/common/model/textModelTokens.ts | 3 +- .../common/model/tokenizationTextModelPart.ts | 2 +- .../common/services/languagesRegistry.ts | 3 +- .../services/semanticTokensProviderStyling.ts | 3 +- src/vs/editor/common/tokenizationRegistry.ts | 3 +- .../common/tokenizationTextModelPart.ts | 2 +- .../common/tokens/contiguousTokensStore.ts | 3 +- src/vs/editor/common/tokens/lineTokens.ts | 3 +- .../editor/common/tokens/sparseTokensStore.ts | 3 +- .../viewModel/minimapTokensColorTracker.ts | 3 +- .../common/viewModel/viewModelDecorations.ts | 2 +- .../editor/common/viewModel/viewModelImpl.ts | 3 +- .../test/browser/lineCommentCommand.test.ts | 3 +- .../indentation/browser/indentation.ts | 3 +- .../editor/contrib/suggest/browser/suggest.ts | 7 +- .../suggest/test/browser/suggestModel.test.ts | 3 +- src/vs/editor/standalone/browser/colorizer.ts | 3 +- .../browser/inspectTokens/inspectTokens.ts | 3 +- .../browser/standaloneThemeService.ts | 3 +- .../standalone/common/monarch/monarchLexer.ts | 5 +- .../test/browser/standaloneLanguages.test.ts | 3 +- .../test/browser/controller/cursor.test.ts | 3 +- .../viewModel/modelLineProjection.test.ts | 3 +- .../test/common/core/lineTokens.test.ts | 2 +- .../editor/test/common/core/testLineToken.ts | 2 +- .../bracketPairColorizer/tokenizer.test.ts | 3 +- .../test/common/model/model.line.test.ts | 2 +- src/vs/editor/test/common/model/model.test.ts | 3 +- .../common/model/textModelWithTokens.test.ts | 3 +- .../test/common/model/tokensStore.test.ts | 2 +- .../modes/languageConfiguration.test.ts | 2 +- .../modes/supports/characterPair.test.ts | 2 +- .../modes/supports/electricCharacter.test.ts | 2 +- .../modes/supports/tokenization.test.ts | 2 +- .../common/modes/textToHtmlTokenizer.test.ts | 3 +- src/vs/editor/test/common/modesTestUtils.ts | 2 +- .../semanticTokensProviderStyling.test.ts | 2 +- .../viewLayout/viewLineRenderer.test.ts | 2 +- .../node/classification/typescript.test.ts | 2 +- .../api/browser/mainThreadLanguages.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 3 +- .../api/common/extHostTypeConverters.ts | 11 +- .../inspectEditorTokens.ts | 3 +- .../debug/browser/debugEditorContribution.ts | 3 +- .../view/cellParts/cellDragRenderer.ts | 5 +- .../browser/themes.test.contribution.ts | 3 +- .../browser/abstractTextMateService.ts | 3 +- .../textMate/browser/textMateWorker.ts | 2 +- .../textMate/common/TMScopeRegistry.ts | 2 +- .../textMate/common/TMTokenization.ts | 3 +- 60 files changed, 295 insertions(+), 262 deletions(-) create mode 100644 src/vs/editor/common/encodedTokenAttributes.ts diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index a8a0a6d985e..ffbe3b55882 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -31,7 +31,8 @@ import * as viewEvents from 'vs/editor/common/viewEvents'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; -import { ColorId, ITokenPresentation, TokenizationRegistry } from 'vs/editor/common/languages'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { ColorId, ITokenPresentation } from 'vs/editor/common/encodedTokenAttributes'; import { Color } from 'vs/base/common/color'; export interface IVisibleRangeProvider { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 212bd90a929..2edb031b356 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -18,7 +18,7 @@ import { Range } from 'vs/editor/common/core/range'; import { RGBA8 } from 'vs/editor/common/core/rgba'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { ColorId } from 'vs/editor/common/languages'; +import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimapCharRenderer'; import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; diff --git a/src/vs/editor/common/encodedTokenAttributes.ts b/src/vs/editor/common/encodedTokenAttributes.ts new file mode 100644 index 00000000000..c0f2fad70f1 --- /dev/null +++ b/src/vs/editor/common/encodedTokenAttributes.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Open ended enum at runtime + */ +export const enum LanguageId { + Null = 0, + PlainText = 1 +} + +/** + * A font style. Values are 2^x such that a bit mask can be used. + */ +export const enum FontStyle { + NotSet = -1, + None = 0, + Italic = 1, + Bold = 2, + Underline = 4, + Strikethrough = 8, +} + +/** + * Open ended enum at runtime + */ +export const enum ColorId { + None = 0, + DefaultForeground = 1, + DefaultBackground = 2 +} + +/** + * A standard token type. + */ +export const enum StandardTokenType { + Other = 0, + Comment = 1, + String = 2, + RegEx = 3 +} + +/** + * Helpers to manage the "collapsed" metadata of an entire StackElement stack. + * The following assumptions have been made: + * - languageId < 256 => needs 8 bits + * - unique color count < 512 => needs 9 bits + * + * The binary format is: + * - ------------------------------------------- + * 3322 2222 2222 1111 1111 1100 0000 0000 + * 1098 7654 3210 9876 5432 1098 7654 3210 + * - ------------------------------------------- + * xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx + * bbbb bbbb ffff ffff fFFF FBTT LLLL LLLL + * - ------------------------------------------- + * - L = LanguageId (8 bits) + * - T = StandardTokenType (2 bits) + * - B = Balanced bracket (1 bit) + * - F = FontStyle (4 bits) + * - f = foreground color (9 bits) + * - b = background color (9 bits) + * + */ +export const enum MetadataConsts { + LANGUAGEID_MASK = 0b00000000000000000000000011111111, + TOKEN_TYPE_MASK = 0b00000000000000000000001100000000, + BALANCED_BRACKETS_MASK = 0b00000000000000000000010000000000, + FONT_STYLE_MASK = 0b00000000000000000111100000000000, + FOREGROUND_MASK = 0b00000000111111111000000000000000, + BACKGROUND_MASK = 0b11111111000000000000000000000000, + + ITALIC_MASK = 0b00000000000000000000100000000000, + BOLD_MASK = 0b00000000000000000001000000000000, + UNDERLINE_MASK = 0b00000000000000000010000000000000, + STRIKETHROUGH_MASK = 0b00000000000000000100000000000000, + + // Semantic tokens cannot set the language id, so we can + // use the first 8 bits for control purposes + SEMANTIC_USE_ITALIC = 0b00000000000000000000000000000001, + SEMANTIC_USE_BOLD = 0b00000000000000000000000000000010, + SEMANTIC_USE_UNDERLINE = 0b00000000000000000000000000000100, + SEMANTIC_USE_STRIKETHROUGH = 0b00000000000000000000000000001000, + SEMANTIC_USE_FOREGROUND = 0b00000000000000000000000000010000, + SEMANTIC_USE_BACKGROUND = 0b00000000000000000000000000100000, + + LANGUAGEID_OFFSET = 0, + TOKEN_TYPE_OFFSET = 8, + BALANCED_BRACKETS_OFFSET = 10, + FONT_STYLE_OFFSET = 11, + FOREGROUND_OFFSET = 15, + BACKGROUND_OFFSET = 24 +} + +/** + */ +export class TokenMetadata { + + public static getLanguageId(metadata: number): LanguageId { + return (metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET; + } + + public static getTokenType(metadata: number): StandardTokenType { + return (metadata & MetadataConsts.TOKEN_TYPE_MASK) >>> MetadataConsts.TOKEN_TYPE_OFFSET; + } + + public static containsBalancedBrackets(metadata: number): boolean { + return (metadata & MetadataConsts.BALANCED_BRACKETS_MASK) !== 0; + } + + public static getFontStyle(metadata: number): FontStyle { + return (metadata & MetadataConsts.FONT_STYLE_MASK) >>> MetadataConsts.FONT_STYLE_OFFSET; + } + + public static getForeground(metadata: number): ColorId { + return (metadata & MetadataConsts.FOREGROUND_MASK) >>> MetadataConsts.FOREGROUND_OFFSET; + } + + public static getBackground(metadata: number): ColorId { + return (metadata & MetadataConsts.BACKGROUND_MASK) >>> MetadataConsts.BACKGROUND_OFFSET; + } + + public static getClassNameFromMetadata(metadata: number): string { + const foreground = this.getForeground(metadata); + let className = 'mtk' + foreground; + + const fontStyle = this.getFontStyle(metadata); + if (fontStyle & FontStyle.Italic) { + className += ' mtki'; + } + if (fontStyle & FontStyle.Bold) { + className += ' mtkb'; + } + if (fontStyle & FontStyle.Underline) { + className += ' mtku'; + } + if (fontStyle & FontStyle.Strikethrough) { + className += ' mtks'; + } + + return className; + } + + public static getInlineStyleFromMetadata(metadata: number, colorMap: string[]): string { + const foreground = this.getForeground(metadata); + const fontStyle = this.getFontStyle(metadata); + + let result = `color: ${colorMap[foreground]};`; + if (fontStyle & FontStyle.Italic) { + result += 'font-style: italic;'; + } + if (fontStyle & FontStyle.Bold) { + result += 'font-weight: bold;'; + } + let textDecoration = ''; + if (fontStyle & FontStyle.Underline) { + textDecoration += ' underline'; + } + if (fontStyle & FontStyle.Strikethrough) { + textDecoration += ' line-through'; + } + if (textDecoration) { + result += `text-decoration:${textDecoration};`; + + } + return result; + } + + public static getPresentationFromMetadata(metadata: number): ITokenPresentation { + const foreground = this.getForeground(metadata); + const fontStyle = this.getFontStyle(metadata); + + return { + foreground: foreground, + italic: Boolean(fontStyle & FontStyle.Italic), + bold: Boolean(fontStyle & FontStyle.Bold), + underline: Boolean(fontStyle & FontStyle.Underline), + strikethrough: Boolean(fontStyle & FontStyle.Strikethrough), + }; + } +} + +/** + */ +export interface ITokenPresentation { + foreground: ColorId; + italic: boolean; + bold: boolean; + underline: boolean; + strikethrough: boolean; +} diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 1c366d79a5a..647dc1fa131 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -15,208 +15,13 @@ import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; +import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import * as model from 'vs/editor/common/model'; import { TokenizationRegistry as TokenizationRegistryImpl } from 'vs/editor/common/tokenizationRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -/** - * Open ended enum at runtime - * @internal - */ -export const enum LanguageId { - Null = 0, - PlainText = 1 -} - -/** - * A font style. Values are 2^x such that a bit mask can be used. - * @internal - */ -export const enum FontStyle { - NotSet = -1, - None = 0, - Italic = 1, - Bold = 2, - Underline = 4, - Strikethrough = 8, -} - -/** - * Open ended enum at runtime - * @internal - */ -export const enum ColorId { - None = 0, - DefaultForeground = 1, - DefaultBackground = 2 -} - -/** - * A standard token type. - * @internal - */ -export const enum StandardTokenType { - Other = 0, - Comment = 1, - String = 2, - RegEx = 3 -} - -/** - * Helpers to manage the "collapsed" metadata of an entire StackElement stack. - * The following assumptions have been made: - * - languageId < 256 => needs 8 bits - * - unique color count < 512 => needs 9 bits - * - * The binary format is: - * - ------------------------------------------- - * 3322 2222 2222 1111 1111 1100 0000 0000 - * 1098 7654 3210 9876 5432 1098 7654 3210 - * - ------------------------------------------- - * xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - * bbbb bbbb ffff ffff fFFF FBTT LLLL LLLL - * - ------------------------------------------- - * - L = LanguageId (8 bits) - * - T = StandardTokenType (2 bits) - * - B = Balanced bracket (1 bit) - * - F = FontStyle (4 bits) - * - f = foreground color (9 bits) - * - b = background color (9 bits) - * - * @internal - */ -export const enum MetadataConsts { - LANGUAGEID_MASK = 0b00000000000000000000000011111111, - TOKEN_TYPE_MASK = 0b00000000000000000000001100000000, - BALANCED_BRACKETS_MASK = 0b00000000000000000000010000000000, - FONT_STYLE_MASK = 0b00000000000000000111100000000000, - FOREGROUND_MASK = 0b00000000111111111000000000000000, - BACKGROUND_MASK = 0b11111111000000000000000000000000, - - ITALIC_MASK = 0b00000000000000000000100000000000, - BOLD_MASK = 0b00000000000000000001000000000000, - UNDERLINE_MASK = 0b00000000000000000010000000000000, - STRIKETHROUGH_MASK = 0b00000000000000000100000000000000, - - // Semantic tokens cannot set the language id, so we can - // use the first 8 bits for control purposes - SEMANTIC_USE_ITALIC = 0b00000000000000000000000000000001, - SEMANTIC_USE_BOLD = 0b00000000000000000000000000000010, - SEMANTIC_USE_UNDERLINE = 0b00000000000000000000000000000100, - SEMANTIC_USE_STRIKETHROUGH = 0b00000000000000000000000000001000, - SEMANTIC_USE_FOREGROUND = 0b00000000000000000000000000010000, - SEMANTIC_USE_BACKGROUND = 0b00000000000000000000000000100000, - - LANGUAGEID_OFFSET = 0, - TOKEN_TYPE_OFFSET = 8, - BALANCED_BRACKETS_OFFSET = 10, - FONT_STYLE_OFFSET = 11, - FOREGROUND_OFFSET = 15, - BACKGROUND_OFFSET = 24 -} - -/** - * @internal - */ -export class TokenMetadata { - - public static getLanguageId(metadata: number): LanguageId { - return (metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET; - } - - public static getTokenType(metadata: number): StandardTokenType { - return (metadata & MetadataConsts.TOKEN_TYPE_MASK) >>> MetadataConsts.TOKEN_TYPE_OFFSET; - } - - public static containsBalancedBrackets(metadata: number): boolean { - return (metadata & MetadataConsts.BALANCED_BRACKETS_MASK) !== 0; - } - - public static getFontStyle(metadata: number): FontStyle { - return (metadata & MetadataConsts.FONT_STYLE_MASK) >>> MetadataConsts.FONT_STYLE_OFFSET; - } - - public static getForeground(metadata: number): ColorId { - return (metadata & MetadataConsts.FOREGROUND_MASK) >>> MetadataConsts.FOREGROUND_OFFSET; - } - - public static getBackground(metadata: number): ColorId { - return (metadata & MetadataConsts.BACKGROUND_MASK) >>> MetadataConsts.BACKGROUND_OFFSET; - } - - public static getClassNameFromMetadata(metadata: number): string { - const foreground = this.getForeground(metadata); - let className = 'mtk' + foreground; - - const fontStyle = this.getFontStyle(metadata); - if (fontStyle & FontStyle.Italic) { - className += ' mtki'; - } - if (fontStyle & FontStyle.Bold) { - className += ' mtkb'; - } - if (fontStyle & FontStyle.Underline) { - className += ' mtku'; - } - if (fontStyle & FontStyle.Strikethrough) { - className += ' mtks'; - } - - return className; - } - - public static getInlineStyleFromMetadata(metadata: number, colorMap: string[]): string { - const foreground = this.getForeground(metadata); - const fontStyle = this.getFontStyle(metadata); - - let result = `color: ${colorMap[foreground]};`; - if (fontStyle & FontStyle.Italic) { - result += 'font-style: italic;'; - } - if (fontStyle & FontStyle.Bold) { - result += 'font-weight: bold;'; - } - let textDecoration = ''; - if (fontStyle & FontStyle.Underline) { - textDecoration += ' underline'; - } - if (fontStyle & FontStyle.Strikethrough) { - textDecoration += ' line-through'; - } - if (textDecoration) { - result += `text-decoration:${textDecoration};`; - - } - return result; - } - - public static getPresentationFromMetadata(metadata: number): ITokenPresentation { - const foreground = this.getForeground(metadata); - const fontStyle = this.getFontStyle(metadata); - - return { - foreground: foreground, - italic: Boolean(fontStyle & FontStyle.Italic), - bold: Boolean(fontStyle & FontStyle.Bold), - underline: Boolean(fontStyle & FontStyle.Underline), - strikethrough: Boolean(fontStyle & FontStyle.Strikethrough), - }; - } -} - -/** - * @internal - */ -export interface ITokenPresentation { - foreground: ColorId; - italic: boolean; - bold: boolean; - underline: boolean; - strikethrough: boolean; -} - /** * @internal */ diff --git a/src/vs/editor/common/languages/languageConfiguration.ts b/src/vs/editor/common/languages/languageConfiguration.ts index 1f4a458bc08..69b852e407c 100644 --- a/src/vs/editor/common/languages/languageConfiguration.ts +++ b/src/vs/editor/common/languages/languageConfiguration.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ScopedLineTokens } from 'vs/editor/common/languages/supports'; /** diff --git a/src/vs/editor/common/languages/nullTokenize.ts b/src/vs/editor/common/languages/nullTokenize.ts index ce7b605783d..2bf77a6acd8 100644 --- a/src/vs/editor/common/languages/nullTokenize.ts +++ b/src/vs/editor/common/languages/nullTokenize.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Token, TokenizationResult, EncodedTokenizationResult, ColorId, FontStyle, IState, LanguageId, MetadataConsts, StandardTokenType } from 'vs/editor/common/languages'; +import { Token, TokenizationResult, EncodedTokenizationResult, IState } from 'vs/editor/common/languages'; +import { LanguageId, FontStyle, ColorId, StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; export const NullState: IState = new class implements IState { public clone(): IState { diff --git a/src/vs/editor/common/languages/supports.ts b/src/vs/editor/common/languages/supports.ts index bef3fc120a9..8fdfa17bb51 100644 --- a/src/vs/editor/common/languages/supports.ts +++ b/src/vs/editor/common/languages/supports.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; export function createScopedLineTokens(context: LineTokens, offset: number): ScopedLineTokens { const tokenCount = context.getCount(); diff --git a/src/vs/editor/common/languages/supports/tokenization.ts b/src/vs/editor/common/languages/supports/tokenization.ts index b3f686300dd..6f7f777dcdb 100644 --- a/src/vs/editor/common/languages/supports/tokenization.ts +++ b/src/vs/editor/common/languages/supports/tokenization.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Color } from 'vs/base/common/color'; -import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType } from 'vs/editor/common/languages'; +import { LanguageId, FontStyle, ColorId, StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; export interface ITokenThemeRule { token: string; diff --git a/src/vs/editor/common/languages/textToHtmlTokenizer.ts b/src/vs/editor/common/languages/textToHtmlTokenizer.ts index 64bf71febb1..66651f25410 100644 --- a/src/vs/editor/common/languages/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/languages/textToHtmlTokenizer.ts @@ -6,7 +6,8 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { IViewLineTokens, LineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { ILanguageIdCodec, IState, ITokenizationSupport, LanguageId, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { NullState, nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts index c65a57b0175..0e7381b7677 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { NotSupportedError } from 'vs/base/common/errors'; -import { StandardTokenType, TokenMetadata } from 'vs/editor/common/languages'; +import { StandardTokenType, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { BracketAstNode, TextAstNode } from './ast'; import { BracketTokens, LanguageAgnosticBracketTokens } from './brackets'; diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index a0aaf377690..b504fc14366 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -8,7 +8,8 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; -import { EncodedTokenizationResult, ILanguageIdCodec, IState, ITokenizationSupport, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize'; import { TextModel } from 'vs/editor/common/model/textModel'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index df1000db4cd..cc2cf81f02b 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILanguageConfigurationService, ResolvedLanguageConfiguration } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { TextModel } from 'vs/editor/common/model/textModel'; diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index dba3c0b9bb6..cf7a5f5ea35 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -9,7 +9,8 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { compareIgnoreCase, regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { clearPlatformLanguageAssociations, getLanguageIds, registerPlatformLanguageAssociation } from 'vs/editor/common/services/languagesAssociations'; import { URI } from 'vs/base/common/uri'; -import { ILanguageIdCodec, LanguageId } from 'vs/editor/common/languages'; +import { ILanguageIdCodec } from 'vs/editor/common/languages'; +import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageExtensionPoint, ILanguageNameIdPair, ILanguageIcon } from 'vs/editor/common/languages/language'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/editor/common/services/semanticTokensProviderStyling.ts b/src/vs/editor/common/services/semanticTokensProviderStyling.ts index e804a241d4e..019c5d01726 100644 --- a/src/vs/editor/common/services/semanticTokensProviderStyling.ts +++ b/src/vs/editor/common/services/semanticTokensProviderStyling.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SemanticTokensLegend, TokenMetadata, FontStyle, MetadataConsts, SemanticTokens } from 'vs/editor/common/languages'; +import { SemanticTokensLegend, SemanticTokens } from 'vs/editor/common/languages'; +import { FontStyle, MetadataConsts, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; diff --git a/src/vs/editor/common/tokenizationRegistry.ts b/src/vs/editor/common/tokenizationRegistry.ts index 223eb58996b..4f1876bbb9a 100644 --- a/src/vs/editor/common/tokenizationRegistry.ts +++ b/src/vs/editor/common/tokenizationRegistry.ts @@ -6,7 +6,8 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ColorId, ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent, ITokenizationSupportFactory } from 'vs/editor/common/languages'; +import { ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent, ITokenizationSupportFactory } from 'vs/editor/common/languages'; +import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; export class TokenizationRegistry implements ITokenizationRegistry { diff --git a/src/vs/editor/common/tokenizationTextModelPart.ts b/src/vs/editor/common/tokenizationTextModelPart.ts index b68ce485850..6c8e7c322ed 100644 --- a/src/vs/editor/common/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/tokenizationTextModelPart.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ContiguousMultilineTokens } from 'vs/editor/common/tokens/contiguousMultilineTokens'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; diff --git a/src/vs/editor/common/tokens/contiguousTokensStore.ts b/src/vs/editor/common/tokens/contiguousTokensStore.ts index b1aac9f8232..aa2c1421491 100644 --- a/src/vs/editor/common/tokens/contiguousTokensStore.ts +++ b/src/vs/editor/common/tokens/contiguousTokensStore.ts @@ -8,7 +8,8 @@ import { Position } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { ContiguousTokensEditing, EMPTY_LINE_TOKENS, toUint32Array } from 'vs/editor/common/tokens/contiguousTokensEditing'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { ColorId, FontStyle, ILanguageIdCodec, LanguageId, MetadataConsts, StandardTokenType, TokenMetadata } from 'vs/editor/common/languages'; +import { ILanguageIdCodec } from 'vs/editor/common/languages'; +import { LanguageId, FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; /** * Represents contiguous tokens in a text model. diff --git a/src/vs/editor/common/tokens/lineTokens.ts b/src/vs/editor/common/tokens/lineTokens.ts index b950acf4768..bdcda7cb180 100644 --- a/src/vs/editor/common/tokens/lineTokens.ts +++ b/src/vs/editor/common/tokens/lineTokens.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ColorId, FontStyle, ILanguageIdCodec, ITokenPresentation, MetadataConsts, StandardTokenType, TokenMetadata } from 'vs/editor/common/languages'; +import { ILanguageIdCodec } from 'vs/editor/common/languages'; +import { FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata, ITokenPresentation } from 'vs/editor/common/encodedTokenAttributes'; export interface IViewLineTokens { equals(other: IViewLineTokens): boolean; diff --git a/src/vs/editor/common/tokens/sparseTokensStore.ts b/src/vs/editor/common/tokens/sparseTokensStore.ts index 387adb9b518..90a1f3ab818 100644 --- a/src/vs/editor/common/tokens/sparseTokensStore.ts +++ b/src/vs/editor/common/tokens/sparseTokensStore.ts @@ -7,7 +7,8 @@ import * as arrays from 'vs/base/common/arrays'; import { IRange, Range } from 'vs/editor/common/core/range'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; -import { ILanguageIdCodec, MetadataConsts } from 'vs/editor/common/languages'; +import { ILanguageIdCodec } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; /** * Represents sparse tokens in a text model. diff --git a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts index 16cc6154be9..a063d37a0ce 100644 --- a/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts +++ b/src/vs/editor/common/viewModel/minimapTokensColorTracker.ts @@ -6,7 +6,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { ColorId, TokenizationRegistry } from 'vs/editor/common/languages'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; export class MinimapTokensColorTracker extends Disposable { private static _INSTANCE: MinimapTokensColorTracker | null = null; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index e9db60465a0..909d9f19bc3 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -11,7 +11,7 @@ import { IModelDecoration, ITextModel, PositionAffinity } from 'vs/editor/common import { IViewModelLines } from 'vs/editor/common/viewModel/viewModelLines'; import { ICoordinatesConverter, InlineDecoration, InlineDecorationType, ViewModelDecoration } from 'vs/editor/common/viewModel'; import { filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; export interface IDecorationsViewportData { /** diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 9d2738c3b9f..c55cf3d02d1 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -23,7 +23,8 @@ import { EndOfLinePreference, ICursorStateComputer, IIdentifiedSingleEditOperati import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from 'vs/editor/common/textModelGuides'; import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/textModelEvents'; -import { ColorId, TokenizationRegistry } from 'vs/editor/common/languages'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { tokenizeLineToHTML } from 'vs/editor/common/languages/textToHtmlTokenizer'; diff --git a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts index a451d61ecac..7f9bea14db9 100644 --- a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts @@ -7,7 +7,8 @@ import * as assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; -import { EncodedTokenizationResult, ColorId, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ColorId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { CommentRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; diff --git a/src/vs/editor/contrib/indentation/browser/indentation.ts b/src/vs/editor/contrib/indentation/browser/indentation.ts index 2f7bcfd1eae..8bc18b1d8c1 100644 --- a/src/vs/editor/contrib/indentation/browser/indentation.ts +++ b/src/vs/editor/contrib/indentation/browser/indentation.ts @@ -15,7 +15,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder, IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; -import { StandardTokenType, TextEdit } from 'vs/editor/common/languages'; +import { TextEdit } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IndentConsts } from 'vs/editor/common/languages/supports/indentRules'; import { IModelService } from 'vs/editor/common/services/model'; diff --git a/src/vs/editor/contrib/suggest/browser/suggest.ts b/src/vs/editor/contrib/suggest/browser/suggest.ts index 33b339aea72..99a61d27d8a 100644 --- a/src/vs/editor/contrib/suggest/browser/suggest.ts +++ b/src/vs/editor/contrib/suggest/browser/suggest.ts @@ -27,6 +27,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { historyNavigationVisible } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { InternalQuickSuggestionsOptions, QuickSuggestionsValue } from 'vs/editor/common/config/editorOptions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; export const Context = { Visible: historyNavigationVisible, @@ -436,10 +437,10 @@ export abstract class QuickSuggestionsOptions { return config.other === 'on' && config.comments === 'on' && config.strings === 'on'; } - static valueFor(config: InternalQuickSuggestionsOptions, tokenType: languages.StandardTokenType): QuickSuggestionsValue { + static valueFor(config: InternalQuickSuggestionsOptions, tokenType: StandardTokenType): QuickSuggestionsValue { switch (tokenType) { - case languages.StandardTokenType.Comment: return config.comments; - case languages.StandardTokenType.String: return config.strings; + case StandardTokenType.Comment: return config.comments; + case StandardTokenType.String: return config.strings; default: return config.other; } } diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts index 14069773008..0f5af3b638a 100644 --- a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts @@ -15,7 +15,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { CompletionItemKind, CompletionItemProvider, CompletionList, CompletionTriggerKind, EncodedTokenizationResult, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages'; +import { CompletionItemKind, CompletionItemProvider, CompletionList, CompletionTriggerKind, EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 7adcf0cea76..2e38febe774 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -6,7 +6,8 @@ import * as strings from 'vs/base/common/strings'; import { IViewLineTokens, LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { ITextModel } from 'vs/editor/common/model'; -import { ColorId, FontStyle, ILanguageIdCodec, ITokenizationSupport, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageIdCodec, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { RenderLineInput, renderViewLine2 as renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineRenderingData } from 'vs/editor/common/viewModel'; diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 8ed87552447..6ac269a0f8c 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -14,7 +14,8 @@ import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorCon import { Position } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { FontStyle, IState, ITokenizationSupport, StandardTokenType, TokenMetadata, TokenizationRegistry, ILanguageIdCodec, Token } from 'vs/editor/common/languages'; +import { IState, ITokenizationSupport, TokenizationRegistry, ILanguageIdCodec, Token } from 'vs/editor/common/languages'; +import { FontStyle, StandardTokenType, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { NullState, nullTokenize, nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme'; diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index cafca9f55a7..e6019720020 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -7,7 +7,8 @@ import * as dom from 'vs/base/browser/dom'; import { addMatchMediaChangeListener } from 'vs/base/browser/browser'; import { Color } from 'vs/base/common/color'; import { Emitter } from 'vs/base/common/event'; -import { FontStyle, TokenizationRegistry, TokenMetadata } from 'vs/editor/common/languages'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { FontStyle, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { ITokenThemeRule, TokenTheme, generateTokensCSSForColorMap } from 'vs/editor/common/languages/supports/tokenization'; import { BuiltinTheme, IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme'; import { hc_black, hc_light, vs, vs_dark } from 'vs/editor/standalone/common/themes'; diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index bdc490fdb41..7ef5b725f48 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -15,6 +15,7 @@ import { TokenTheme } from 'vs/editor/common/languages/supports/tokenization'; import { ILanguageService } from 'vs/editor/common/languages/language'; import * as monarchCommon from 'vs/editor/standalone/common/monarch/monarchCommon'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme'; +import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; const CACHE_STACK_DEPTH = 5; @@ -296,7 +297,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { private readonly _theme: TokenTheme; private _prependTokens: Uint32Array | null; private _tokens: number[]; - private _currentLanguageId: languages.LanguageId; + private _currentLanguageId: LanguageId; private _lastTokenMetadata: number; constructor(languageService: ILanguageService, theme: TokenTheme) { @@ -304,7 +305,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { this._theme = theme; this._prependTokens = null; this._tokens = []; - this._currentLanguageId = languages.LanguageId.Null; + this._currentLanguageId = LanguageId.Null; this._lastTokenMetadata = 0; } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 50d960dd0fb..dbb0ee675a8 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -7,7 +7,8 @@ import * as assert from 'assert'; import { Color } from 'vs/base/common/color'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Token, IState, LanguageId, MetadataConsts } from 'vs/editor/common/languages'; +import { Token, IState } from 'vs/editor/common/languages'; +import { LanguageId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { TokenTheme } from 'vs/editor/common/languages/supports/tokenization'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { ILineTokens, IToken, TokenizationSupportAdapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 745e7388b60..14348ef8d37 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -13,7 +13,8 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { EncodedTokenizationResult, IState, ITokenizationSupport, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { IndentAction, IndentationRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; diff --git a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts index 2fe1d779919..790442706c4 100644 --- a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts +++ b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts @@ -20,6 +20,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; import { ISimpleModel, IModelLineProjection, createModelLineProjection } from 'vs/editor/common/viewModel/modelLineProjection'; import { ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { @@ -342,7 +343,7 @@ suite('SplitLinesCollection', () => { for (let i = 0; i < tokens.length; i++) { result[2 * i] = tokens[i].startIndex; result[2 * i + 1] = ( - tokens[i].value << languages.MetadataConsts.FOREGROUND_OFFSET + tokens[i].value << MetadataConsts.FOREGROUND_OFFSET ); } return new languages.EncodedTokenizationResult(result, state); diff --git a/src/vs/editor/test/common/core/lineTokens.test.ts b/src/vs/editor/test/common/core/lineTokens.test.ts index f26d8930a56..a6b8f18a88d 100644 --- a/src/vs/editor/test/common/core/lineTokens.test.ts +++ b/src/vs/editor/test/common/core/lineTokens.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { IViewLineTokens, LineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { MetadataConsts } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; suite('LineTokens', () => { diff --git a/src/vs/editor/test/common/core/testLineToken.ts b/src/vs/editor/test/common/core/testLineToken.ts index 0c6b236fbc7..ed9876b6a28 100644 --- a/src/vs/editor/test/common/core/testLineToken.ts +++ b/src/vs/editor/test/common/core/testLineToken.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { ColorId, ITokenPresentation, TokenMetadata } from 'vs/editor/common/languages'; +import { ColorId, TokenMetadata, ITokenPresentation } from 'vs/editor/common/encodedTokenAttributes'; /** * A token on a line. diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index c88eca52ef2..2615475d9f0 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -10,7 +10,8 @@ import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/ import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { EncodedTokenizationResult, IState, ITokenizationSupport, LanguageId, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { LanguageId, StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index 7424e5f573e..583e4f5ab60 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { Range } from 'vs/editor/common/core/range'; import { computeIndentLevel } from 'vs/editor/common/model/utils'; -import { MetadataConsts } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { TestLineToken, TestLineTokenFactory } from 'vs/editor/test/common/core/testLineToken'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 96fe698cb1b..e9e4d63647d 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -10,7 +10,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { InternalModelContentChangeEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents'; -import { EncodedTokenizationResult, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index ed66c94516d..c20c35b3ff8 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -9,7 +9,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IFoundBracket } from 'vs/editor/common/textModelBracketPairs'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { ITokenizationSupport, MetadataConsts, TokenizationRegistry, StandardTokenType, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { ITokenizationSupport, TokenizationRegistry, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { CharacterPair } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 0dcbea92aa7..632a5375d25 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -9,7 +9,7 @@ import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { MetadataConsts, TokenMetadata, FontStyle, ColorId } from 'vs/editor/common/languages'; +import { FontStyle, ColorId, MetadataConsts, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; diff --git a/src/vs/editor/test/common/modes/languageConfiguration.test.ts b/src/vs/editor/test/common/modes/languageConfiguration.test.ts index 49f076e217e..2f47dfd04de 100644 --- a/src/vs/editor/test/common/modes/languageConfiguration.test.ts +++ b/src/vs/editor/test/common/modes/languageConfiguration.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index d3f78fc15a4..1bfeaa4b56f 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { CharacterPairSupport } from 'vs/editor/common/languages/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; diff --git a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts index 5e3a3bf276b..5a4dc78ec43 100644 --- a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts +++ b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; import { RichEditBrackets } from 'vs/editor/common/languages/supports/richEditBrackets'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; diff --git a/src/vs/editor/test/common/modes/supports/tokenization.test.ts b/src/vs/editor/test/common/modes/supports/tokenization.test.ts index a38cec767f9..1bb5f9bbc76 100644 --- a/src/vs/editor/test/common/modes/supports/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/supports/tokenization.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { FontStyle } from 'vs/editor/common/languages'; +import { FontStyle } from 'vs/editor/common/encodedTokenAttributes'; import { ColorMap, ExternalThemeTrieElement, ParsedTokenThemeRule, ThemeTrieElementRule, TokenTheme, parseTokenTheme, strcmp } from 'vs/editor/common/languages/supports/tokenization'; suite('Token theme matching', () => { diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index e49e06d804b..a05fbb7e865 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EncodedTokenizationResult, ColorId, FontStyle, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { tokenizeLineToHTML, _tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; diff --git a/src/vs/editor/test/common/modesTestUtils.ts b/src/vs/editor/test/common/modesTestUtils.ts index d198101edca..21bc380e2c2 100644 --- a/src/vs/editor/test/common/modesTestUtils.ts +++ b/src/vs/editor/test/common/modesTestUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { MetadataConsts, StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ScopedLineTokens, createScopedLineTokens } from 'vs/editor/common/languages/supports'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; diff --git a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts index f512a58a9f2..b6660b3da97 100644 --- a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts +++ b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; -import { MetadataConsts } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; import { createModelServices } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 4264f60e47e..19e225ab4f7 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { MetadataConsts } from 'vs/editor/common/languages'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange, DomPosition } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { InlineDecorationType } from 'vs/editor/common/viewModel'; diff --git a/src/vs/editor/test/node/classification/typescript.test.ts b/src/vs/editor/test/node/classification/typescript.test.ts index 279801f4c7e..e99cfa28d94 100644 --- a/src/vs/editor/test/node/classification/typescript.test.ts +++ b/src/vs/editor/test/node/classification/typescript.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import * as fs from 'fs'; // import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; // import { parse } from 'vs/editor/common/modes/tokenization/typescript'; diff --git a/src/vs/workbench/api/browser/mainThreadLanguages.ts b/src/vs/workbench/api/browser/mainThreadLanguages.ts index e8316e42dfb..fffdbc7f4db 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguages.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguages.ts @@ -10,7 +10,7 @@ import { MainThreadLanguagesShape, MainContext, ExtHostContext, ExtHostLanguages import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { StandardTokenType } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ILanguageStatus, ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2a7023db7a0..33fcc3fb351 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -22,6 +22,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as languages from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; 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'; @@ -398,7 +399,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { export interface MainThreadLanguagesShape extends IDisposable { $changeLanguage(resource: UriComponents, languageId: string): Promise; - $tokensAtPosition(resource: UriComponents, position: IPosition): Promise; + $tokensAtPosition(resource: UriComponents, position: IPosition): Promise; $setLanguageStatus(handle: number, status: ILanguageStatus): void; $removeLanguageStatus(handle: number): void; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 94226d271f2..95e24e8692a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -20,6 +20,7 @@ import * as editorRange from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import { IContentDecorationRenderOptions, IDecorationOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import * as languages from 'vs/editor/common/languages'; +import * as encodedTokenAttributes from 'vs/editor/common/encodedTokenAttributes'; import * as languageSelector from 'vs/editor/common/languageSelector'; import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model'; import { EditorResolution, ITextEditorOptions } from 'vs/platform/editor/common/editor'; @@ -112,12 +113,12 @@ export namespace Range { } export namespace TokenType { - export function to(type: languages.StandardTokenType): types.StandardTokenType { + export function to(type: encodedTokenAttributes.StandardTokenType): types.StandardTokenType { switch (type) { - case languages.StandardTokenType.Comment: return types.StandardTokenType.Comment; - case languages.StandardTokenType.Other: return types.StandardTokenType.Other; - case languages.StandardTokenType.RegEx: return types.StandardTokenType.RegEx; - case languages.StandardTokenType.String: return types.StandardTokenType.String; + case encodedTokenAttributes.StandardTokenType.Comment: return types.StandardTokenType.Comment; + case encodedTokenAttributes.StandardTokenType.Other: return types.StandardTokenType.Other; + case encodedTokenAttributes.StandardTokenType.RegEx: return types.StandardTokenType.RegEx; + case encodedTokenAttributes.StandardTokenType.String: return types.StandardTokenType.String; } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index e3111f08d6c..ed70650c31e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -16,7 +16,8 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { FontStyle, StandardTokenType, TokenMetadata, SemanticTokensLegend, SemanticTokens, ColorId } from 'vs/editor/common/languages'; +import { SemanticTokensLegend, SemanticTokens } from 'vs/editor/common/languages'; +import { FontStyle, ColorId, StandardTokenType, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 98bcc2d1d6f..48a002966c6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -12,7 +12,8 @@ import { setProperty } from 'vs/base/common/jsonEdit'; import { Constants } from 'vs/base/common/uint'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { InlineValueContext, StandardTokenType } from 'vs/editor/common/languages'; +import { InlineValueContext } from 'vs/editor/common/languages'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { distinct, flatten } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts index 43c5852753e..5efe69fbc3d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts @@ -10,6 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import * as languages from 'vs/editor/common/languages'; +import { ColorId } from 'vs/editor/common/encodedTokenAttributes'; import { tokenizeLineToHTML } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { ITextModel } from 'vs/editor/common/model'; import { BaseCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; @@ -33,8 +34,8 @@ class EditorTextRenderer { const fontWeightVar = '--notebook-editor-font-weight'; const style = `` - + `color: ${colorMap[languages.ColorId.DefaultForeground]};` - + `background-color: ${colorMap[languages.ColorId.DefaultBackground]};` + + `color: ${colorMap[ColorId.DefaultForeground]};` + + `background-color: ${colorMap[ColorId.DefaultBackground]};` + `font-family: var(${fontFamilyVar});` + `font-weight: var(${fontWeightVar});` + `font-size: var(${fontSizeVar});` diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index 2fbc546da95..5adb077e4cd 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -12,7 +12,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { EditorResourceAccessor } from 'vs/workbench/common/editor'; import { ITextMateService } from 'vs/workbench/services/textMate/browser/textMate'; import type { IGrammar, StackElement } from 'vscode-textmate'; -import { TokenizationRegistry, TokenMetadata } from 'vs/editor/common/languages'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { ThemeRule, findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper'; import { Color } from 'vs/base/common/color'; import { IFileService } from 'vs/platform/files/common/files'; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 0df6157e1bd..296cf73f0bc 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -12,7 +12,8 @@ import * as resources from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { equals as equalArray } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; -import { IState, ITokenizationSupport, LanguageId, TokenizationRegistry, StandardTokenType, ITokenizationSupportFactory, TokenizationResult, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { IState, ITokenizationSupport, TokenizationRegistry, ITokenizationSupportFactory, TokenizationResult, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { LanguageId, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { nullTokenizeEncoded } from 'vs/editor/common/languages/nullTokenize'; import { generateTokensCSSForColorMap } from 'vs/editor/common/languages/supports/tokenization'; import { ILanguageService } from 'vs/editor/common/languages/language'; diff --git a/src/vs/workbench/services/textMate/browser/textMateWorker.ts b/src/vs/workbench/services/textMate/browser/textMateWorker.ts index 6b848d33c24..c4d6299742d 100644 --- a/src/vs/workbench/services/textMate/browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/browser/textMateWorker.ts @@ -5,7 +5,7 @@ import { IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; import { UriComponents, URI } from 'vs/base/common/uri'; -import { LanguageId } from 'vs/editor/common/languages'; +import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { IValidEmbeddedLanguagesMap, IValidTokenTypeMap, IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { TMGrammarFactory, ICreateGrammarResult } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IModelChangedEvent, MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; diff --git a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts index 83b041e7823..5d88a28f4f4 100644 --- a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts +++ b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts @@ -6,7 +6,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; -import { StandardTokenType, LanguageId } from 'vs/editor/common/languages'; +import { LanguageId, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; export interface IValidGrammarDefinition { location: URI; diff --git a/src/vs/workbench/services/textMate/common/TMTokenization.ts b/src/vs/workbench/services/textMate/common/TMTokenization.ts index 64816e5d912..058115fb065 100644 --- a/src/vs/workbench/services/textMate/common/TMTokenization.ts +++ b/src/vs/workbench/services/textMate/common/TMTokenization.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationResult, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { IState, ITokenizationSupport, TokenizationResult, EncodedTokenizationResult } from 'vs/editor/common/languages'; +import { LanguageId, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import type { IGrammar, StackElement } from 'vscode-textmate'; import { Disposable } from 'vs/base/common/lifecycle'; From ffb503f65a8f957fdbce6b2e7cddcc6922c1314c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 24 May 2022 16:11:47 +0200 Subject: [PATCH 250/350] Fix #149632 (#150278) --- src/vs/workbench/contrib/extensions/browser/extensionEditor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index eb5589276f2..f2da4fedd34 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -644,6 +644,7 @@ export class ExtensionEditor extends EditorPane { private setRecommendationText(extension: IExtension, template: IExtensionEditorTemplate): void { const updateRecommendationText = (layout: boolean) => { + reset(template.recommendation); const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { const reasonText = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; @@ -658,8 +659,8 @@ export class ExtensionEditor extends EditorPane { this.layout(this.dimension); } }; - reset(template.recommendation); if (extension.deprecationInfo || extension.state === ExtensionState.Installed) { + reset(template.recommendation); return; } updateRecommendationText(false); From 0844d7c996d6b0c3e4b1196a1d7d426356bdc9c8 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 24 May 2022 16:13:37 +0200 Subject: [PATCH 251/350] move `USER_TASKS_GROUP_KEY` into tasks.ts, out of taskService.ts, and prevent cyclic dependency fixes https://github.com/microsoft/vscode/issues/150178 --- src/vs/workbench/api/common/extHostTask.ts | 2 +- .../workbench/contrib/tasks/browser/abstractTaskService.ts | 5 +++-- src/vs/workbench/contrib/tasks/common/taskConfiguration.ts | 5 ++--- src/vs/workbench/contrib/tasks/common/taskService.ts | 2 -- src/vs/workbench/contrib/tasks/common/tasks.ts | 4 +++- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 00d03d0b6ee..c05d65eee9e 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -24,7 +24,7 @@ import { Schemas } from 'vs/base/common/network'; import * as Platform from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/tasks'; import { NotSupportedError } from 'vs/base/common/errors'; export interface IExtHostTask extends ExtHostTaskShape { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 7e4c505c69c..dbd096e791c 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -52,9 +52,10 @@ import { Task, CustomTask, ConfiguringTask, ContributedTask, InMemoryTask, TaskEvent, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, - KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition, RuntimeType + KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition, RuntimeType, + USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; import * as TaskConfig from '../common/taskConfiguration'; diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 9c6f0ba9e34..3f567b6320a 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -23,7 +23,7 @@ import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { URI } from 'vs/base/common/uri'; -import { USER_TASKS_GROUP_KEY, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const enum ShellQuoting { @@ -1268,7 +1268,7 @@ export namespace GroupKind { namespace TaskDependency { function uriFromSource(context: ParseContext, source: TaskConfigSource): URI | string { switch (source) { - case TaskConfigSource.User: return USER_TASKS_GROUP_KEY; + case TaskConfigSource.User: return Tasks.USER_TASKS_GROUP_KEY; case TaskConfigSource.TasksJson: return context.workspaceFolder.uri; default: return context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri; } @@ -2146,4 +2146,3 @@ export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | export function createCustomTask(contributedTask: Tasks.ContributedTask, configuredProps: Tasks.ConfiguringTask | Tasks.CustomTask): Tasks.CustomTask { return CustomTask.createCustomTask(contributedTask, configuredProps); } - diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 68a2394a880..b75cd445666 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -55,8 +55,6 @@ export interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { workspaceFolder: IWorkspaceFolder; } -export const USER_TASKS_GROUP_KEY = 'settings'; - export interface ITaskService { readonly _serviceBrand: undefined; onDidStateChange: Event; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1e7e42b6c81..e26ad2051e5 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -16,7 +16,9 @@ import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/comm import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; + + +export const USER_TASKS_GROUP_KEY = 'settings'; export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false, nls.localize('tasks.taskRunningContext', "Whether a task is currently running.")); export const TASKS_CATEGORY = { value: nls.localize('tasksCategory', "Tasks"), original: 'Tasks' }; From d097ca3f0c13c39ffa01167d1a30fe69b41ff3af Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 24 May 2022 10:19:03 -0400 Subject: [PATCH 252/350] Don't run code review bot on draft PR (#150280) --- .github/workflows/pr-chat.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-chat.yml b/.github/workflows/pr-chat.yml index 0c8d6bab793..13803fda778 100644 --- a/.github/workflows/pr-chat.yml +++ b/.github/workflows/pr-chat.yml @@ -6,6 +6,7 @@ on: jobs: main: runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} steps: - name: Checkout Actions uses: actions/checkout@v2 From dfa06b84f820790649f7712dffbfac964b6e0d89 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 24 May 2022 16:42:47 +0200 Subject: [PATCH 253/350] Use an `UtilityProcess` for the extension host (#150167) --- src/bootstrap-fork.js | 18 + .../extensions/common/extensionHostStarter.ts | 6 +- .../electron-main/extensionHostStarter.ts | 166 ++++- .../api/node/extensionHostProcess.ts | 40 +- .../localProcessExtensionHost.ts | 577 ++++++++++-------- 5 files changed, 525 insertions(+), 282 deletions(-) diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 06a95927644..c035cb6f262 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -37,6 +37,11 @@ if (process.env['VSCODE_PARENT_PID']) { terminateWhenParentTerminates(); } +// Listen for message ports +if (process.env['VSCODE_WILL_SEND_MESSAGE_PORT']) { + listenForMessagePort(); +} + // Load AMD entry point require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); @@ -264,4 +269,17 @@ function terminateWhenParentTerminates() { } } +function listenForMessagePort() { + // We need to listen for the 'port' event as soon as possible, + // otherwise we might miss the event. But we should also be + // prepared in case the event arrives late. + process.on('port', (e) => { + if (global.vscodePortsCallback) { + global.vscodePortsCallback(e.ports); + } else { + global.vscodePorts = e.ports; + } + }); +} + //#endregion diff --git a/src/vs/platform/extensions/common/extensionHostStarter.ts b/src/vs/platform/extensions/common/extensionHostStarter.ts index d4fc6b48112..33f3192f206 100644 --- a/src/vs/platform/extensions/common/extensionHostStarter.ts +++ b/src/vs/platform/extensions/common/extensionHostStarter.ts @@ -12,6 +12,9 @@ export const IExtensionHostStarter = createDecorator('ext export const ipcExtensionHostStarterChannelName = 'extensionHostStarter'; export interface IExtensionHostProcessOptions { + responseWindowId: number; + responseChannel: string; + responseNonce: string; env: { [key: string]: string | undefined }; detached: boolean; execArgv: string[] | undefined; @@ -27,8 +30,9 @@ export interface IExtensionHostStarter { onDynamicError(id: string): Event<{ error: SerializedError }>; onDynamicExit(id: string): Event<{ code: number; signal: string }>; + usesUtilityProcess(): Promise; createExtensionHost(): Promise<{ id: string }>; - start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }>; + start(id: string, opts: IExtensionHostProcessOptions): Promise; enableInspectPort(id: string): Promise; kill(id: string): Promise; diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index 493168507e8..8ef9c5a0da7 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { canceled, SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; import { Emitter, Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; @@ -17,20 +17,40 @@ import { FileAccess } from 'vs/base/common/network'; import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; +import type { EventEmitter } from 'events'; +import * as electron from 'electron'; + +declare namespace UtilityProcessProposedApi { + interface UtilityProcessOptions { + serviceName?: string | undefined; + execArgv?: string[] | undefined; + env?: NodeJS.ProcessEnv | undefined; + } + export class UtilityProcess extends EventEmitter { + readonly pid?: number | undefined; + constructor(modulePath: string, args?: string[] | undefined, options?: UtilityProcessOptions); + postMessage(channel: string, message: any, transfer?: Electron.MessagePortMain[]): void; + kill(signal?: number | string): boolean; + on(event: 'exit', listener: (code: number) => void): this; + on(event: 'spawn', listener: () => void): this; + } +} +const UtilityProcess = ((electron as any).UtilityProcess); +const canUseUtilityProcess = (typeof UtilityProcess !== 'undefined') && !!process.env['VSCODE_USE_UTILITY_PROCESS']; export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { _serviceBrand: undefined; private static _lastId: number = 0; - protected readonly _extHosts: Map; + protected readonly _extHosts: Map; private _shutdown = false; constructor( @ILogService private readonly _logService: ILogService, @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { - this._extHosts = new Map(); + this._extHosts = new Map(); // On shutdown: gracefully await extension host shutdowns lifecycleMainService.onWillShutdown((e) => { @@ -43,7 +63,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter // Intentionally not killing the extension host processes } - private _getExtHost(id: string): ExtensionHostProcess { + private _getExtHost(id: string): ExtensionHostProcess | UtilityExtensionHostProcess { const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { throw new Error(`Unknown extension host!`); @@ -71,12 +91,20 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter return this._getExtHost(id).onExit; } + async usesUtilityProcess(): Promise { + return canUseUtilityProcess; + } + async createExtensionHost(): Promise<{ id: string }> { if (this._shutdown) { throw canceled(); } const id = String(++ExtensionHostStarter._lastId); - const extHost = new ExtensionHostProcess(id, this._logService); + const extHost = ( + canUseUtilityProcess + ? new UtilityExtensionHostProcess(id, this._logService) + : new ExtensionHostProcess(id, this._logService) + ); this._extHosts.set(id, extHost); extHost.onExit(({ pid, code, signal }) => { this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); @@ -88,7 +116,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter return { id }; } - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { + async start(id: string, opts: IExtensionHostProcessOptions): Promise { if (this._shutdown) { throw canceled(); } @@ -160,7 +188,7 @@ class ExtensionHostProcess extends Disposable { super(); } - start(opts: IExtensionHostProcessOptions): { pid: number } { + start(opts: IExtensionHostProcessOptions): void { if (platform.isCI) { this._logService.info(`Calling fork to start extension host...`); } @@ -199,8 +227,130 @@ class ExtensionHostProcess extends Disposable { this._hasExited = true; this._onExit.fire({ pid, code, signal }); }); + } - return { pid }; + enableInspectPort(): boolean { + if (!this._process) { + return false; + } + + this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`); + + interface ProcessExt { + _debugProcess?(n: number): any; + } + + if (typeof (process)._debugProcess === 'function') { + // use (undocumented) _debugProcess feature of node + (process)._debugProcess!(this._process.pid!); + return true; + } else if (!platform.isWindows) { + // use KILL USR1 on non-windows platforms (fallback) + this._process.kill('SIGUSR1'); + return true; + } else { + // not supported... + return false; + } + } + + kill(): void { + if (!this._process) { + return; + } + this._logService.info(`Killing extension host with pid ${this._process.pid}.`); + this._process.kill(); + } + + async waitForExit(maxWaitTimeMs: number): Promise { + if (!this._process) { + return; + } + const pid = this._process.pid; + this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); + await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); + + if (!this._hasExited) { + // looks like we timed out + this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._process.kill(); + } + } +} + +class UtilityExtensionHostProcess extends Disposable { + + readonly onStdout = Event.None; + readonly onStderr = Event.None; + readonly onError = Event.None; + + readonly _onMessage = this._register(new Emitter()); + readonly onMessage = this._onMessage.event; + + readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); + readonly onExit = this._onExit.event; + + private _process: UtilityProcessProposedApi.UtilityProcess | null = null; + private _hasExited: boolean = false; + + constructor( + public readonly id: string, + @ILogService private readonly _logService: ILogService, + ) { + super(); + } + + start(opts: IExtensionHostProcessOptions): void { + const responseWindow = electron.BrowserWindow.fromId(opts.responseWindowId); + if (!responseWindow || responseWindow.isDestroyed() || responseWindow.webContents.isDestroyed()) { + this._logService.info(`Refusing to create new Extension Host UtilityProcess because requesting window cannot be found...`); + return; + } + + const serviceName = `extensionHost${this.id}`; + const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath; + const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock']; + const execArgv: string[] = opts.execArgv || []; + const env: { [key: string]: any } = { ...opts.env }; + + // Make sure all values are strings, otherwise the process will not start + for (const key of Object.keys(env)) { + env[key] = String(env[key]); + } + + this._logService.info(`Creating new UtilityProcess to start extension host...`); + + this._process = new UtilityProcess(modulePath, args, { serviceName, env, execArgv }); + + this._process.on('spawn', () => { + this._logService.info(`Utility process emits spawn!`); + }); + this._process.on('exit', (code: number) => { + this._logService.info(`Utility process emits exit!`); + this._hasExited = true; + this._onExit.fire({ pid: this._process!.pid!, code, signal: '' }); + }); + const listener = (event: electron.Event, details: electron.Details) => { + if (details.type !== 'Utility') { + return; + } + // Despite the fact that we pass the argument `seviceName`, + // the details have a field called `name` where this value appears + if (details.name === serviceName) { + this._logService.info(`Utility process emits exit!`); + this._hasExited = true; + this._onExit.fire({ pid: this._process!.pid!, code: details.exitCode, signal: '' }); + } + }; + electron.app.on('child-process-gone', listener); + this._register(toDisposable(() => { + electron.app.off('child-process-gone', listener); + })); + + const { port1, port2 } = new electron.MessageChannelMain(); + + this._process.postMessage('port', null, [port2]); + responseWindow.webContents.postMessage(opts.responseChannel, opts.responseNonce, [port1]); } enableInspectPort(): boolean { diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index 2707ecf73d3..101afcd8e76 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -23,6 +23,7 @@ import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { ProcessTimeRunOnceScheduler } from 'vs/base/common/async'; import { boolean } from 'vs/editor/common/config/editorOptions'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; +import { MessagePortMain } from 'electron'; import 'vs/workbench/api/common/extHost.common.services'; import 'vs/workbench/api/node/extHost.node.services'; @@ -102,8 +103,37 @@ let onTerminate = function (reason: string) { nativeExit(); }; -function _createExtHostProtocol(): Promise { - if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) { +function _createExtHostProtocol(): Promise { + if (process.env.VSCODE_WILL_SEND_MESSAGE_PORT) { + + return new Promise((resolve, reject) => { + + const withPorts = (ports: MessagePortMain[]) => { + const port = ports[0]; + const onMessage = new BufferedEmitter(); + port.on('message', (e) => onMessage.fire(VSBuffer.wrap(e.data))); + port.on('close', () => { + onTerminate('renderer closed the MessagePort'); + }); + port.start(); + + resolve({ + onMessage: onMessage.event, + send: message => port.postMessage(message.buffer) + }); + }; + + if ((global).vscodePorts) { + const ports = (global).vscodePorts; + delete (global).vscodePorts; + withPorts(ports); + } else { + (global).vscodePortsCallback = withPorts; + } + + }); + + } else if (process.env.VSCODE_EXTHOST_WILL_SEND_SOCKET) { return new Promise((resolve, reject) => { @@ -220,8 +250,10 @@ async function createExtHostProtocol(): Promise { } } - drain(): Promise { - return protocol.drain(); + async drain(): Promise { + if (protocol.drain) { + return protocol.drain(); + } } }; } diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 0f7f60bbc7d..87be62fa31f 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Server, Socket, createServer } from 'net'; +import { Server, createServer } from 'net'; import { createRandomIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import * as nls from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IRemoteConsoleLog, log } from 'vs/base/common/console'; import { logRemoteEntry, logRemoteEntryIfError } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; +import { BufferedEmitter, PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -44,6 +44,8 @@ import { SerializedError } from 'vs/base/common/errors'; import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { StopWatch } from 'vs/base/common/stopwatch'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { generateUuid } from 'vs/base/common/uuid'; +import { acquirePort } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; export interface ILocalProcessExtensionHostInitData { readonly autoStart: boolean; @@ -91,7 +93,7 @@ class ExtensionHostProcess { this._id = id; } - public start(opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { + public start(opts: IExtensionHostProcessOptions): Promise { return this._extensionHostStarter.start(this._id, opts); } @@ -127,11 +129,9 @@ export class LocalProcessExtensionHost implements IExtensionHost { private _terminating: boolean; // Resources, in order they get acquired/created when .start() is called: - private _namedPipeServer: Server | null; private _inspectPort: number | null; private _extensionHostProcess: ExtensionHostProcess | null; - private _extensionHostConnection: Socket | null; - private _messageProtocol: Promise | null; + private _messageProtocol: Promise | null; private readonly _extensionHostLogFile: URI; @@ -161,17 +161,15 @@ export class LocalProcessExtensionHost implements IExtensionHost { this._lastExtensionHostError = null; this._terminating = false; - this._namedPipeServer = null; this._inspectPort = null; this._extensionHostProcess = null; - this._extensionHostConnection = null; this._messageProtocol = null; this._extensionHostLogFile = joinPath(this._environmentService.extHostLogsPath, `${ExtensionHostLogFileName}.log`); this._toDispose.add(this._onExit); this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); - this._toDispose.add(this._lifecycleService.onDidShutdown(() => this.terminate())); + this._toDispose.add(this._lifecycleService.onDidShutdown(() => this._terminate())); this._toDispose.add(this._extensionHostDebugService.onClose(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { this._nativeHostService.closeWindow(); @@ -185,7 +183,16 @@ export class LocalProcessExtensionHost implements IExtensionHost { } public dispose(): void { - this.terminate(); + this._terminate(); + } + + private _terminate(): void { + if (this._terminating) { + return; + } + this._terminating = true; + + this._toDispose.dispose(); } public start(): Promise | null { @@ -195,172 +202,204 @@ export class LocalProcessExtensionHost implements IExtensionHost { } if (!this._messageProtocol) { - this._messageProtocol = Promise.all([ - this._extensionHostStarter.createExtensionHost(), - this._tryListenOnPipe(), - this._tryFindDebugPort(), - this._shellEnvironmentService.getShellEnv(), - ]).then(([extensionHostCreationResult, pipeName, portNumber, processEnv]) => { - - this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter); - - const env = objects.mixin(processEnv, { - VSCODE_AMD_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess', - VSCODE_PIPE_LOGGING: 'true', - VSCODE_VERBOSE_LOGGING: true, - VSCODE_LOG_NATIVE: this._isExtensionDevHost, - VSCODE_IPC_HOOK_EXTHOST: pipeName, - VSCODE_HANDLES_UNCAUGHT_ERRORS: true, - VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose) - }); - - if (this._environmentService.debugExtensionHost.env) { - objects.mixin(env, this._environmentService.debugExtensionHost.env); - } - - removeDangerousEnvVariables(env); - - if (this._isExtensionDevHost) { - // Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might - // be that dependencies, that otherwise would be cached, get modified. - delete env['VSCODE_CODE_CACHE_PATH']; - } - - const opts = { - env, - // We only detach the extension host on windows. Linux and Mac orphan by default - // and detach under Linux and Mac create another process group. - // We detach because we have noticed that when the renderer exits, its child processes - // (i.e. extension host) are taken down in a brutal fashion by the OS - detached: !!platform.isWindows, - execArgv: undefined as string[] | undefined, - silent: true - }; - - if (portNumber !== 0) { - opts.execArgv = [ - '--nolazy', - (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portNumber - ]; - } else { - opts.execArgv = ['--inspect-port=0']; - } - - if (this._environmentService.extensionTestsLocationURI) { - opts.execArgv.unshift('--expose-gc'); - } - - if (this._environmentService.args['prof-v8-extensions']) { - opts.execArgv.unshift('--prof'); - } - - if (this._environmentService.args['max-memory']) { - opts.execArgv.unshift(`--max-old-space-size=${this._environmentService.args['max-memory']}`); - } - - // Catch all output coming from the extension host process - type Output = { data: string; format: string[] }; - const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.onStdout); - const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.onStderr); - const onOutput = Event.any( - Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })), - Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] })) - ); - - // Debounce all output, so we can render it in the Chrome console as a group - const onDebouncedOutput = Event.debounce(onOutput, (r, o) => { - return r - ? { data: r.data + o.data, format: [...r.format, ...o.format] } - : { data: o.data, format: o.format }; - }, 100); - - // Print out extension host output - onDebouncedOutput(output => { - const inspectorUrlMatch = output.data && output.data.match(/ws:\/\/([^\s]+:(\d+)\/[^\s]+)/); - if (inspectorUrlMatch) { - if (!this._environmentService.isBuilt && !this._isExtensionDevTestFromCli) { - console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color:'); - } - if (!this._inspectPort) { - this._inspectPort = Number(inspectorUrlMatch[2]); - this._onDidSetInspectPort.fire(); - } - } else { - if (!this._isExtensionDevTestFromCli) { - console.group('Extension Host'); - console.log(output.data, ...output.format); - console.groupEnd(); - } - } - }); - - // Support logging from extension host - this._extensionHostProcess.onMessage(msg => { - if (msg && (msg).type === '__$console') { - this._logExtensionHostMessage(msg); - } - }); - - // Lifecycle - - this._extensionHostProcess.onError((e) => this._onExtHostProcessError(e.error)); - this._extensionHostProcess.onExit(({ code, signal }) => this._onExtHostProcessExit(code, signal)); - - // Notify debugger that we are ready to attach to the process if we run a development extension - if (portNumber) { - if (this._isExtensionDevHost && portNumber && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { - this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portNumber); - } - this._inspectPort = portNumber; - this._onDidSetInspectPort.fire(); - } - - // Help in case we fail to start it - let startupTimeoutHandle: any; - if (!this._environmentService.isBuilt && !this._environmentService.remoteAuthority || this._isExtensionDevHost) { - startupTimeoutHandle = setTimeout(() => { - this._logService.error(`[LocalProcessExtensionHost]: Extension host did not start in 10 seconds (debugBrk: ${this._isExtensionDevDebugBrk})`); - - const msg = this._isExtensionDevDebugBrk - ? nls.localize('extensionHost.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.") - : nls.localize('extensionHost.startupFail', "Extension host did not start in 10 seconds, that might be a problem."); - - this._notificationService.prompt(Severity.Warning, msg, - [{ - label: nls.localize('reloadWindow', "Reload Window"), - run: () => this._hostService.reload() - }], - { sticky: true } - ); - }, 10000); - } - - // Initialize extension host process with hand shakes - return this._tryExtHostHandshake(opts).then((protocol) => { - clearTimeout(startupTimeoutHandle); - return protocol; - }); - }); + this._messageProtocol = this._start(); } return this._messageProtocol; } + private async _start(): Promise { + const usesUtilityProcess = await this._extensionHostStarter.usesUtilityProcess(); + + let extensionHostCreationResult: { id: string }; + let listenOnPipeResult: { pipeName: string; namedPipeServer: Server } | undefined; + let portNumber: number; + let processEnv: platform.IProcessEnvironment; + + if (usesUtilityProcess) { + // We will receive a message port + [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([ + this._extensionHostStarter.createExtensionHost(), + this._tryFindDebugPort(), + this._shellEnvironmentService.getShellEnv(), + ]); + } else { + // We will listen on a named pipe + [extensionHostCreationResult, listenOnPipeResult, portNumber, processEnv] = await Promise.all([ + this._extensionHostStarter.createExtensionHost(), + this._tryListenOnPipe(), + this._tryFindDebugPort(), + this._shellEnvironmentService.getShellEnv(), + ]); + } + + this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter); + + const env = objects.mixin(processEnv, { + VSCODE_AMD_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess', + VSCODE_PIPE_LOGGING: 'true', + VSCODE_VERBOSE_LOGGING: true, + VSCODE_LOG_NATIVE: this._isExtensionDevHost, + VSCODE_WILL_SEND_MESSAGE_PORT: listenOnPipeResult ? undefined : 'true', + VSCODE_IPC_HOOK_EXTHOST: listenOnPipeResult ? listenOnPipeResult.pipeName : undefined, + VSCODE_HANDLES_UNCAUGHT_ERRORS: true, + VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose) + }); + + if (this._environmentService.debugExtensionHost.env) { + objects.mixin(env, this._environmentService.debugExtensionHost.env); + } + + removeDangerousEnvVariables(env); + + if (this._isExtensionDevHost) { + // Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might + // be that dependencies, that otherwise would be cached, get modified. + delete env['VSCODE_CODE_CACHE_PATH']; + } + + const opts: IExtensionHostProcessOptions = { + responseWindowId: this._environmentService.window.id, + responseChannel: 'vscode:startExtensionHostMessagePortResult', + responseNonce: generateUuid(), + env, + // We only detach the extension host on windows. Linux and Mac orphan by default + // and detach under Linux and Mac create another process group. + // We detach because we have noticed that when the renderer exits, its child processes + // (i.e. extension host) are taken down in a brutal fashion by the OS + detached: !!platform.isWindows, + execArgv: undefined as string[] | undefined, + silent: true + }; + + if (portNumber !== 0) { + opts.execArgv = [ + '--nolazy', + (this._isExtensionDevDebugBrk ? '--inspect-brk=' : '--inspect=') + portNumber + ]; + } else { + opts.execArgv = ['--inspect-port=0']; + } + + if (this._environmentService.extensionTestsLocationURI) { + opts.execArgv.unshift('--expose-gc'); + } + + if (this._environmentService.args['prof-v8-extensions']) { + opts.execArgv.unshift('--prof'); + } + + if (this._environmentService.args['max-memory']) { + opts.execArgv.unshift(`--max-old-space-size=${this._environmentService.args['max-memory']}`); + } + + // Catch all output coming from the extension host process + type Output = { data: string; format: string[] }; + const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.onStdout); + const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.onStderr); + const onOutput = Event.any( + Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })), + Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] })) + ); + + // Debounce all output, so we can render it in the Chrome console as a group + const onDebouncedOutput = Event.debounce(onOutput, (r, o) => { + return r + ? { data: r.data + o.data, format: [...r.format, ...o.format] } + : { data: o.data, format: o.format }; + }, 100); + + // Print out extension host output + onDebouncedOutput(output => { + const inspectorUrlMatch = output.data && output.data.match(/ws:\/\/([^\s]+:(\d+)\/[^\s]+)/); + if (inspectorUrlMatch) { + if (!this._environmentService.isBuilt && !this._isExtensionDevTestFromCli) { + console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color:'); + } + if (!this._inspectPort) { + this._inspectPort = Number(inspectorUrlMatch[2]); + this._onDidSetInspectPort.fire(); + } + } else { + if (!this._isExtensionDevTestFromCli) { + console.group('Extension Host'); + console.log(output.data, ...output.format); + console.groupEnd(); + } + } + }); + + // Support logging from extension host + this._extensionHostProcess.onMessage(msg => { + if (msg && (msg).type === '__$console') { + this._logExtensionHostMessage(msg); + } + }); + + // Lifecycle + + this._extensionHostProcess.onError((e) => this._onExtHostProcessError(e.error)); + this._extensionHostProcess.onExit(({ code, signal }) => this._onExtHostProcessExit(code, signal)); + + // Notify debugger that we are ready to attach to the process if we run a development extension + if (portNumber) { + if (this._isExtensionDevHost && portNumber && this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { + this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, portNumber); + } + this._inspectPort = portNumber; + this._onDidSetInspectPort.fire(); + } + + // Help in case we fail to start it + let startupTimeoutHandle: any; + if (!this._environmentService.isBuilt && !this._environmentService.remoteAuthority || this._isExtensionDevHost) { + startupTimeoutHandle = setTimeout(() => { + this._logService.error(`[LocalProcessExtensionHost]: Extension host did not start in 10 seconds (debugBrk: ${this._isExtensionDevDebugBrk})`); + + const msg = this._isExtensionDevDebugBrk + ? nls.localize('extensionHost.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.") + : nls.localize('extensionHost.startupFail', "Extension host did not start in 10 seconds, that might be a problem."); + + this._notificationService.prompt(Severity.Warning, msg, + [{ + label: nls.localize('reloadWindow', "Reload Window"), + run: () => this._hostService.reload() + }], + { sticky: true } + ); + }, 10000); + } + + // Initialize extension host process with hand shakes + const protocol = await ( + listenOnPipeResult + ? this._performNamedPipeHandshake(listenOnPipeResult.namedPipeServer, opts) + : this._performMessagePortHandshake(opts) + ); + clearTimeout(startupTimeoutHandle); + return protocol; + } + /** * Start a server (`this._namedPipeServer`) that listens on a named pipe and return the named pipe name. */ - private _tryListenOnPipe(): Promise { - return new Promise((resolve, reject) => { + private _tryListenOnPipe(): Promise<{ pipeName: string; namedPipeServer: Server }> { + return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { const pipeName = createRandomIPCHandle(); - this._namedPipeServer = createServer(); - this._namedPipeServer.on('error', reject); - this._namedPipeServer.listen(pipeName, () => { - if (this._namedPipeServer) { - this._namedPipeServer.removeListener('error', reject); + const namedPipeServer = createServer(); + namedPipeServer.on('error', reject); + namedPipeServer.listen(pipeName, () => { + if (namedPipeServer) { + namedPipeServer.removeListener('error', reject); } - resolve(pipeName); + resolve({ pipeName, namedPipeServer }); }); + this._toDispose.add(toDisposable(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + })); }); } @@ -394,33 +433,82 @@ export class LocalProcessExtensionHost implements IExtensionHost { return port || 0; } - private _tryExtHostHandshake(opts: IExtensionHostProcessOptions): Promise { + private _performMessagePortHandshake(opts: IExtensionHostProcessOptions): Promise { + + // Get ready to acquire the message port from the shared process worker + const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce); + + return new Promise((resolve, reject) => { + + const handle = setTimeout(() => { + reject('The local extension host took longer than 60s to connect.'); + }, 60 * 1000); + + portPromise.then((port) => { + clearTimeout(handle); + + const onMessage = new BufferedEmitter(); + port.onmessage = ((e) => onMessage.fire(VSBuffer.wrap(e.data))); + port.start(); + + resolve({ + onMessage: onMessage.event, + send: message => port.postMessage(message.buffer), + }); + }); + + // Now that the message port listener is installed, start the ext host process + const sw = StopWatch.create(false); + this._extensionHostProcess!.start(opts).then(() => { + const duration = sw.elapsed(); + if (platform.isCI) { + this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); + } + }, (err) => { + // Starting the ext host process resulted in an error + reject(err); + }); + + }).then((protocol) => { + return this._performHandshake(protocol); + }); + } + + private _performNamedPipeHandshake(namedPipeServer: Server, opts: IExtensionHostProcessOptions): Promise { return new Promise((resolve, reject) => { // Wait for the extension host to connect to our named pipe // and wrap the socket in the message passing protocol - let handle = setTimeout(() => { - if (this._namedPipeServer) { - this._namedPipeServer.close(); - this._namedPipeServer = null; + const handle = setTimeout(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); } reject('The local extension host took longer than 60s to connect.'); }, 60 * 1000); - this._namedPipeServer!.on('connection', socket => { + namedPipeServer.on('connection', socket => { clearTimeout(handle); - if (this._namedPipeServer) { - this._namedPipeServer.close(); - this._namedPipeServer = null; + if (namedPipeServer.listening) { + namedPipeServer.close(); } - this._extensionHostConnection = socket; - // using a buffered message protocol here because between now - // and the first time a `then` executes some messages might be lost - // unless we immediately register a listener for `onMessage`. - resolve(new PersistentProtocol(new NodeSocket(this._extensionHostConnection, 'renderer-exthost'))); + const nodeSocket = new NodeSocket(socket, 'renderer-exthost'); + const protocol = new PersistentProtocol(nodeSocket); + + this._toDispose.add(toDisposable(() => { + // Send the extension host a request to terminate itself + // (graceful termination) + protocol.send(createMessageOfType(MessageType.Terminate)); + protocol.flush(); + + socket.end(); + nodeSocket.dispose(); + protocol.dispose(); + })); + + resolve(protocol); }); // Now that the named pipe listener is installed, start the ext host process @@ -436,60 +524,62 @@ export class LocalProcessExtensionHost implements IExtensionHost { }); }).then((protocol) => { + return this._performHandshake(protocol); + }); + } - // 1) wait for the incoming `ready` event and send the initialization data. - // 2) wait for the incoming `initialized` event. - return new Promise((resolve, reject) => { + private _performHandshake(protocol: IMessagePassingProtocol): Promise { + // 1) wait for the incoming `ready` event and send the initialization data. + // 2) wait for the incoming `initialized` event. + return new Promise((resolve, reject) => { - let timeoutHandle: NodeJS.Timer; - const installTimeoutCheck = () => { - timeoutHandle = setTimeout(() => { - reject('The local extenion host took longer than 60s to send its ready message.'); - }, 60 * 1000); - }; - const uninstallTimeoutCheck = () => { - clearTimeout(timeoutHandle); - }; + let timeoutHandle: NodeJS.Timer; + const installTimeoutCheck = () => { + timeoutHandle = setTimeout(() => { + reject('The local extenion host took longer than 60s to send its ready message.'); + }, 60 * 1000); + }; + const uninstallTimeoutCheck = () => { + clearTimeout(timeoutHandle); + }; - // Wait 60s for the ready message - installTimeoutCheck(); + // Wait 60s for the ready message + installTimeoutCheck(); - const disposable = protocol.onMessage(msg => { + const disposable = protocol.onMessage(msg => { - if (isMessageOfType(msg, MessageType.Ready)) { + if (isMessageOfType(msg, MessageType.Ready)) { - // 1) Extension Host is ready to receive messages, initialize it - uninstallTimeoutCheck(); + // 1) Extension Host is ready to receive messages, initialize it + uninstallTimeoutCheck(); - this._createExtHostInitData().then(data => { + this._createExtHostInitData().then(data => { - // Wait 60s for the initialized message - installTimeoutCheck(); + // Wait 60s for the initialized message + installTimeoutCheck(); - protocol.send(VSBuffer.fromString(JSON.stringify(data))); - }); - return; - } + protocol.send(VSBuffer.fromString(JSON.stringify(data))); + }); + return; + } - if (isMessageOfType(msg, MessageType.Initialized)) { + if (isMessageOfType(msg, MessageType.Initialized)) { - // 2) Extension Host is initialized - uninstallTimeoutCheck(); + // 2) Extension Host is initialized + uninstallTimeoutCheck(); - // stop listening for messages here - disposable.dispose(); + // stop listening for messages here + disposable.dispose(); - // Register log channel for exthost log - Registry.as(Extensions.OutputChannels).registerChannel({ id: 'extHostLog', label: nls.localize('extension host Log', "Extension Host"), file: this._extensionHostLogFile, log: true }); + // Register log channel for exthost log + Registry.as(Extensions.OutputChannels).registerChannel({ id: 'extHostLog', label: nls.localize('extension host Log', "Extension Host"), file: this._extensionHostLogFile, log: true }); - // release this promise - resolve(protocol); - return; - } - - console.error(`received unexpected message during handshake phase from the extension host: `, msg); - }); + // release this promise + resolve(protocol); + return; + } + console.error(`received unexpected message during handshake phase from the extension host: `, msg); }); }); @@ -632,58 +722,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { return withNullAsUndefined(this._inspectPort); } - private terminate(): void { - if (this._terminating) { - return; - } - this._terminating = true; - - this._toDispose.dispose(); - - if (!this._messageProtocol) { - // .start() was not called - return; - } - - this._messageProtocol.then((protocol) => { - - // Send the extension host a request to terminate itself - // (graceful termination) - protocol.send(createMessageOfType(MessageType.Terminate)); - - protocol.getSocket().dispose(); - - protocol.dispose(); - - // Give the extension host 10s, after which we will - // try to kill the process and release any resources - setTimeout(() => this._cleanResources(), 10 * 1000); - - }, (err) => { - - // Establishing a protocol with the extension host failed, so - // try to kill the process and release any resources. - this._cleanResources(); - }); - } - - private _cleanResources(): void { - if (this._namedPipeServer) { - this._namedPipeServer.close(); - this._namedPipeServer = null; - } - if (this._extensionHostConnection) { - this._extensionHostConnection.end(); - this._extensionHostConnection = null; - } - if (this._extensionHostProcess) { - this._extensionHostProcess.kill(); - this._extensionHostProcess = null; - } - } - private _onWillShutdown(event: WillShutdownEvent): void { - // If the extension development host was started without debugger attached we need // to communicate this back to the main side to terminate the debug session if (this._isExtensionDevHost && !this._isExtensionDevTestFromCli && !this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) { From 54cf6a154f31750bc6cb77b16b9eb18cae8a32a8 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 24 May 2022 17:01:41 +0200 Subject: [PATCH 254/350] SCMInput enablement proposed api (#150270) --- src/vs/workbench/api/browser/mainThreadSCM.ts | 10 ++++++++++ .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostSCM.ts | 19 ++++++++++++++++++ .../contrib/scm/browser/scmViewPane.ts | 7 +++++++ src/vs/workbench/contrib/scm/common/scm.ts | 3 +++ .../contrib/scm/common/scmService.ts | 14 +++++++++++++ .../common/extensionsApiProposals.ts | 1 + src/vscode-dts/vscode.proposed.scmInput.d.ts | 20 +++++++++++++++++++ 8 files changed, 75 insertions(+) create mode 100644 src/vscode-dts/vscode.proposed.scmInput.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 3cb6a499c8c..5ac8c935667 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -421,6 +421,16 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.placeholder = placeholder; } + $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): void { + const repository = this._repositories.get(sourceControlHandle); + + if (!repository) { + return; + } + + repository.input.enabled = enabled; + } + $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): 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 33fcc3fb351..c7ebcd110f8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1187,6 +1187,7 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxValue(sourceControlHandle: number, value: string): void; $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; + $setInputBoxEnablement(sourceControlHandle: number, enabled: boolean): void; $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 49a54bc71fe..b65c52714b0 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -249,6 +249,25 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { this.#proxy.$setValidationProviderIsEnabled(this._sourceControlHandle, !!fn); } + private _enabled: boolean = true; + + get enabled(): boolean { + checkProposedApiEnabled(this._extension, 'scmInput'); + return this._enabled; + } + + set enabled(enabled: boolean) { + checkProposedApiEnabled(this._extension, 'scmInput'); + enabled = !!enabled; + + if (this._enabled === enabled) { + return; + } + + this._enabled = enabled; + this.#proxy.$setInputBoxEnablement(this._sourceControlHandle, enabled); + } + private _visible: boolean = true; get visible(): boolean { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a3059234eb1..53c6abecef4 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1862,6 +1862,13 @@ class SCMInputWidget extends Disposable { this.repositoryDisposables.add(input.repository.provider.onDidChangeCommitTemplate(updateTemplate, this)); updateTemplate(); + // Update input enablement + const updateEnablement = (enabled: boolean) => { + this.inputEditor.updateOptions({ readOnly: enabled }); + }; + this.repositoryDisposables.add(input.onDidChangeEnablement(enabled => updateEnablement(enabled))); + updateEnablement(input.enabled); + // Save model this.model = { input, textModel }; } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index e180e8e9d3c..9d18d25a131 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -121,6 +121,9 @@ export interface ISCMInput { validateInput: IInputValidator; readonly onDidChangeValidateInput: Event; + enabled: boolean; + readonly onDidChangeEnablement: Event; + visible: boolean; readonly onDidChangeVisibility: Event; diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 7877ba1d34e..73da6c00923 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -38,6 +38,20 @@ class SCMInput implements ISCMInput { private readonly _onDidChangePlaceholder = new Emitter(); readonly onDidChangePlaceholder: Event = this._onDidChangePlaceholder.event; + private _enabled = true; + + get enabled(): boolean { + return this._enabled; + } + + set enabled(enabled: boolean) { + this._enabled = enabled; + this._onDidChangeEnablement.fire(enabled); + } + + private readonly _onDidChangeEnablement = new Emitter(); + readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; + private _visible = true; get visible(): boolean { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 99311836683..3c4bf725a84 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -46,6 +46,7 @@ export const allApiProposals = Object.freeze({ quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', + scmInput: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmInput.d.ts', scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts', scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts', taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts', diff --git a/src/vscode-dts/vscode.proposed.scmInput.d.ts b/src/vscode-dts/vscode.proposed.scmInput.d.ts new file mode 100644 index 00000000000..6efdb57ae45 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.scmInput.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/150268 + + /** + * Represents the input box in the Source Control viewlet. + */ + export interface SourceControlInputBox { + + /** + * Controls whether the input box is enabled (default is `true`). + */ + enabled: boolean; + } +} From ac0a282547d16089582d7506e721415954505733 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Tue, 24 May 2022 08:33:53 -0700 Subject: [PATCH 255/350] Fix default override model value (#150232) --- .../settingsEditorSettingIndicators.ts | 6 +++--- .../preferences/browser/settingsTree.ts | 2 +- .../preferences/browser/settingsTreeModels.ts | 19 +++++++++++++++++-- .../preferences/common/preferences.ts | 2 +- .../preferences/common/preferencesModels.ts | 2 +- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 2f5cfdd2caa..7f5d1e698ed 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -122,7 +122,7 @@ export class SettingsTreeIndicatorsLabel { updateDefaultOverrideIndicator(element: SettingsTreeSettingElement) { this.defaultOverrideIndicatorElement.style.display = 'none'; - const defaultValueSource = element.setting.defaultValueSource; + const defaultValueSource = element.defaultValueSource; if (defaultValueSource) { this.defaultOverrideIndicatorElement.style.display = 'inline'; if (typeof defaultValueSource !== 'string' && defaultValueSource.id !== element.setting.extensionInfo?.id) { @@ -157,8 +157,8 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, } // Add default override indicator text - if (element.setting.defaultValueSource) { - const defaultValueSource = element.setting.defaultValueSource; + if (element.defaultValueSource) { + const defaultValueSource = element.defaultValueSource; if (typeof defaultValueSource !== 'string' && defaultValueSource.id !== element.setting.extensionInfo?.id) { const extensionSource = defaultValueSource.displayName ?? defaultValueSource.id; ariaLabelSections.push(localize('defaultOverriddenDetails', "Default setting value overridden by {0}", extensionSource)); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 5a9aa77dcf2..b65d169f75c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1115,7 +1115,7 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I template.elementDisposables.add(template.button.onDidClick(() => { if (isLanguageTagSetting) { - this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}:${plainKey}`); + this._onApplyFilter.fire(`@${LANGUAGE_SETTING_TAG}${plainKey}`); } else { this._onDidOpenSettings.fire(dataElement.setting.key); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index b16ea3ac942..aa4cce1ae2b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -18,7 +18,7 @@ import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_S import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry, IExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -129,6 +129,12 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { */ defaultValue?: any; + /** + * The source of the default value to display. + * This value also accounts for extension-contributed language-specific default value overrides. + */ + defaultValueSource: string | IExtensionInfo | undefined; + /** * Whether the setting is configured in the selected scope. */ @@ -146,7 +152,12 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { tags?: Set; overriddenScopeList: string[] = []; + + /** + * For each language that contributes setting values or default overrides, we can see those values here. + */ languageOverrideValues: Map> = new Map>(); + description!: string; valueType!: SettingValueType; @@ -219,6 +230,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } + // The user might have added, removed, or modified a language filter, + // so we reset the default value source to the non-language-specific default value source for now. + this.defaultValueSource = this.setting.nonLanguageSpecificDefaultValueSource; + if (inspected.policyValue) { this.hasPolicyValue = true; isConfigured = false; // The user did not manually configure the setting themselves. @@ -236,7 +251,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { const registryValues = Registry.as(Extensions.Configuration).getConfigurationDefaultsOverrides(); const overrideValueSource = registryValues.get(`[${languageSelector}]`)?.valuesSources?.get(this.setting.key); if (overrideValueSource) { - this.setting.defaultValueSource = overrideValueSource; + this.defaultValueSource = overrideValueSource; } } else { this.scopeValue = isConfigured && inspected[targetSelector]; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 5aaf1198fe4..06b43562999 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -87,7 +87,7 @@ export interface ISetting { enumItemLabels?: string[]; allKeysAreBoolean?: boolean; editPresentation?: EditPresentationTypes; - defaultValueSource?: string | IExtensionInfo; + nonLanguageSpecificDefaultValueSource?: string | IExtensionInfo; isLanguageTagSetting?: boolean; categoryOrder?: number; categoryLabel?: string; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index d93923deb81..9fb02061b6e 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -751,7 +751,7 @@ export class DefaultSettings extends Disposable { allKeysAreBoolean, editPresentation: prop.editPresentation, order: prop.order, - defaultValueSource, + nonLanguageSpecificDefaultValueSource: defaultValueSource, isLanguageTagSetting, categoryLabel, categoryOrder From 9950ba00d9bd21fa46f86fbbd5ac88b6c021e838 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 24 May 2022 18:52:13 +0200 Subject: [PATCH 256/350] Extract connection strategies to two classes --- .../localProcessExtensionHost.ts | 313 +++++++++--------- 1 file changed, 165 insertions(+), 148 deletions(-) diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 87be62fa31f..8fdad2df069 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -10,7 +10,7 @@ import * as nls from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -210,28 +210,23 @@ export class LocalProcessExtensionHost implements IExtensionHost { private async _start(): Promise { const usesUtilityProcess = await this._extensionHostStarter.usesUtilityProcess(); - - let extensionHostCreationResult: { id: string }; - let listenOnPipeResult: { pipeName: string; namedPipeServer: Server } | undefined; - let portNumber: number; - let processEnv: platform.IProcessEnvironment; - if (usesUtilityProcess) { - // We will receive a message port - [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([ - this._extensionHostStarter.createExtensionHost(), - this._tryFindDebugPort(), - this._shellEnvironmentService.getShellEnv(), - ]); + const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); + return this._startWithCommunication(communication); } else { - // We will listen on a named pipe - [extensionHostCreationResult, listenOnPipeResult, portNumber, processEnv] = await Promise.all([ - this._extensionHostStarter.createExtensionHost(), - this._tryListenOnPipe(), - this._tryFindDebugPort(), - this._shellEnvironmentService.getShellEnv(), - ]); + const communication = this._toDispose.add(new ExtHostNamedPipeCommunication(this._logService)); + return this._startWithCommunication(communication); } + } + + private async _startWithCommunication(communication: IExtHostCommunication): Promise { + + const [extensionHostCreationResult, communicationPreparedData, portNumber, processEnv] = await Promise.all([ + this._extensionHostStarter.createExtensionHost(), + communication.prepare(), + this._tryFindDebugPort(), + this._shellEnvironmentService.getShellEnv(), + ]); this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter); @@ -240,8 +235,6 @@ export class LocalProcessExtensionHost implements IExtensionHost { VSCODE_PIPE_LOGGING: 'true', VSCODE_VERBOSE_LOGGING: true, VSCODE_LOG_NATIVE: this._isExtensionDevHost, - VSCODE_WILL_SEND_MESSAGE_PORT: listenOnPipeResult ? undefined : 'true', - VSCODE_IPC_HOOK_EXTHOST: listenOnPipeResult ? listenOnPipeResult.pipeName : undefined, VSCODE_HANDLES_UNCAUGHT_ERRORS: true, VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose) }); @@ -371,38 +364,12 @@ export class LocalProcessExtensionHost implements IExtensionHost { } // Initialize extension host process with hand shakes - const protocol = await ( - listenOnPipeResult - ? this._performNamedPipeHandshake(listenOnPipeResult.namedPipeServer, opts) - : this._performMessagePortHandshake(opts) - ); + const protocol = await communication.establishProtocol(communicationPreparedData, this._extensionHostProcess, opts); + await this._performHandshake(protocol); clearTimeout(startupTimeoutHandle); return protocol; } - /** - * Start a server (`this._namedPipeServer`) that listens on a named pipe and return the named pipe name. - */ - private _tryListenOnPipe(): Promise<{ pipeName: string; namedPipeServer: Server }> { - return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { - const pipeName = createRandomIPCHandle(); - - const namedPipeServer = createServer(); - namedPipeServer.on('error', reject); - namedPipeServer.listen(pipeName, () => { - if (namedPipeServer) { - namedPipeServer.removeListener('error', reject); - } - resolve({ pipeName, namedPipeServer }); - }); - this._toDispose.add(toDisposable(() => { - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - })); - }); - } - /** * Find a free port if extension host debugging is enabled. */ @@ -433,105 +400,10 @@ export class LocalProcessExtensionHost implements IExtensionHost { return port || 0; } - private _performMessagePortHandshake(opts: IExtensionHostProcessOptions): Promise { - - // Get ready to acquire the message port from the shared process worker - const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce); - - return new Promise((resolve, reject) => { - - const handle = setTimeout(() => { - reject('The local extension host took longer than 60s to connect.'); - }, 60 * 1000); - - portPromise.then((port) => { - clearTimeout(handle); - - const onMessage = new BufferedEmitter(); - port.onmessage = ((e) => onMessage.fire(VSBuffer.wrap(e.data))); - port.start(); - - resolve({ - onMessage: onMessage.event, - send: message => port.postMessage(message.buffer), - }); - }); - - // Now that the message port listener is installed, start the ext host process - const sw = StopWatch.create(false); - this._extensionHostProcess!.start(opts).then(() => { - const duration = sw.elapsed(); - if (platform.isCI) { - this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); - } - }, (err) => { - // Starting the ext host process resulted in an error - reject(err); - }); - - }).then((protocol) => { - return this._performHandshake(protocol); - }); - } - - private _performNamedPipeHandshake(namedPipeServer: Server, opts: IExtensionHostProcessOptions): Promise { - - return new Promise((resolve, reject) => { - - // Wait for the extension host to connect to our named pipe - // and wrap the socket in the message passing protocol - const handle = setTimeout(() => { - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - reject('The local extension host took longer than 60s to connect.'); - }, 60 * 1000); - - namedPipeServer.on('connection', socket => { - - clearTimeout(handle); - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - - const nodeSocket = new NodeSocket(socket, 'renderer-exthost'); - const protocol = new PersistentProtocol(nodeSocket); - - this._toDispose.add(toDisposable(() => { - // Send the extension host a request to terminate itself - // (graceful termination) - protocol.send(createMessageOfType(MessageType.Terminate)); - protocol.flush(); - - socket.end(); - nodeSocket.dispose(); - protocol.dispose(); - })); - - resolve(protocol); - }); - - // Now that the named pipe listener is installed, start the ext host process - const sw = StopWatch.create(false); - this._extensionHostProcess!.start(opts).then(() => { - const duration = sw.elapsed(); - if (platform.isCI) { - this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); - } - }, (err) => { - // Starting the ext host process resulted in an error - reject(err); - }); - - }).then((protocol) => { - return this._performHandshake(protocol); - }); - } - - private _performHandshake(protocol: IMessagePassingProtocol): Promise { + private _performHandshake(protocol: IMessagePassingProtocol): Promise { // 1) wait for the incoming `ready` event and send the initialization data. // 2) wait for the incoming `initialized` event. - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let timeoutHandle: NodeJS.Timer; const installTimeoutCheck = () => { @@ -575,7 +447,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { Registry.as(Extensions.OutputChannels).registerChannel({ id: 'extHostLog', label: nls.localize('extension host Log', "Extension Host"), file: this._extensionHostLogFile, log: true }); // release this promise - resolve(protocol); + resolve(); return; } @@ -731,3 +603,148 @@ export class LocalProcessExtensionHost implements IExtensionHost { } } } + +interface IExtHostCommunication { + prepare(): Promise; + establishProtocol(prepared: T, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise; +} + +class ExtHostMessagePortCommunication extends Disposable implements IExtHostCommunication { + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + async prepare(): Promise { + } + + establishProtocol(prepared: void, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise { + opts.env['VSCODE_WILL_SEND_MESSAGE_PORT'] = 'true'; + + // Get ready to acquire the message port from the shared process worker + const portPromise = acquirePort(undefined /* we trigger the request via service call! */, opts.responseChannel, opts.responseNonce); + + return new Promise((resolve, reject) => { + + const handle = setTimeout(() => { + reject('The local extension host took longer than 60s to connect.'); + }, 60 * 1000); + + portPromise.then((port) => { + clearTimeout(handle); + + const onMessage = new BufferedEmitter(); + port.onmessage = ((e) => onMessage.fire(VSBuffer.wrap(e.data))); + port.start(); + + resolve({ + onMessage: onMessage.event, + send: message => port.postMessage(message.buffer), + }); + }); + + // Now that the message port listener is installed, start the ext host process + const sw = StopWatch.create(false); + extensionHostProcess.start(opts).then(() => { + const duration = sw.elapsed(); + if (platform.isCI) { + this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); + } + }, (err) => { + // Starting the ext host process resulted in an error + reject(err); + }); + }); + } +} + +interface INamedPipePreparedData { + pipeName: string; + namedPipeServer: Server; +} + +class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication { + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + prepare(): Promise { + return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { + const pipeName = createRandomIPCHandle(); + + const namedPipeServer = createServer(); + namedPipeServer.on('error', reject); + namedPipeServer.listen(pipeName, () => { + if (namedPipeServer) { + namedPipeServer.removeListener('error', reject); + } + resolve({ pipeName, namedPipeServer }); + }); + this._register(toDisposable(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + })); + }); + } + + establishProtocol(prepared: INamedPipePreparedData, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise { + const { namedPipeServer, pipeName } = prepared; + + opts.env['VSCODE_IPC_HOOK_EXTHOST'] = pipeName; + + return new Promise((resolve, reject) => { + + // Wait for the extension host to connect to our named pipe + // and wrap the socket in the message passing protocol + const handle = setTimeout(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + reject('The local extension host took longer than 60s to connect.'); + }, 60 * 1000); + + namedPipeServer.on('connection', (socket) => { + + clearTimeout(handle); + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + + const nodeSocket = new NodeSocket(socket, 'renderer-exthost'); + const protocol = new PersistentProtocol(nodeSocket); + + this._register(toDisposable(() => { + // Send the extension host a request to terminate itself + // (graceful termination) + protocol.send(createMessageOfType(MessageType.Terminate)); + protocol.flush(); + + socket.end(); + nodeSocket.dispose(); + protocol.dispose(); + })); + + resolve(protocol); + }); + + // Now that the named pipe listener is installed, start the ext host process + const sw = StopWatch.create(false); + extensionHostProcess.start(opts).then(() => { + const duration = sw.elapsed(); + if (platform.isCI) { + this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); + } + }, (err) => { + // Starting the ext host process resulted in an error + reject(err); + }); + + }); + } +} From 9322fd543d401efa136d24656c8be455edd7c010 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 10:13:04 -0700 Subject: [PATCH 257/350] Fix test to pass --- .../src/singlefolder-tests/interactiveWindow.test.ts | 2 +- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index eafc18804fd..0890418484c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -32,7 +32,7 @@ async function createInteractiveWindow(kernel: Kernel) { setup(async function () { // there should be ONE default kernel in this suite - defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); + defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel', 'interactive'); testDisposables.push(defaultKernel.controller); await saveAllFilesAndCloseAll(); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index b68eb587922..8c413aaf9fb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -35,8 +35,8 @@ export class Kernel { readonly associatedNotebooks = new Set(); - constructor(id: string, label: string) { - this.controller = vscode.notebooks.createNotebookController(id, 'notebookCoreTest', label); + constructor(id: string, label: string, viewType: string = 'notebookCoreTest') { + this.controller = vscode.notebooks.createNotebookController(id, viewType, label); this.controller.executeHandler = this._execute.bind(this); this.controller.supportsExecutionOrder = true; this.controller.supportedLanguages = ['typescript', 'javascript']; From 1b88f28b495c90bb656bc3fb710a49d662074a3e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 24 May 2022 10:32:27 -0700 Subject: [PATCH 258/350] pass in registry --- .../contrib/tasks/common/taskConfiguration.ts | 12 ++++++------ .../tasks/common/taskDefinitionRegistry.ts | 2 +- .../tasks/test/common/taskConfiguration.test.ts | 16 +++++++++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 1fd6ca72d5b..7bf202d5c8e 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -20,7 +20,7 @@ import { import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from './tasks'; -import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; +import { ITaskDefinitionRegistry, TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { URI } from 'vs/base/common/uri'; import { USER_TASKS_GROUP_KEY, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; @@ -1389,7 +1389,7 @@ namespace ConfiguringTask { customize: string; } - export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource, testTaskDefinition?: Tasks.TaskDefinition): Tasks.ConfiguringTask | undefined { + export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource, registry?: Partial): Tasks.ConfiguringTask | undefined { if (!external) { return undefined; } @@ -1399,7 +1399,7 @@ namespace ConfiguringTask { context.problemReporter.error(nls.localize('ConfigurationParser.noTaskType', 'Error: tasks configuration must have a type property. The configuration will be ignored.\n{0}\n', JSON.stringify(external, null, 4))); return undefined; } - let typeDeclaration = type ? testTaskDefinition || TaskDefinitionRegistry.get(type) : undefined; + let typeDeclaration = type ? registry?.get?.(type) || TaskDefinitionRegistry.get(type) : undefined; if (!typeDeclaration) { let message = nls.localize('ConfigurationParser.noTypeDefinition', 'Error: there is no registered task type \'{0}\'. Did you miss installing an extension that provides a corresponding task provider?', type); context.problemReporter.error(message); @@ -1672,7 +1672,7 @@ export namespace TaskParser { process: ProcessExecutionSupportedContext }; - export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource, testTaskDefinition?: Tasks.TaskDefinition): TaskParseResult { + export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource, registry?: Partial): TaskParseResult { let result: TaskParseResult = { custom: [], configured: [] }; if (!externals) { return result; @@ -1683,7 +1683,7 @@ export namespace TaskParser { const baseLoadIssues = Objects.deepClone(context.taskLoadIssues); for (let index = 0; index < externals.length; index++) { let external = externals[index]; - const definition = external.type ? testTaskDefinition || TaskDefinitionRegistry.get(external.type) : undefined; + const definition = external.type ? registry?.get?.(external.type) || TaskDefinitionRegistry.get(external.type) : undefined; let typeNotSupported: boolean = false; if (definition && definition.when && !context.contextKeyService.contextMatchesRules(definition.when)) { typeNotSupported = true; @@ -1743,7 +1743,7 @@ export namespace TaskParser { result.custom.push(customTask); } } else { - let configuredTask = ConfiguringTask.from(external, context, index, source, testTaskDefinition); + let configuredTask = ConfiguringTask.from(external, context, index, source, registry); if (configuredTask) { configuredTask.addTaskLoadMessages(context.taskLoadIssues); result.configured.push(configuredTask); diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 8d612f6d9a6..340990cb62c 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -99,7 +99,7 @@ export interface ITaskDefinitionRegistry { onDefinitionsChanged: Event; } -class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { +export class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { private taskTypes: IStringDictionary; private readyPromise: Promise; diff --git a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 95fb3eb7b55..4db99d3955c 100644 --- a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -19,6 +19,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { IContext } from 'vs/platform/contextkey/common/contextkey'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ITaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), @@ -1772,7 +1773,19 @@ class TestNamedProblemMatcher implements Partial { class TestParseContext implements Partial { } +class TestTaskDefinitionRegistry implements Partial { + private _task: Tasks.TaskDefinition | undefined; + public get(key: string): Tasks.TaskDefinition { + return this._task!; + } + public set(task: Tasks.TaskDefinition) { + this._task = task; + } +} + suite('To task configuration from', () => { + + const TaskDefinitionRegistry = new TestTaskDefinitionRegistry(); let instantiationService: TestInstantiationService; let parseContext: ParseContext; let namedProblemMatcher: NamedProblemMatcher; @@ -1827,7 +1840,8 @@ suite('To task configuration from', () => { suite('ConfiguredTask', () => { test('returns expected result', () => { const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }, { taskName: 'task 2', command: 'echo test', type: 'any', label: 'task 2' }]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, { extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); + TaskDefinitionRegistry.set({ extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); + const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, TaskDefinitionRegistry); assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); }); }); From 397fa12a6c86ea3e9ce3a44826bb9b638a3048d5 Mon Sep 17 00:00:00 2001 From: Song Xie Date: Tue, 24 May 2022 10:33:48 -0700 Subject: [PATCH 259/350] [easy] Fix a typo for "synchronizing" in log string (#150236) * [easy] Fix a typo for "synchronizing" in log string * Include artifacts from yarn build compile --- build/lib/builtInExtensions.js | 2 +- build/lib/builtInExtensions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index 221e5ba7516..2671a5a3a6c 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -97,7 +97,7 @@ function writeControlFile(control) { fs.writeFileSync(controlFilePath, JSON.stringify(control, null, 2)); } function getBuiltInExtensions() { - log('Syncronizing built-in extensions...'); + log('Synchronizing built-in extensions...'); log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); const control = readControlFile(); const streams = []; diff --git a/build/lib/builtInExtensions.ts b/build/lib/builtInExtensions.ts index f5a03d1304e..1abc85b3b09 100644 --- a/build/lib/builtInExtensions.ts +++ b/build/lib/builtInExtensions.ts @@ -136,7 +136,7 @@ function writeControlFile(control: IControlFile): void { } export function getBuiltInExtensions(): Promise { - log('Syncronizing built-in extensions...'); + log('Synchronizing built-in extensions...'); log(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); const control = readControlFile(); From b6aaee8c617f6861ddd355bab7d702264be9dc73 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 24 May 2022 10:39:21 -0700 Subject: [PATCH 260/350] remove a bunch of casts --- .../test/common/taskConfiguration.test.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts index 4db99d3955c..bf496555a15 100644 --- a/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/taskConfiguration.test.ts @@ -1783,8 +1783,9 @@ class TestTaskDefinitionRegistry implements Partial { } } -suite('To task configuration from', () => { - +suite('Task configuration conversions', () => { + const globals = {} as Globals; + const taskConfigSource = {} as TaskConfigSource; const TaskDefinitionRegistry = new TestTaskDefinitionRegistry(); let instantiationService: TestInstantiationService; let parseContext: ParseContext; @@ -1801,7 +1802,7 @@ suite('To task configuration from', () => { parseContext.namedProblemMatchers = { 'real': namedProblemMatcher }; parseContext.uuidMap = new UUIDMap(); }); - suite('ProblemMatcher config', () => { + suite('ProblemMatcherConverter.from', () => { test('returns [] and an error for an unknown problem matcher', () => { const result = (ProblemMatcherConverter.from('$fake', parseContext)); assert.deepEqual(result.value, []); @@ -1819,21 +1820,24 @@ suite('To task configuration from', () => { assert.deepEqual(result.value, [{ "label": "real label", "applyTo": ApplyToKind.closedDocuments }]); }); }); - suite('TaskParser external config', () => { + suite('TaskParser.from', () => { suite('CustomTask', () => { suite('incomplete config reports an appropriate error for missing', () => { test('name', () => { - const result = TaskParser.from([{} as CustomTask], {} as Globals, parseContext, {} as TaskConfigSource); + const result = TaskParser.from([{} as CustomTask], globals, parseContext, taskConfigSource); assertTaskParseResult(result, undefined, problemReporter, 'Error: a task must provide a label property'); }); test('command', () => { - const result = TaskParser.from([{ taskName: 'task' } as CustomTask], {} as Globals, parseContext, {} as TaskConfigSource); + const result = TaskParser.from([{ taskName: 'task' } as CustomTask], globals, parseContext, taskConfigSource); assertTaskParseResult(result, undefined, problemReporter, "Error: the task 'task' doesn't define a command"); }); }); test('returns expected result', () => { - const expected = [{ taskName: 'task', command: 'echo test' } as CustomTask, { taskName: 'task 2', command: 'echo test' } as CustomTask]; - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource); + const expected = [ + { taskName: 'task', command: 'echo test' } as CustomTask, + { taskName: 'task 2', command: 'echo test' } as CustomTask + ]; + const result = TaskParser.from(expected, globals, parseContext, taskConfigSource); assertTaskParseResult(result, { custom: expected }, problemReporter, undefined); }); }); @@ -1841,7 +1845,7 @@ suite('To task configuration from', () => { test('returns expected result', () => { const expected = [{ taskName: 'task', command: 'echo test', type: 'any', label: 'task' }, { taskName: 'task 2', command: 'echo test', type: 'any', label: 'task 2' }]; TaskDefinitionRegistry.set({ extensionId: 'registered', taskType: 'any', properties: {} } as Tasks.TaskDefinition); - const result = TaskParser.from(expected, {} as Globals, parseContext, {} as TaskConfigSource, TaskDefinitionRegistry); + const result = TaskParser.from(expected, globals, parseContext, taskConfigSource, TaskDefinitionRegistry); assertTaskParseResult(result, { configured: expected }, problemReporter, undefined); }); }); From bf6971ac461b2c5a99765e437f5019d7e7c19213 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 24 May 2022 19:43:27 +0200 Subject: [PATCH 261/350] Fix enablement check (#150297) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 53c6abecef4..a738e3d7a21 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1864,7 +1864,7 @@ class SCMInputWidget extends Disposable { // Update input enablement const updateEnablement = (enabled: boolean) => { - this.inputEditor.updateOptions({ readOnly: enabled }); + this.inputEditor.updateOptions({ readOnly: !enabled }); }; this.repositoryDisposables.add(input.onDidChangeEnablement(enabled => updateEnablement(enabled))); updateEnablement(input.enabled); From fa120bbacde9c8018e78d79d299534f752e5e659 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 24 May 2022 19:46:45 +0200 Subject: [PATCH 262/350] Have the renderer side decide if it wants to use `UtilityProcess` --- .../extensions/common/extensionHostStarter.ts | 4 ++-- .../electron-main/extensionHostStarter.ts | 20 +++++++++++-------- .../localProcessExtensionHost.ts | 12 ++++++++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/extensions/common/extensionHostStarter.ts b/src/vs/platform/extensions/common/extensionHostStarter.ts index 33f3192f206..5d2a9e3a83a 100644 --- a/src/vs/platform/extensions/common/extensionHostStarter.ts +++ b/src/vs/platform/extensions/common/extensionHostStarter.ts @@ -30,8 +30,8 @@ export interface IExtensionHostStarter { onDynamicError(id: string): Event<{ error: SerializedError }>; onDynamicExit(id: string): Event<{ code: number; signal: string }>; - usesUtilityProcess(): Promise; - createExtensionHost(): Promise<{ id: string }>; + canUseUtilityProcess(): Promise; + createExtensionHost(useUtilityProcess: boolean): Promise<{ id: string }>; start(id: string, opts: IExtensionHostProcessOptions): Promise; enableInspectPort(id: string): Promise; kill(id: string): Promise; diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index 8ef9c5a0da7..1647d46f55b 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -36,7 +36,7 @@ declare namespace UtilityProcessProposedApi { } } const UtilityProcess = ((electron as any).UtilityProcess); -const canUseUtilityProcess = (typeof UtilityProcess !== 'undefined') && !!process.env['VSCODE_USE_UTILITY_PROCESS']; +const canUseUtilityProcess = (typeof UtilityProcess !== 'undefined'); export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { _serviceBrand: undefined; @@ -91,20 +91,24 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter return this._getExtHost(id).onExit; } - async usesUtilityProcess(): Promise { + async canUseUtilityProcess(): Promise { return canUseUtilityProcess; } - async createExtensionHost(): Promise<{ id: string }> { + async createExtensionHost(useUtilityProcess: boolean): Promise<{ id: string }> { if (this._shutdown) { throw canceled(); } const id = String(++ExtensionHostStarter._lastId); - const extHost = ( - canUseUtilityProcess - ? new UtilityExtensionHostProcess(id, this._logService) - : new ExtensionHostProcess(id, this._logService) - ); + let extHost: UtilityExtensionHostProcess | ExtensionHostProcess; + if (useUtilityProcess) { + if (!canUseUtilityProcess) { + throw new Error(`Cannot use UtilityProcess!`); + } + extHost = new UtilityExtensionHostProcess(id, this._logService); + } else { + extHost = new ExtensionHostProcess(id, this._logService); + } this._extHosts.set(id, extHost); extHost.onExit(({ pid, code, signal }) => { this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 8fdad2df069..c43ae39d09f 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -209,8 +209,8 @@ export class LocalProcessExtensionHost implements IExtensionHost { } private async _start(): Promise { - const usesUtilityProcess = await this._extensionHostStarter.usesUtilityProcess(); - if (usesUtilityProcess) { + const canUseUtilityProcess = await this._extensionHostStarter.canUseUtilityProcess(); + if (canUseUtilityProcess && process.env['VSCODE_USE_UTILITY_PROCESS']) { const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); return this._startWithCommunication(communication); } else { @@ -222,7 +222,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { private async _startWithCommunication(communication: IExtHostCommunication): Promise { const [extensionHostCreationResult, communicationPreparedData, portNumber, processEnv] = await Promise.all([ - this._extensionHostStarter.createExtensionHost(), + this._extensionHostStarter.createExtensionHost(communication.useUtilityProcess), communication.prepare(), this._tryFindDebugPort(), this._shellEnvironmentService.getShellEnv(), @@ -605,12 +605,15 @@ export class LocalProcessExtensionHost implements IExtensionHost { } interface IExtHostCommunication { + readonly useUtilityProcess: boolean; prepare(): Promise; establishProtocol(prepared: T, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise; } class ExtHostMessagePortCommunication extends Disposable implements IExtHostCommunication { + readonly useUtilityProcess = true; + constructor( @ILogService private readonly _logService: ILogService ) { @@ -621,6 +624,7 @@ class ExtHostMessagePortCommunication extends Disposable implements IExtHostComm } establishProtocol(prepared: void, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise { + opts.env['VSCODE_WILL_SEND_MESSAGE_PORT'] = 'true'; // Get ready to acquire the message port from the shared process worker @@ -667,6 +671,8 @@ interface INamedPipePreparedData { class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication { + readonly useUtilityProcess = false; + constructor( @ILogService private readonly _logService: ILogService ) { From b79d02db5c11960188a43d8bfa12001ab84bb3b8 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 11:01:51 -0700 Subject: [PATCH 263/350] Add test for scrolling --- .../interactiveWindow.test.ts | 37 +++++++++++++++---- .../src/singlefolder-tests/notebook.test.ts | 9 ++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 0890418484c..6880607ca7d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -24,6 +24,22 @@ async function createInteractiveWindow(kernel: Kernel) { return notebookEditor; } +async function addCell(code: string, notebook: vscode.NotebookDocument) { + const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, code, 'typescript'); + const edit = vscode.NotebookEdit.insertCells(notebook.cellCount, [cell]); + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.set(notebook.uri, [edit]); + await vscode.workspace.applyEdit(workspaceEdit); + return notebook.cellAt(notebook.cellCount - 1); +} + +async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { + const cell = await addCell(code, notebook); + await vscode.commands.executeCommand('notebook.execute'); + assert.strictEqual(cell.outputs.length, 1, 'execute failed'); + return cell; +} + (vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Interactive Window', function () { @@ -49,17 +65,24 @@ async function createInteractiveWindow(kernel: Kernel) { assert.ok(notebookEditor); // Try adding a cell and running it. - const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print foo', 'typescript'); - const edit = vscode.NotebookEdit.insertCells(0, [cell]); - const workspaceEdit = new vscode.WorkspaceEdit(); - workspaceEdit.set(notebookEditor.notebook.uri, [edit]); - await vscode.workspace.applyEdit(workspaceEdit); + await addCell('print foo', notebookEditor.notebook); assert.strictEqual(notebookEditor.notebook.cellCount, 1); assert.strictEqual(notebookEditor.notebook.cellAt(0).kind, vscode.NotebookCellKind.Code); + }); - await vscode.commands.executeCommand('notebook.execute'); - assert.strictEqual(notebookEditor.notebook.cellAt(0).outputs.length, 1, 'should execute'); + test('Interactive window scrolls after execute', async () => { + assert.ok(vscode.workspace.workspaceFolders); + const notebookEditor = await createInteractiveWindow(defaultKernel); + assert.ok(notebookEditor); + + // Run and add a bunch of cells + for (let i = 0; i < 20; i++) { + await addCellAndRun(`print ${i}`, notebookEditor.notebook); + } + + // Verify visible range has the last cell + assert.strictEqual(notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end, notebookEditor.notebook.cellCount, `Last cell is not visible`); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 8c413aaf9fb..33cad6edb97 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -29,6 +29,12 @@ async function withEvent(event: vscode.Event, callback: (e: Promise) => } +function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + export class Kernel { readonly controller: vscode.NotebookController; @@ -59,8 +65,9 @@ export class Kernel { // create a single output with exec order 1 and output is plain/text // of either the cell itself or (iff empty) the cell's document's uri const task = this.controller.createNotebookCellExecution(cell); - task.start(); + task.start(Date.now()); task.executionOrder = 1; + await sleep(10); // Force to be take some time await task.replaceOutput([new vscode.NotebookCellOutput([ vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain') ])]); From f9b9f42034dd39fc473ea27003afdb57e5588c03 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 24 May 2022 11:14:12 -0700 Subject: [PATCH 264/350] update layout troubleshooting --- .../browser/contrib/troubleshoot/layout.ts | 66 ++++++++++++++----- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index 29d4e867f65..b3af4143741 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -7,9 +7,10 @@ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/commo import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { getNotebookEditorFromEditorPane, ICellViewModel, ICommonCellViewModelLayoutChangeInfo, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { getNotebookEditorFromEditorPane, ICellViewModel, ICommonCellViewModelLayoutChangeInfo, INotebookDeltaCellStatusBarItems, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { CellStatusbarAlignment, INotebookCellStatusBarItem } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -18,31 +19,37 @@ export class TroubleshootController extends Disposable implements INotebookEdito private readonly _localStore = this._register(new DisposableStore()); private _cellStateListeners: IDisposable[] = []; - private _logging: boolean = false; + private _enabled: boolean = false; + private _cellStatusItems: string[] = []; constructor(private readonly _notebookEditor: INotebookEditor) { super(); this._register(this._notebookEditor.onDidChangeModel(() => { - this._localStore.clear(); - this._cellStateListeners.forEach(listener => listener.dispose()); - - if (!this._notebookEditor.hasModel()) { - return; - } - - this._updateListener(); + this._update(); })); + this._update(); + } + + toggle(): void { + this._enabled = !this._enabled; + this._update(); + } + + private _update() { + this._localStore.clear(); + this._cellStateListeners.forEach(listener => listener.dispose()); + + if (!this._notebookEditor.hasModel()) { + return; + } + this._updateListener(); } - toggleLogging(): void { - this._logging = !this._logging; - } - private _log(cell: ICellViewModel, e: any) { - if (this._logging) { + if (this._enabled) { const oldHeight = (this._notebookEditor as NotebookEditorWidget).getViewHeight(cell); console.log(`cell#${cell.handle}`, e, `${oldHeight} -> ${cell.layoutInfo.totalHeight}`); } @@ -73,6 +80,33 @@ export class TroubleshootController extends Disposable implements INotebookEdito dispose(deletedCells); }); })); + + const vm = this._notebookEditor._getViewModel(); + let items: INotebookDeltaCellStatusBarItems[] = []; + + if (this._enabled) { + items = this._getItemsForCells(); + } + + this._cellStatusItems = vm.deltaCellStatusBarItems(this._cellStatusItems, items); + } + + private _getItemsForCells(): INotebookDeltaCellStatusBarItems[] { + const items: INotebookDeltaCellStatusBarItems[] = []; + for (let i = 0; i < this._notebookEditor.getLength(); i++) { + items.push({ + handle: i, + items: [ + { + text: `index: ${i}`, + alignment: CellStatusbarAlignment.Left, + priority: Number.MAX_SAFE_INTEGER + } + ] + }); + } + + return items; } override dispose() { @@ -102,7 +136,7 @@ registerAction2(class extends Action2 { } const controller = editor.getContribution(TroubleshootController.id); - controller?.toggleLogging(); + controller?.toggle(); } }); From 98fdef2b5e18193dccab264da977f6d2988c4804 Mon Sep 17 00:00:00 2001 From: rebornix Date: Tue, 24 May 2022 11:15:46 -0700 Subject: [PATCH 265/350] make sure page up and down goes to next page. --- src/vs/base/browser/ui/list/listWidget.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 55082221467..87a271fc02f 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1598,9 +1598,10 @@ export class List implements ISpliceable, IThemable, IDisposable { let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight); lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1; const lastPageElement = this.view.element(lastPageIndex); - const currentlyFocusedElement = this.getFocusedElements()[0]; + const currentlyFocusedElementIndex = this.getFocus()[0]; + const currentlyFocusedElement = this.view.element(currentlyFocusedElementIndex); - if (currentlyFocusedElement !== lastPageElement) { + if (currentlyFocusedElement !== lastPageElement && lastPageIndex > currentlyFocusedElementIndex) { const lastGoodPageIndex = this.findPreviousIndex(lastPageIndex, false, filter); if (lastGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(lastGoodPageIndex)) { @@ -1610,7 +1611,13 @@ export class List implements ISpliceable, IThemable, IDisposable { } } else { const previousScrollTop = this.view.getScrollTop(); - this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex)); + let nextpageScrollTop = previousScrollTop + this.view.renderHeight; + if (lastPageIndex > currentlyFocusedElementIndex) { + // scroll last page element to the top only if the last page element is below the focused element + nextpageScrollTop -= this.view.elementHeight(lastPageIndex); + } + + this.view.setScrollTop(nextpageScrollTop); if (this.view.getScrollTop() !== previousScrollTop) { this.setFocus([]); @@ -1633,9 +1640,10 @@ export class List implements ISpliceable, IThemable, IDisposable { } const firstPageElement = this.view.element(firstPageIndex); - const currentlyFocusedElement = this.getFocusedElements()[0]; + const currentlyFocusedElementIndex = this.getFocus()[0]; + const currentlyFocusedElement = this.view.element(currentlyFocusedElementIndex); - if (currentlyFocusedElement !== firstPageElement) { + if (currentlyFocusedElement !== firstPageElement && currentlyFocusedElementIndex >= firstPageIndex) { const firstGoodPageIndex = this.findNextIndex(firstPageIndex, false, filter); if (firstGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(firstGoodPageIndex)) { From 21e64f84458090e52aede145202c9940ae21172c Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 11:24:44 -0700 Subject: [PATCH 266/350] Remove unnecessary changes --- .../browser/view/renderers/webviewPreloads.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 5aff1145fd8..bdaa36e08b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1829,8 +1829,6 @@ async function webviewPreloads(ctx: PreloadContext) { class OutputCell { public readonly element: HTMLElement; - public readonly bottomElement: HTMLElement; - private readonly outputElements = new Map(); constructor(cellId: string) { @@ -1849,14 +1847,8 @@ async function webviewPreloads(ctx: PreloadContext) { this.element = this.element; - this.bottomElement = createFocusSink(cellId, true); - container.appendChild(this.bottomElement); - - // New thoughts. - // Send message to indicate force scroll. - // Add resizeObserver on the element - // When resize occurs, scroll lowerWrapperElement into view - // Send message to indicate stop scrolling + const lowerWrapperElement = createFocusSink(cellId, true); + container.appendChild(lowerWrapperElement); } public createOutputElement(outputId: string, outputOffset: number, left: number): OutputElement { From 7baaf796b0a03571ed793325b37e36ea49152b35 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 24 May 2022 20:29:14 +0200 Subject: [PATCH 267/350] Extract `NativeLocalProcessExtensionHost` --- .../electron-browser/extensionService.ts | 5 +- .../nativeLocalProcessExtensionHost.ts | 121 +++++++++++++++++ .../localProcessExtensionHost.ts | 128 ++---------------- 3 files changed, 138 insertions(+), 116 deletions(-) create mode 100644 src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts rename src/vs/workbench/services/extensions/{electron-browser => electron-sandbox}/localProcessExtensionHost.ts (85%) diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 388033c90a1..548ae5b4b16 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, LocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/localProcessExtensionHost'; +import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost'; import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -50,6 +50,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { isCI } from 'vs/base/common/platform'; import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; import { URI } from 'vs/base/common/uri'; +import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -240,7 +241,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { switch (runningLocation.kind) { case ExtensionHostKind.LocalProcess: { - return this._instantiationService.createInstance(LocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); } case ExtensionHostKind.LocalWebWorker: { if (this._enableLocalWebWorker) { diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts new file mode 100644 index 00000000000..d498ddd7960 --- /dev/null +++ b/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createServer, Server } from 'net'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import * as platform from 'vs/base/common/platform'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; +import { createRandomIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { IExtensionHostProcessOptions } from 'vs/platform/extensions/common/extensionHostStarter'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createMessageOfType, MessageType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { ExtensionHostProcess, ExtHostMessagePortCommunication, IExtHostCommunication, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; + +export class NativeLocalProcessExtensionHost extends SandboxLocalProcessExtensionHost { + protected override async _start(): Promise { + const canUseUtilityProcess = await this._extensionHostStarter.canUseUtilityProcess(); + if (canUseUtilityProcess && process.env['VSCODE_USE_UTILITY_PROCESS']) { + const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); + return this._startWithCommunication(communication); + } else { + const communication = this._toDispose.add(new ExtHostNamedPipeCommunication(this._logService)); + return this._startWithCommunication(communication); + } + } +} + +interface INamedPipePreparedData { + pipeName: string; + namedPipeServer: Server; +} + +class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication { + + readonly useUtilityProcess = false; + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + prepare(): Promise { + return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { + const pipeName = createRandomIPCHandle(); + + const namedPipeServer = createServer(); + namedPipeServer.on('error', reject); + namedPipeServer.listen(pipeName, () => { + if (namedPipeServer) { + namedPipeServer.removeListener('error', reject); + } + resolve({ pipeName, namedPipeServer }); + }); + this._register(toDisposable(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + })); + }); + } + + establishProtocol(prepared: INamedPipePreparedData, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise { + const { namedPipeServer, pipeName } = prepared; + + opts.env['VSCODE_IPC_HOOK_EXTHOST'] = pipeName; + + return new Promise((resolve, reject) => { + + // Wait for the extension host to connect to our named pipe + // and wrap the socket in the message passing protocol + const handle = setTimeout(() => { + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + reject('The local extension host took longer than 60s to connect.'); + }, 60 * 1000); + + namedPipeServer.on('connection', (socket) => { + + clearTimeout(handle); + if (namedPipeServer.listening) { + namedPipeServer.close(); + } + + const nodeSocket = new NodeSocket(socket, 'renderer-exthost'); + const protocol = new PersistentProtocol(nodeSocket); + + this._register(toDisposable(() => { + // Send the extension host a request to terminate itself + // (graceful termination) + protocol.send(createMessageOfType(MessageType.Terminate)); + protocol.flush(); + + socket.end(); + nodeSocket.dispose(); + protocol.dispose(); + })); + + resolve(protocol); + }); + + // Now that the named pipe listener is installed, start the ext host process + const sw = StopWatch.create(false); + extensionHostProcess.start(opts).then(() => { + const duration = sw.elapsed(); + if (platform.isCI) { + this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); + } + }, (err) => { + // Starting the ext host process resulted in an error + reject(err); + }); + + }); + } +} diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts similarity index 85% rename from src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts rename to src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index c43ae39d09f..800bf0576ae 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -3,21 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Server, createServer } from 'net'; -import { createRandomIPCHandle, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; - import * as nls from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IRemoteConsoleLog, log } from 'vs/base/common/console'; import { logRemoteEntry, logRemoteEntryIfError } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { BufferedEmitter, PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; +import { BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -27,7 +24,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { isUntitledWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { MessageType, createMessageOfType, isMessageOfType, IExtensionHostInitData, UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { MessageType, isMessageOfType, IExtensionHostInitData, UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { parseExtensionDevOptions } from '../common/extensionDevOptions'; @@ -62,7 +59,7 @@ const enum NativeLogMarkers { End = 'END_NATIVE_LOG', } -class ExtensionHostProcess { +export class ExtensionHostProcess { private readonly _id: string; @@ -106,7 +103,7 @@ class ExtensionHostProcess { } } -export class LocalProcessExtensionHost implements IExtensionHost { +export class SandboxLocalProcessExtensionHost implements IExtensionHost { public readonly remoteAuthority = null; public readonly lazyStart = false; @@ -117,7 +114,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { private readonly _onDidSetInspectPort = new Emitter(); - private readonly _toDispose = new DisposableStore(); + protected readonly _toDispose = new DisposableStore(); private readonly _isExtensionDevHost: boolean; private readonly _isExtensionDevDebug: boolean; @@ -144,13 +141,13 @@ export class LocalProcessExtensionHost implements IExtensionHost { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @INativeWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @ILogService private readonly _logService: ILogService, + @ILogService protected readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, @IHostService private readonly _hostService: IHostService, @IProductService private readonly _productService: IProductService, @IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService, - @IExtensionHostStarter private readonly _extensionHostStarter: IExtensionHostStarter, + @IExtensionHostStarter protected readonly _extensionHostStarter: IExtensionHostStarter, ) { const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; @@ -208,18 +205,12 @@ export class LocalProcessExtensionHost implements IExtensionHost { return this._messageProtocol; } - private async _start(): Promise { - const canUseUtilityProcess = await this._extensionHostStarter.canUseUtilityProcess(); - if (canUseUtilityProcess && process.env['VSCODE_USE_UTILITY_PROCESS']) { - const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); - return this._startWithCommunication(communication); - } else { - const communication = this._toDispose.add(new ExtHostNamedPipeCommunication(this._logService)); - return this._startWithCommunication(communication); - } + protected async _start(): Promise { + const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); + return this._startWithCommunication(communication); } - private async _startWithCommunication(communication: IExtHostCommunication): Promise { + protected async _startWithCommunication(communication: IExtHostCommunication): Promise { const [extensionHostCreationResult, communicationPreparedData, portNumber, processEnv] = await Promise.all([ this._extensionHostStarter.createExtensionHost(communication.useUtilityProcess), @@ -604,13 +595,13 @@ export class LocalProcessExtensionHost implements IExtensionHost { } } -interface IExtHostCommunication { +export interface IExtHostCommunication { readonly useUtilityProcess: boolean; prepare(): Promise; establishProtocol(prepared: T, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise; } -class ExtHostMessagePortCommunication extends Disposable implements IExtHostCommunication { +export class ExtHostMessagePortCommunication extends Disposable implements IExtHostCommunication { readonly useUtilityProcess = true; @@ -663,94 +654,3 @@ class ExtHostMessagePortCommunication extends Disposable implements IExtHostComm }); } } - -interface INamedPipePreparedData { - pipeName: string; - namedPipeServer: Server; -} - -class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication { - - readonly useUtilityProcess = false; - - constructor( - @ILogService private readonly _logService: ILogService - ) { - super(); - } - - prepare(): Promise { - return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { - const pipeName = createRandomIPCHandle(); - - const namedPipeServer = createServer(); - namedPipeServer.on('error', reject); - namedPipeServer.listen(pipeName, () => { - if (namedPipeServer) { - namedPipeServer.removeListener('error', reject); - } - resolve({ pipeName, namedPipeServer }); - }); - this._register(toDisposable(() => { - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - })); - }); - } - - establishProtocol(prepared: INamedPipePreparedData, extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise { - const { namedPipeServer, pipeName } = prepared; - - opts.env['VSCODE_IPC_HOOK_EXTHOST'] = pipeName; - - return new Promise((resolve, reject) => { - - // Wait for the extension host to connect to our named pipe - // and wrap the socket in the message passing protocol - const handle = setTimeout(() => { - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - reject('The local extension host took longer than 60s to connect.'); - }, 60 * 1000); - - namedPipeServer.on('connection', (socket) => { - - clearTimeout(handle); - if (namedPipeServer.listening) { - namedPipeServer.close(); - } - - const nodeSocket = new NodeSocket(socket, 'renderer-exthost'); - const protocol = new PersistentProtocol(nodeSocket); - - this._register(toDisposable(() => { - // Send the extension host a request to terminate itself - // (graceful termination) - protocol.send(createMessageOfType(MessageType.Terminate)); - protocol.flush(); - - socket.end(); - nodeSocket.dispose(); - protocol.dispose(); - })); - - resolve(protocol); - }); - - // Now that the named pipe listener is installed, start the ext host process - const sw = StopWatch.create(false); - extensionHostProcess.start(opts).then(() => { - const duration = sw.elapsed(); - if (platform.isCI) { - this._logService.info(`IExtensionHostStarter.start() took ${duration} ms.`); - } - }, (err) => { - // Starting the ext host process resulted in an error - reject(err); - }); - - }); - } -} From a88075a8472184b6507b39dc1cd872eb6bfcef46 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 24 May 2022 20:42:47 +0200 Subject: [PATCH 268/350] show migrate action in extensions viewlet (#150301) --- src/vs/workbench/contrib/extensions/browser/extensionsList.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 9b5c1397ee4..d61e4518b9b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtension } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -118,6 +118,7 @@ export class Renderer implements IPagedRenderer { const reloadAction = this.instantiationService.createInstance(ReloadAction); const actions = [ this.instantiationService.createInstance(ExtensionStatusLabelAction), + this.instantiationService.createInstance(MigrateDeprecatedExtension, true), this.instantiationService.createInstance(UpdateAction), reloadAction, this.instantiationService.createInstance(InstallDropdownAction), From acc1f9c29ba3c16214af1e77832454e25d66afcd Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 11:48:40 -0700 Subject: [PATCH 269/350] Remove empty line --- .../contrib/notebook/browser/view/renderers/webviewPreloads.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index bdaa36e08b3..441f75067b9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1846,7 +1846,6 @@ async function webviewPreloads(ctx: PreloadContext) { container.appendChild(this.element); this.element = this.element; - const lowerWrapperElement = createFocusSink(cellId, true); container.appendChild(lowerWrapperElement); } From d9373893583a1b78df346f4671bc24221d5e5aba Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 24 May 2022 14:58:08 -0400 Subject: [PATCH 270/350] Add owners to all telemetry events (#150296) --- src/vs/base/common/actions.ts | 1 + src/vs/code/electron-main/app.ts | 2 +- src/vs/editor/browser/editorExtensions.ts | 1 + .../contrib/suggest/browser/suggestController.ts | 10 +++++----- .../common/extensionGalleryService.ts | 3 +++ .../electron-sandbox/extensionTipsService.ts | 1 + .../files/browser/indexedDBFileSystemProvider.ts | 1 + src/vs/platform/telemetry/common/telemetryUtils.ts | 6 ++++-- .../terminal/common/xterm/shellIntegrationAddon.ts | 4 ++-- .../update/electron-main/abstractUpdateService.ts | 1 + .../update/electron-main/updateService.darwin.ts | 1 + .../userDataSync/common/abstractSynchronizer.ts | 1 + .../userDataSync/common/userDataAutoSyncService.ts | 4 +++- .../common/userDataSyncEnablementService.ts | 1 + .../userDataSync/common/userDataSyncService.ts | 1 + src/vs/server/node/remoteExtensionHostAgentServer.ts | 8 ++++---- .../workbench/api/common/extHostExtensionService.ts | 5 ++++- .../workbench/api/common/extHostRequireInterceptor.ts | 3 +++ src/vs/workbench/api/common/extHostSCM.ts | 5 ++++- .../browser/parts/activitybar/activitybarActions.ts | 1 + .../workbench/browser/parts/editor/editorCommands.ts | 1 + src/vs/workbench/browser/parts/views/viewPane.ts | 1 + .../browser/parts/views/viewPaneContainer.ts | 1 + .../contrib/experiments/common/experimentService.ts | 1 + .../browser/dynamicWorkspaceRecommendations.ts | 1 + .../extensionRecommendationNotificationService.ts | 2 ++ .../browser/extensionRecommendationsService.ts | 1 + .../contrib/extensions/browser/extensionsViews.ts | 1 + .../extensions/browser/extensionsWorkbenchService.ts | 1 + .../extensions/browser/fileBasedRecommendations.ts | 1 + .../contrib/notebook/browser/notebookEditor.ts | 1 + .../contrib/notebook/browser/notebookEditorWidget.ts | 1 + .../contrib/preferences/browser/keybindingsEditor.ts | 1 + .../searchEditor/browser/searchEditorActions.ts | 4 ++-- .../contrib/searchEditor/browser/searchEditorInput.ts | 2 +- .../contrib/tags/electron-sandbox/workspaceTags.ts | 2 +- .../telemetry/browser/telemetry.contribution.ts | 11 +++++++++-- .../contrib/terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/terminalProcessManager.ts | 2 +- .../contrib/userDataSync/browser/userDataSync.ts | 1 + .../electron-sandbox/accessibilityService.ts | 1 + .../extensions/browser/extensionUrlHandler.ts | 1 + .../browser/languageDetectionWorkerServiceImpl.ts | 2 ++ .../preferences/browser/preferencesService.ts | 1 + .../workbench/services/search/common/searchService.ts | 3 +++ .../services/themes/browser/workbenchThemeService.ts | 1 + .../browser/userDataSyncWorkbenchService.ts | 4 +++- 47 files changed, 85 insertions(+), 26 deletions(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 2fe5d37c03f..f9729a18770 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -14,6 +14,7 @@ export interface ITelemetryData { } export type WorkbenchActionExecutedClassification = { + owner: 'bpasero'; id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2d7e71bef34..eb752810e1c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1105,7 +1105,7 @@ export class CodeApplication extends Disposable { code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The type of shared process crash to understand the nature of the crash better.' }; visible: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether shared process window was visible or not.' }; shuttingdown: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the application is shutting down when the crash happens.' }; - owner: 'bpaser'; + owner: 'bpasero'; comment: 'Event which fires whenever an error occurs in the shared process'; }; diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 50766a815c2..93b20f1d2ed 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -338,6 +338,7 @@ export abstract class EditorAction extends EditorCommand { protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) { type EditorActionInvokedClassification = { + owner: 'alexdima'; name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 6c66d2a31ea..56a882e650c 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -451,11 +451,11 @@ export class SuggestController implements IEditorContribution { type AcceptedSuggestionClassification = { owner: 'jrieken'; comment: 'Information accepting completion items'; - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comments: 'Provider of the completions item' }; - basenameHash: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comments: 'Hash of the basename of the file into which the completion was inserted' }; - fileExtension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comments: 'File extension of the file into which the completion was inserted' }; - languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comments: 'Language type of the file into which the completion was inserted' }; - kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comments: 'The completion item kind' }; + providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'Provider of the completions item' }; + basenameHash: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'Hash of the basename of the file into which the completion was inserted' }; + fileExtension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension of the file into which the completion was inserted' }; + languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Language type of the file into which the completion was inserted' }; + kind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The completion item kind' }; }; // _debugDisplayName looks like `vscode.css-language-features(/-:)`, where the last bit is the trigger chars // normalize it to just the extension ID and lowercase diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 3f563b19e40..ad990114c43 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -235,6 +235,7 @@ const DefaultQueryState: IQueryState = { }; type GalleryServiceQueryClassification = { + owner: 'sandy081'; readonly filterTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; readonly flags: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; readonly sortBy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; @@ -272,6 +273,7 @@ type GalleryServiceQueryEvent = QueryTelemetryData & { }; type GalleryServiceAdditionalQueryClassification = { + owner: 'sandy081'; readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true }; readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; @@ -1124,6 +1126,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const message = getErrorMessage(err); type GalleryServiceCDNFallbackClassification = { + owner: 'sandy081'; url: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index f0a14198878..91cda7e7d16 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -27,6 +27,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type ExeExtensionRecommendationsClassification = { + owner: 'sandy081'; extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; exeName: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index ee053cda77a..6436f0a4d4b 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -16,6 +16,7 @@ import { createFileSystemProviderError, FileChangeType, IFileDeleteOptions, IFil import { DBClosedError, IndexedDB } from 'vs/base/browser/indexedDB'; export type IndexedDBFileSystemProviderErrorDataClassification = { + owner: 'sandy081'; readonly scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; readonly operation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; readonly code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 5a30a5743de..859af104212 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -84,8 +84,10 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf return configurationService.onDidChangeConfiguration(event => { if (event.source !== ConfigurationTarget.DEFAULT) { type UpdateConfigurationClassification = { - configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + owner: 'lramos15, sbatten'; + comment: 'Event which fires when user updates telemetry configuration'; + configurationSource: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration file was updated i.e user or workspace' }; + configurationKeys: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'What configuration keys were updated' }; }; type UpdateConfigurationEvent = { configurationSource: string; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index df36e27041d..0c02dfbf908 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -144,7 +144,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _handleVSCodeSequence(data: string): boolean { const didHandle = this._doHandleVSCodeSequence(data); if (!this._hasUpdatedTelemetry && didHandle) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationSucceeded'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationSucceeded'); this._hasUpdatedTelemetry = true; if (this._activationTimeout !== undefined) { clearTimeout(this._activationTimeout); @@ -232,7 +232,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { this._activationTimeout = setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationTimeout'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationTimeout'); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } this._hasUpdatedTelemetry = true; diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 42b9e8cee7a..8d777b69a7e 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -19,6 +19,7 @@ export function createUpdateURL(platform: string, quality: string, productServic } export type UpdateNotAvailableClassification = { + owner: 'joaomoreno'; explicit: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 0c008ea8504..df08a4b7966 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -92,6 +92,7 @@ export class DarwinUpdateService extends AbstractUpdateService { } type UpdateDownloadedClassification = { + owner: 'joaomoreno'; version: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ version: String }, UpdateDownloadedClassification>('update:downloaded', { version: update.version }); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 932841567c6..74d549a5bee 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -29,6 +29,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, ISyncResourceHandle, ISyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataInitializer, IUserDataManifest, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; type SyncSourceClassification = { + owner: 'sandy081'; source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index bea7313f0b3..108017e834b 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -22,10 +22,12 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; type AutoSyncClassification = { + owner: 'sandy081'; sources: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; type AutoSyncErrorClassification = { + owner: 'sandy081'; code: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; service: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; @@ -194,7 +196,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto // Reset if (everywhere) { - this.telemetryService.publicLog2('sync/turnOffEveryWhere'); + this.telemetryService.publicLog2<{}, { owner: 'sandy081' }>('sync/turnOffEveryWhere'); await this.userDataSyncService.reset(); } else { await this.userDataSyncService.resetLocal(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index 8e237a29063..9538b66d373 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -12,6 +12,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ALL_SYNC_RESOURCES, getEnablementKey, IUserDataSyncEnablementService, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; type SyncEnablementClassification = { + owner: 'sandy081'; enabled?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index f3dc3c82982..ff26c4d2b88 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -28,6 +28,7 @@ import { TasksSynchroniser } from 'vs/platform/userDataSync/common/tasksSync'; import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncResourcePreview, ISyncTask, IUserDataManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE } from 'vs/platform/userDataSync/common/userDataSync'; type SyncErrorClassification = { + owner: 'sandy081'; code: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; service: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; serverCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; diff --git a/src/vs/server/node/remoteExtensionHostAgentServer.ts b/src/vs/server/node/remoteExtensionHostAgentServer.ts index 3ae68711aa1..09aafc649a7 100644 --- a/src/vs/server/node/remoteExtensionHostAgentServer.ts +++ b/src/vs/server/node/remoteExtensionHostAgentServer.ts @@ -757,10 +757,10 @@ export async function createServer(address: string | net.AddressInfo | null, arg type ServerStartClassification = { owner: 'alexdima'; comment: 'The server has started up'; - startTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - startedTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - codeLoadedTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - readyTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + startTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time the server started at.' }; + startedTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time the server began listening for connections.' }; + codeLoadedTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time which the code loaded on the server' }; + readyTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time when the server was completely ready' }; }; type ServerStartEvent = { startTime: number; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 49eaa65d462..d23efb3a111 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -424,6 +424,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { const event = getTelemetryActivationEvent(extensionDescription, reason); type ExtensionActivationTimesClassification = { + owner: 'jrieken'; outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; @@ -447,7 +448,9 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { const event = getTelemetryActivationEvent(extensionDescription, reason); - type ActivatePluginClassification = {} & TelemetryActivationEventFragment; + type ActivatePluginClassification = { + owner: 'jrieken'; + } & TelemetryActivationEventFragment; this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); const entryPoint = this._getEntryPoint(extensionDescription); if (!entryPoint) { diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index 4a072952392..4ae49cc2a8b 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -249,6 +249,7 @@ class KeytarNodeModuleFactory implements INodeModuleFactory { public load(_request: string, parent: URI): any { const ext = this._extensionPaths.findSubstr(parent); type ShimmingKeytarClassification = { + owner: 'jrieken'; extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingKeytarClassification>('shimming.keytar', { extension: ext?.identifier.value ?? 'unknown_extension' }); @@ -346,6 +347,7 @@ class OpenNodeModuleFactory implements INodeModuleFactory { return; } type ShimmingOpenClassification = { + owner: 'jrieken'; extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); @@ -356,6 +358,7 @@ class OpenNodeModuleFactory implements INodeModuleFactory { return; } type ShimmingOpenCallNoForwardClassification = { + owner: 'jrieken'; extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index b65c52714b0..db595e3c9a5 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -741,7 +741,10 @@ export class ExtHostSCM implements ExtHostSCMShape { this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri); type TEvent = { extensionId: string }; - type TMeta = { extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' } }; + type TMeta = { + owner: 'joaomoreno'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + }; this._telemetry.$publicLog2('api/scm/createSourceControl', { extensionId: extension.identifier.value, }); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 36647e6dcfe..307fd9e9fec 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -102,6 +102,7 @@ export class ViewContainerActivityAction extends ActivityAction { private logAction(action: string) { type ActivityBarActionClassification = { + owner: 'sbatten'; viewletId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 3624936e41f..a07f202b076 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -968,6 +968,7 @@ function registerCloseEditorCommands() { ]); type WorkbenchEditorReopenClassification = { + owner: 'rebornix'; scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 80be8b96724..20f83615cab 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -51,6 +51,7 @@ export interface IViewPaneOptions extends IPaneOptions { } type WelcomeActionClassification = { + owner: 'sandy081'; viewId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; uri: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index be75bf31cd1..0ff11494826 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -821,6 +821,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) { const visible = !this.viewContainerModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { + owner: 'sandy081'; viewId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; visible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 0770dbb143e..07741ba7717 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -306,6 +306,7 @@ export class ExperimentService extends Disposable implements IExperimentService const promises = rawExperiments.map(experiment => this.evaluateExperiment(experiment)); return Promise.all(promises).then(() => { type ExperimentsClassification = { + owner: 'sbatten'; experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ experiments: string[] }, ExperimentsClassification>('experiments', { experiments: this._experiments.map(e => e.id) }); diff --git a/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts index f287d1ebe38..28a22fa7191 100644 --- a/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations.ts @@ -16,6 +16,7 @@ import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRe import { localize } from 'vs/nls'; type DynamicWorkspaceRecommendationsClassification = { + owner: 'sandy081'; count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; cache: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index 4e95edf7f9e..dc5bbff9180 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -27,12 +27,14 @@ import { EnablementState, IWorkbenchExtensionManagementService, IWorkbenchExtens import { IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; type ExtensionRecommendationsNotificationClassification = { + owner: 'sandy081'; userReaction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; extensionId?: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; type ExtensionWorkspaceRecommendationsNotificationClassification = { + owner: 'sandy081'; userReaction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 26e23dad630..86ffb382e28 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -29,6 +29,7 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; type IgnoreRecommendationClassification = { + owner: 'sandy081'; recommendationReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index de628c54a8d..31424b2285a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -61,6 +61,7 @@ import { isOfflineError } from 'vs/base/parts/request/common/request'; const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.git-base', 'vscode.search-result']; type WorkspaceRecommendationsClassification = { + owner: 'sandy081'; count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; 'isMeasurement': true }; }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index ece35b058d1..764a26d89ec 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -57,6 +57,7 @@ interface InstalledExtensionsEvent { readonly count: number; } interface ExtensionsLoadClassification extends GDPRClassification { + owner: 'digitarald'; readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; } diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index b81342b1bb9..26527cd7d68 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -37,6 +37,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; type FileExtensionSuggestionClassification = { + owner: 'sandy081'; userReaction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; fileExtension: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index c6b4e71bb47..1ae1e7ea6bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -245,6 +245,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti mark(input.resource, 'editorLoaded'); type WorkbenchNotebookOpenClassification = { + owner: 'rebornix'; scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8567bdded40..aabbb8f23de 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1130,6 +1130,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }); } type WorkbenchNotebookOpenClassification = { + owner: 'rebornix'; scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 018b31c5768..c7c5e722f4f 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -51,6 +51,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; type KeybindingEditorActionClassification = { + owner: 'sandy081'; action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 28e8b4fd1e5..8878b684466 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -139,7 +139,7 @@ export const openNewSearchEditor = } } - telemetryService.publicLog2('searchEditor/openNewSearchEditor'); + telemetryService.publicLog2<{}, { owner: 'roblourens' }>('searchEditor/openNewSearchEditor'); const seedSearchStringFromSelection = _args.location === 'new' || configurationService.getValue('editor').find!.seedSearchStringFromSelection; const args: OpenSearchEditorArgs = { query: seedSearchStringFromSelection ? selected : undefined }; @@ -189,7 +189,7 @@ export const createEditorFromSearchResult = const sortOrder = configurationService.getValue('search').sortOrder; - telemetryService.publicLog2('searchEditor/createEditorFromSearchResult'); + telemetryService.publicLog2<{}, { owner: 'roblourens' }>('searchEditor/createEditorFromSearchResult'); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index dfd56c33267..56f822657f5 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -184,7 +184,7 @@ export class SearchEditorInput extends EditorInput { override async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { const path = await this.fileDialogService.pickFileToSave(await this.suggestFileName(), options?.availableFileSystems); if (path) { - this.telemetryService.publicLog2('searchEditor/saveSearchResults'); + this.telemetryService.publicLog2<{}, { owner: 'roblourens' }>('searchEditor/saveSearchResults'); const toWrite = await this.serializeForDisk(); if (await this.textFileService.create([{ resource: path, value: toWrite, options: { overwrite: true } }])) { this.setDirty(false); diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts index 8e263a87401..c5156ebe74b 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts @@ -67,7 +67,7 @@ export class WorkspaceTags implements IWorkbenchContribution { value = 'Unknown'; } - this.telemetryService.publicLog2<{ edition: string }, { edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' } }>('windowsEdition', { edition: value }); + this.telemetryService.publicLog2<{ edition: string }, { owner: 'sbatten'; edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' } }>('windowsEdition', { edition: value }); } private async getWorkspaceInformation(): Promise { diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index adf6f60619f..2ebf2547eca 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -72,6 +72,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr }; type WorkspaceLoadClassification = { + owner: 'bpasero'; userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; windowSize: WindowSizeFragment; @@ -134,12 +135,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsReadClassification = { + owner: 'bpasero'; settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ settingsType: string }, SettingsReadClassification>('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data } else { - type FileGetClassification = {} & FileTelemetryDataFragment; + type FileGetClassification = { + owner: 'bpasero'; + } & FileTelemetryDataFragment; this.telemetryService.publicLog2('fileGet', this.getTelemetryData(e.model.resource, e.reason)); } @@ -149,11 +153,14 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsWrittenClassification = { + owner: 'bpasero'; settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data } else { - type FilePutClassfication = {} & FileTelemetryDataFragment; + type FilePutClassfication = { + owner: 'bpasero'; + } & FileTelemetryDataFragment; this.telemetryService.publicLog2('filePUT', this.getTelemetryData(e.model.resource, e.reason)); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 61c2efbb885..67614b1df92 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1634,7 +1634,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } if (failedShellIntegrationInjection) { - this._telemetryService.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationFailureProcessExit'); + this._telemetryService.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationFailureProcessExit'); } // First onExit to consumers, this can happen after the terminal has already been disposed. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 7ae7dc3c4fc..bf5ad2f3b68 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -335,7 +335,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._hasChildProcesses = value; break; case ProcessPropertyType.FailedShellIntegrationActivation: - this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationFailureCustomArgs'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationFailureCustomArgs'); break; } this._onDidChangeProperty.fire({ type, value }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 4a7fe8b6202..dec9d627cb9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -66,6 +66,7 @@ const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', type ConfigureSyncQuickPickItem = { id: SyncResource; label: string; description?: string }; type SyncConflictsClassification = { + owner: 'sandy081'; source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; action?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; diff --git a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index af9a08af7c8..eb818e7fc16 100644 --- a/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -22,6 +22,7 @@ interface AccessibilityMetrics { enabled: boolean; } type AccessibilityMetricsClassification = { + owner: 'isidorn'; enabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 5c045163b1b..f2008b07592 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -82,6 +82,7 @@ export interface ExtensionUrlHandlerEvent { } export interface ExtensionUrlHandlerClassification extends GDPRClassification { + owner: 'joaomoreno'; readonly extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; } diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index d3428409896..e70dd761951 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -203,6 +203,7 @@ export class LanguageDetectionWorkerHost { async sendTelemetryEvent(languages: string[], confidences: number[], timeSpent: number): Promise { type LanguageDetectionStats = { languages: string; confidences: string; timeSpent: number }; type LanguageDetectionStatsClassification = { + owner: 'TylerLeonhardt'; languages: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; @@ -337,6 +338,7 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { } type LanguageDetectionPerfClassification = { + owner: 'TylerLeonhardt'; timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; detection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; }; diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 555a3a7ee20..27785f847cc 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -304,6 +304,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic async openGlobalKeybindingSettings(textual: boolean, options?: IKeybindingsEditorOptions): Promise { type OpenKeybindingsClassification = { + owner: 'sandy081'; textual: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; this.telemetryService.publicLog2<{ textual: boolean }, OpenKeybindingsClassification>('openKeybindings', { textual }); diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 282bbfbe40e..cf0aad3563a 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -288,6 +288,7 @@ export class SearchService extends Disposable implements ISearchService { const cacheStats: ICachedSearchStats = fileSearchStats.detailStats as ICachedSearchStats; type CachedSearchCompleteClassifcation = { + owner: 'roblourens'; reason?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; resultCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; workspaceFolderCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; @@ -330,6 +331,7 @@ export class SearchService extends Disposable implements ISearchService { const searchEngineStats: ISearchEngineStats = fileSearchStats.detailStats as ISearchEngineStats; type SearchCompleteClassification = { + owner: 'roblourens'; reason?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; resultCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; workspaceFolderCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; @@ -387,6 +389,7 @@ export class SearchService extends Disposable implements ISearchService { } type TextSearchCompleteClassification = { + owner: 'roblourens'; reason?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; workspaceFolderCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; endToEndTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 901f92d9f3e..e2b130592fb 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -579,6 +579,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const key = themeType + themeData.extensionId; if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { + owner: 'aeschli'; id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 482be496d9b..d90996fd7ab 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -37,11 +37,13 @@ import { ICredentialsService } from 'vs/platform/credentials/common/credentials' import { CancellationError } from 'vs/base/common/errors'; type UserAccountClassification = { + owner: 'sandy081'; id: { classification: 'EndUserPseudonymizedInformation'; purpose: 'BusinessInsight' }; providerId: { classification: 'EndUserPseudonymizedInformation'; purpose: 'BusinessInsight' }; }; type FirstTimeSyncClassification = { + owner: 'sandy081'; action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; }; @@ -617,7 +619,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async onDidSuccessiveAuthFailures(): Promise { - this.telemetryService.publicLog2('sync/successiveAuthFailures'); + this.telemetryService.publicLog2<{}, { owner: 'sandy081' }>('sync/successiveAuthFailures'); this.currentSessionId = undefined; await this.update(); From 476e8cc521d07b7ce130795bfc8c1a0e8f44ce8f Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 24 May 2022 21:07:29 +0200 Subject: [PATCH 271/350] Extract `NativeExtensionService` and register a real implementation based on `UtilityProcess` for the extension service in the sandbox case --- .../nativeExtensionService.ts | 20 +++++++++++++++++++ .../electronExtensionService.ts} | 15 +++++--------- .../sandboxExtensionService.ts | 13 ++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 2 +- .../workbench.desktop.sandbox.main.ts | 8 +------- 5 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts rename src/vs/workbench/services/extensions/{electron-browser/extensionService.ts => electron-sandbox/electronExtensionService.ts} (96%) create mode 100644 src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts new file mode 100644 index 00000000000..2e4c745a9f8 --- /dev/null +++ b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost'; +import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService'; + +export class NativeExtensionService extends ElectronExtensionService { + protected override _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { + if (runningLocation.kind === ExtensionHostKind.LocalProcess) { + return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + } + return super._createExtensionHost(runningLocation, isInitialStart); + } +} + +registerSingleton(IExtensionService, NativeExtensionService); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts similarity index 96% rename from src/vs/workbench/services/extensions/electron-browser/extensionService.ts rename to src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index 548ae5b4b16..dabd7358452 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -3,10 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost'; - import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-sandbox/cachedExtensionScanner'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractExtensionService, ExtensionHostCrashTracker, ExtensionRunningPreference, extensionRunningPreferenceToString, filterByRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; import { runWhenIdle } from 'vs/base/common/async'; @@ -50,9 +47,9 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { isCI } from 'vs/base/common/platform'; import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; import { URI } from 'vs/base/common/uri'; -import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; +import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; -export class ExtensionService extends AbstractExtensionService implements IExtensionService { +export abstract class ElectronExtensionService extends AbstractExtensionService implements IExtensionService { private readonly _enableLocalWebWorker: boolean; private readonly _lazyLocalWebWorker: boolean; @@ -156,7 +153,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten ])); } - private _createLocalExtensionHostDataProvider(isInitialStart: boolean, desiredRunningLocation: ExtensionRunningLocation): ILocalProcessExtensionHostDataProvider & IWebWorkerExtensionHostDataProvider { + protected _createLocalExtensionHostDataProvider(isInitialStart: boolean, desiredRunningLocation: ExtensionRunningLocation): ILocalProcessExtensionHostDataProvider & IWebWorkerExtensionHostDataProvider { return { getInitData: async (): Promise => { if (isInitialStart) { @@ -194,7 +191,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } protected _pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null { - const result = ExtensionService.pickExtensionHostKind(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker); + const result = ElectronExtensionService.pickExtensionHostKind(extensionKinds, isInstalledLocally, isInstalledRemotely, preference, Boolean(this._environmentService.remoteAuthority), this._enableLocalWebWorker); this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionHostKindToString(result)}`); return result; } @@ -241,7 +238,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { switch (runningLocation.kind) { case ExtensionHostKind.LocalProcess: { - return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + return this._instantiationService.createInstance(SandboxLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); } case ExtensionHostKind.LocalWebWorker: { if (this._enableLocalWebWorker) { @@ -677,8 +674,6 @@ function getRemoteAuthorityPrefix(remoteAuthority: string): string { return remoteAuthority.substring(0, plusIndex); } -registerSingleton(IExtensionService, ExtensionService); - class RestartExtensionHostAction extends Action2 { constructor() { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts new file mode 100644 index 00000000000..ea51d7abc8f --- /dev/null +++ b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService'; + +export class SandboxExtensionService extends ElectronExtensionService { +} + +registerSingleton(IExtensionService, SandboxExtensionService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 75b3b27affa..6b7b44d4155 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -37,7 +37,7 @@ import 'vs/workbench/workbench.sandbox.main'; // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/extensions/electron-browser/nativeExtensionService'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts index 79258547199..894e6785212 100644 --- a/src/vs/workbench/workbench.desktop.sandbox.main.ts +++ b/src/vs/workbench/workbench.desktop.sandbox.main.ts @@ -27,12 +27,6 @@ import 'vs/workbench/electron-sandbox/desktop.main'; //#region --- workbench services -import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -// TODO@bpasero sandbox: remove me when extension host is present -class SimpleExtensionService extends NullExtensionService { } - -registerSingleton(IExtensionService, SimpleExtensionService); +import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService'; //#endregion From 7226b69ff49978173938a73a5d6ff59d51022e8b Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 24 May 2022 21:14:47 +0200 Subject: [PATCH 272/350] Fix layering issue --- .../extensions/electron-sandbox/localProcessExtensionHost.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 800bf0576ae..cee3a17b7f1 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -396,7 +396,7 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { // 2) wait for the incoming `initialized` event. return new Promise((resolve, reject) => { - let timeoutHandle: NodeJS.Timer; + let timeoutHandle: any; const installTimeoutCheck = () => { timeoutHandle = setTimeout(() => { reject('The local extenion host took longer than 60s to send its ready message.'); From bac03e54cb568044486f26efe91178f2e82545df Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 24 May 2022 13:02:08 -0700 Subject: [PATCH 273/350] Move "Configure Display Language" to Action2 (#150308) --- .../browser/localizations.contribution.ts | 6 +- .../browser/localizationsActions.ts | 60 ++++++++++--------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 92c5e9c0348..f05063b6b05 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -6,8 +6,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import { ConfigureLocaleAction } from 'vs/workbench/contrib/localizations/browser/localizationsActions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -28,8 +27,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; // Register action to configure locale and related settings -const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ConfigureLocaleAction), 'Configure Display Language'); +registerAction2(ConfigureLocaleAction); const LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/languagePackSuggestionIgnore'; diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index 5384fc3e0b0..016000d58c9 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -17,27 +16,22 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/productService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -export class ConfigureLocaleAction extends Action { - public static readonly ID = 'workbench.action.configureLocale'; - public static readonly LABEL = localize('configureLocale', "Configure Display Language"); - - constructor(id: string, label: string, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILocalizationsService private readonly localizationService: ILocalizationsService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, - @IHostService private readonly hostService: IHostService, - @INotificationService private readonly notificationService: INotificationService, - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, - @IDialogService private readonly dialogService: IDialogService, - @IProductService private readonly productService: IProductService - ) { - super(id, label); +export class ConfigureLocaleAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.configureLocale', + title: { original: 'Configure Display Language', value: localize('configureLocale', "Configure Display Language") }, + menu: { + id: MenuId.CommandPalette + } + }); } - private async getLanguageOptions(): Promise { - const availableLanguages = await this.localizationService.getLanguageIds(); + private async getLanguageOptions(localizationService: ILocalizationsService): Promise { + const availableLanguages = await localizationService.getLanguageIds(); availableLanguages.sort(); return availableLanguages @@ -45,12 +39,22 @@ export class ConfigureLocaleAction extends Action { .concat({ label: localize('installAdditionalLanguages', "Install Additional Languages...") }); } - public override async run(): Promise { - const languageOptions = await this.getLanguageOptions(); + public override async run(accessor: ServicesAccessor): Promise { + const environmentService: IEnvironmentService = accessor.get(IEnvironmentService); + const localizationService: ILocalizationsService = accessor.get(ILocalizationsService); + const quickInputService: IQuickInputService = accessor.get(IQuickInputService); + const jsonEditingService: IJSONEditingService = accessor.get(IJSONEditingService); + const hostService: IHostService = accessor.get(IHostService); + const notificationService: INotificationService = accessor.get(INotificationService); + const paneCompositeService: IPaneCompositePartService = accessor.get(IPaneCompositePartService); + const dialogService: IDialogService = accessor.get(IDialogService); + const productService: IProductService = accessor.get(IProductService); + + const languageOptions = await this.getLanguageOptions(localizationService); const currentLanguageIndex = languageOptions.findIndex(l => l.label === language); try { - const selectedLanguage = await this.quickInputService.pick(languageOptions, + const selectedLanguage = await quickInputService.pick(languageOptions, { canPickMany: false, placeHolder: localize('chooseDisplayLanguage', "Select Display Language"), @@ -58,7 +62,7 @@ export class ConfigureLocaleAction extends Action { }); if (selectedLanguage === languageOptions[languageOptions.length - 1]) { - return this.paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true) + return paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true) .then(viewlet => viewlet?.getViewPaneContainer()) .then(viewlet => { const extensionsViewlet = viewlet as IExtensionsViewPaneContainer; @@ -68,20 +72,20 @@ export class ConfigureLocaleAction extends Action { } if (selectedLanguage) { - await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: selectedLanguage.label }], true); - const restart = await this.dialogService.confirm({ + await jsonEditingService.write(environmentService.argvResource, [{ path: ['locale'], value: selectedLanguage.label }], true); + const restart = await dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.productService.nameLong), + detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), primaryButton: localize('restart', "&&Restart") }); if (restart.confirmed) { - this.hostService.restart(); + hostService.restart(); } } } catch (e) { - this.notificationService.error(e); + notificationService.error(e); } } } From b1a0aa5cfdacecf5e4f7b1991353243a07bd3f89 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 13:10:40 -0700 Subject: [PATCH 274/350] Use existing resize observer instead --- .../browser/view/renderers/webviewPreloads.ts | 66 ++++++++----------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 441f75067b9..471fdba68bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -294,7 +294,8 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly _observer: ResizeObserver; - private readonly _observedElements = new WeakMap(); + private readonly _observedElements = new WeakMap(); + private _outputResizeTimer: any; constructor() { this._observer = new ResizeObserver(entries => { @@ -308,6 +309,8 @@ async function webviewPreloads(ctx: PreloadContext) { continue; } + this.postResizeMessage(observedElementInfo.cellId); + if (entry.target.id === observedElementInfo.id && entry.contentRect) { if (observedElementInfo.output) { if (entry.contentRect.height !== 0) { @@ -329,14 +332,26 @@ async function webviewPreloads(ctx: PreloadContext) { }); } - public observe(container: Element, id: string, output: boolean) { + public observe(container: Element, id: string, output: boolean, cellId: string) { if (this._observedElements.has(container)) { return; } - this._observedElements.set(container, { id, output, lastKnownHeight: -1 }); + this._observedElements.set(container, { id, output, lastKnownHeight: -1, cellId }); this._observer.observe(container); } + + private postResizeMessage(cellId: string) { + // Debounce this callback to only happen after + // 250 ms. Don't need resize events that often. + clearTimeout(this._outputResizeTimer); + this._outputResizeTimer = setTimeout(() => { + postNotebookMessage('outputResized', { + cellId + }); + }, 250); + + } }; function scrollWillGoToParent(event: WheelEvent) { @@ -1395,14 +1410,10 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly _markupCells = new Map(); private readonly _outputCells = new Map(); - private _outputResizeObservers: ResizeObserver[] = []; - private _outputResizeTimer: any; public clearAll() { this._markupCells.clear(); this._outputCells.clear(); - this._outputResizeObservers.forEach(o => o.disconnect()); - this._outputResizeObservers = []; } public rerender() { @@ -1517,12 +1528,9 @@ async function webviewPreloads(ctx: PreloadContext) { } const cellOutput = this.ensureOutputCell(data.cellId, data.cellTop, false); - const outputNode = cellOutput.createOutputElement(data.outputId, data.outputOffset, data.left); + const outputNode = cellOutput.createOutputElement(data.outputId, data.outputOffset, data.left, data.cellId); outputNode.render(data.content, preloadsAndErrors); - // Track resizes on this output - this.trackOutputResize(data.cellId, outputNode.element); - // don't hide until after this step so that the height is right cellOutput.element.style.visibility = data.initiallyHidden ? 'hidden' : 'visible'; } @@ -1533,10 +1541,6 @@ async function webviewPreloads(ctx: PreloadContext) { if (!cell) { cell = new OutputCell(cellId); this._outputCells.set(cellId, cell); - - // New output cell, clear resize handlers - this._outputResizeObservers.forEach(o => o.disconnect); - this._outputResizeObservers = []; } if (existed && skipCellTopUpdateIfExist) { @@ -1578,24 +1582,6 @@ async function webviewPreloads(ctx: PreloadContext) { cell?.updateScroll(request); } } - - private outputResizeHandler(cellId: string) { - // Debounce this callback to only happen after - // 250 ms. Don't need resize events that often. - clearTimeout(this._outputResizeTimer); - this._outputResizeTimer = setTimeout(() => { - postNotebookMessage('outputResized', { - cellId - }); - }, 250); - } - - private trackOutputResize(cellId: string, outputContainer: HTMLElement) { - const handler = this.outputResizeHandler.bind(this, cellId); - const observer = new ResizeObserver(handler); - this._outputResizeObservers.push(observer); - observer.observe(outputContainer); - } }(); class MarkdownCodeBlock { @@ -1695,7 +1681,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.addEventListeners(); this.updateContentAndRender(this._content.value).then(() => { - resizeObserver.observe(this.element, this.id, false); + resizeObserver.observe(this.element, this.id, false, this.id); resolveReady(); }); } @@ -1850,7 +1836,7 @@ async function webviewPreloads(ctx: PreloadContext) { container.appendChild(lowerWrapperElement); } - public createOutputElement(outputId: string, outputOffset: number, left: number): OutputElement { + public createOutputElement(outputId: string, outputOffset: number, left: number, cellId: string): OutputElement { let outputContainer = this.outputElements.get(outputId); if (!outputContainer) { outputContainer = new OutputContainer(outputId); @@ -1858,7 +1844,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.outputElements.set(outputId, outputContainer); } - return outputContainer.createOutputElement(outputId, outputOffset, left); + return outputContainer.createOutputElement(outputId, outputOffset, left, cellId); } public clearOutput(outputId: string, rendererId: string | undefined) { @@ -1940,12 +1926,12 @@ async function webviewPreloads(ctx: PreloadContext) { this.element.style.top = `${outputOffset}px`; } - public createOutputElement(outputId: string, outputOffset: number, left: number): OutputElement { + public createOutputElement(outputId: string, outputOffset: number, left: number, cellId: string): OutputElement { this.element.innerText = ''; this.element.style.maxHeight = '0px'; this.element.style.top = `${outputOffset}px`; - this._outputNode = new OutputElement(outputId, left); + this._outputNode = new OutputElement(outputId, left, cellId); this.element.appendChild(this._outputNode.element); return this._outputNode; } @@ -1977,13 +1963,13 @@ async function webviewPreloads(ctx: PreloadContext) { class OutputElement { public readonly element: HTMLElement; - private _content?: { content: webviewMessages.ICreationContent; preloadsAndErrors: unknown[] }; private hasResizeObserver = false; constructor( private readonly outputId: string, left: number, + public readonly cellId: string ) { this.element = document.createElement('div'); this.element.id = outputId; @@ -2017,7 +2003,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (!this.hasResizeObserver) { this.hasResizeObserver = true; - resizeObserver.observe(this.element, this.outputId, true); + resizeObserver.observe(this.element, this.outputId, true, this.cellId); } const offsetHeight = this.element.offsetHeight; From 60c8307e9642d2d950584450efdd8bd240b056c9 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 24 May 2022 13:18:29 -0700 Subject: [PATCH 275/350] Remove unnecessary interface addition --- .../contrib/notebook/browser/diff/notebookTextDiffEditor.ts | 4 ---- .../contrib/notebook/browser/notebookEditorWidget.ts | 1 - .../notebook/browser/view/renderers/backLayerWebView.ts | 1 - 3 files changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 61c5daf9949..c0951594b1c 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -792,10 +792,6 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD throw new Error('Not implemented'); } - getCellByHandle(cellHandle: number): IGenericCellViewModel | undefined { - throw new Error('Not implemented'); - } - removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 4bd1f431a7f..f5ce4eaa696 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1387,7 +1387,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD triggerScroll(event: IMouseWheelEvent) { that._listViewInfoAccessor.triggerScroll(event); }, getCellByInfo: that.getCellByInfo.bind(that), getCellById: that._getCellById.bind(that), - getCellByHandle: that.getCellByHandle.bind(that), toggleNotebookCellSelection: that._toggleNotebookCellSelection.bind(that), focusNotebookCell: that.focusNotebookCell.bind(that), focusNextNotebookCell: that.focusNextNotebookCell.bind(that), diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 316ac6490d7..dedbe999773 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -67,7 +67,6 @@ export interface INotebookDelegateForWebview { focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions): Promise; toggleNotebookCellSelection(cell: IGenericCellViewModel, selectFromPrevious: boolean): void; getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; - getCellByHandle(cellHandle: number): IGenericCellViewModel | undefined; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): Promise; updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean, source?: string): void; scheduleOutputHeightAck(cellInfo: ICommonCellInfo, outputId: string, height: number): void; From bf7ec548f58cae6421d62206137763fa1a2452b4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 24 May 2022 13:19:04 -0700 Subject: [PATCH 276/350] Adopt TS 4.7 final (#150313) Picks up the offical TS 4.7 release --- extensions/package.json | 2 +- extensions/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/package.json b/extensions/package.json index b6d00d2faaf..3d89442d04f 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "4.7.1-rc" + "typescript": "4.7" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 1c746b8d2d0..4ab30e237c6 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -42,10 +42,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -typescript@4.7.1-rc: - version "4.7.1-rc" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.1-rc.tgz#23a0517d36c56de887b4457f29e2d265647bbd7c" - integrity sha512-EQd2NVelDe6ZVc2sO1CSpuSs+RHzY8c2n/kTNQAHw4um/eAXY+ZY4IKoUpNK0wO6C5hN+XcUXR7yqT8VbwwNIQ== +typescript@4.7: + version "4.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" + integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== vscode-grammar-updater@^1.1.0: version "1.1.0" From f363be172ca1ccbc6f037126926d6ca86c5760fc Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 24 May 2022 15:05:17 -0700 Subject: [PATCH 277/350] Rename LocalizationsService to LanguagePackService and misc moves (#150314) * rename LocalizationsService to LanguagePackService and misc moves * couple more renames * one more rename --- build/lib/i18n.resources.json | 2 +- .../contrib/localizationsUpdater.ts | 6 +-- .../sharedProcess/sharedProcessMain.ts | 12 +++--- src/vs/code/node/cliProcessMain.ts | 6 +-- src/vs/platform/driver/browser/driver.ts | 2 +- .../languagePacks/common/languagePacks.ts | 12 ++++++ .../common/localizedStrings.ts | 0 .../node/languagePacks.ts} | 31 ++++++++++++++-- .../localizations/common/localizations.ts | 37 ------------------- .../node/remoteExtensionHostAgentCli.ts | 6 +-- src/vs/server/node/serverServices.ts | 6 +-- .../browser/localizationsActions.ts | 10 ++--- .../localization.contribution.ts} | 8 ++-- .../electron-sandbox}/minimalTranslations.ts | 0 .../electron-sandbox/languagePackService.ts} | 4 +- src/vs/workbench/workbench.sandbox.main.ts | 4 +- 16 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 src/vs/platform/languagePacks/common/languagePacks.ts rename src/vs/platform/{localizations => languagePacks}/common/localizedStrings.ts (100%) rename src/vs/platform/{localizations/node/localizations.ts => languagePacks/node/languagePacks.ts} (88%) delete mode 100644 src/vs/platform/localizations/common/localizations.ts rename src/vs/workbench/contrib/{localizations => localization}/browser/localizationsActions.ts (90%) rename src/vs/workbench/contrib/{localizations/browser/localizations.contribution.ts => localization/electron-sandbox/localization.contribution.ts} (98%) rename src/vs/workbench/contrib/{localizations/browser => localization/electron-sandbox}/minimalTranslations.ts (100%) rename src/vs/workbench/services/{localizations/electron-sandbox/localizationsService.ts => localization/electron-sandbox/languagePackService.ts} (68%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 06bfbb2b506..7c99da42ae7 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -119,7 +119,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/localizations", + "name": "vs/workbench/contrib/localization", "project": "vscode-workbench" }, { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts index 6c9bfa8125a..68085dcefcc 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; export class LocalizationsUpdater extends Disposable { constructor( - @ILocalizationsService private readonly localizationsService: LocalizationsService + @ILanguagePackService private readonly localizationsService: LanguagePackService ) { super(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 243729c4e37..fb6cac109a9 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -46,8 +46,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { ConsoleLogger, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { FollowerLogService, LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; @@ -311,7 +311,7 @@ class SharedProcessMain extends Disposable { services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); // Localizations - services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); // Diagnostics services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); @@ -360,9 +360,9 @@ class SharedProcessMain extends Disposable { const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null); this.server.registerChannel('extensions', channel); - // Localizations - const localizationsChannel = ProxyChannel.fromService(accessor.get(ILocalizationsService)); - this.server.registerChannel('localizations', localizationsChannel); + // Language Packs + const languagePacksChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService)); + this.server.registerChannel('languagePacks', languagePacksChannel); // Diagnostics const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService)); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 6c197b17661..88f9dc88a7a 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -36,8 +36,8 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; @@ -161,7 +161,7 @@ class CliMain extends Disposable { services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); // Localizations - services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); // Telemetry const appenders: AppInsightsAppender[] = []; diff --git a/src/vs/platform/driver/browser/driver.ts b/src/vs/platform/driver/browser/driver.ts index c16032c953f..ce2167ba328 100644 --- a/src/vs/platform/driver/browser/driver.ts +++ b/src/vs/platform/driver/browser/driver.ts @@ -7,7 +7,7 @@ import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom'; import { coalesce } from 'vs/base/common/arrays'; import { language, locale } from 'vs/base/common/platform'; import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver'; -import localizedStrings from 'vs/platform/localizations/common/localizedStrings'; +import localizedStrings from 'vs/platform/languagePacks/common/localizedStrings'; export class BrowserWindowDriver implements IWindowDriver { diff --git a/src/vs/platform/languagePacks/common/languagePacks.ts b/src/vs/platform/languagePacks/common/languagePacks.ts new file mode 100644 index 00000000000..473e358148a --- /dev/null +++ b/src/vs/platform/languagePacks/common/languagePacks.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ILanguagePackService = createDecorator('languagePackService'); +export interface ILanguagePackService { + readonly _serviceBrand: undefined; + getInstalledLanguages(): Promise; +} diff --git a/src/vs/platform/localizations/common/localizedStrings.ts b/src/vs/platform/languagePacks/common/localizedStrings.ts similarity index 100% rename from src/vs/platform/localizations/common/localizedStrings.ts rename to src/vs/platform/languagePacks/common/localizedStrings.ts diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/languagePacks/node/languagePacks.ts similarity index 88% rename from src/vs/platform/localizations/node/localizations.ts rename to src/vs/platform/languagePacks/node/languagePacks.ts index 9d8bd79ac7d..009c936bdb0 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/languagePacks/node/languagePacks.ts @@ -13,7 +13,8 @@ import { Promises } from 'vs/base/node/pfs'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ILocalizationsService, isValidLocalization } from 'vs/platform/localizations/common/localizations'; +import { ILocalizationContribution } from 'vs/platform/extensions/common/extensions'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { ILogService } from 'vs/platform/log/common/log'; interface ILanguagePack { @@ -25,7 +26,7 @@ interface ILanguagePack { translations: { [id: string]: string }; } -export class LocalizationsService extends Disposable implements ILocalizationsService { +export class LanguagePackService extends Disposable implements ILanguagePackService { declare readonly _serviceBrand: undefined; @@ -48,7 +49,7 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe }); } - async getLanguageIds(): Promise { + async getInstalledLanguages(): Promise { const languagePacks = await this.cache.getLanguagePacks(); // Contributed languages are those installed via extension packs, so does not include English const languages = ['en', ...Object.keys(languagePacks)]; @@ -174,3 +175,27 @@ class LanguagePacksCache extends Disposable { }); } } + +function isValidLocalization(localization: ILocalizationContribution): boolean { + if (typeof localization.languageId !== 'string') { + return false; + } + if (!Array.isArray(localization.translations) || localization.translations.length === 0) { + return false; + } + for (const translation of localization.translations) { + if (typeof translation.id !== 'string') { + return false; + } + if (typeof translation.path !== 'string') { + return false; + } + } + if (localization.languageName && typeof localization.languageName !== 'string') { + return false; + } + if (localization.localizedLanguageName && typeof localization.localizedLanguageName !== 'string') { + return false; + } + return true; +} diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts deleted file mode 100644 index 6792440125c..00000000000 --- a/src/vs/platform/localizations/common/localizations.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ILocalizationContribution } from 'vs/platform/extensions/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export const ILocalizationsService = createDecorator('localizationsService'); -export interface ILocalizationsService { - readonly _serviceBrand: undefined; - getLanguageIds(): Promise; -} - -export function isValidLocalization(localization: ILocalizationContribution): boolean { - if (typeof localization.languageId !== 'string') { - return false; - } - if (!Array.isArray(localization.translations) || localization.translations.length === 0) { - return false; - } - for (const translation of localization.translations) { - if (typeof translation.id !== 'string') { - return false; - } - if (typeof translation.path !== 'string') { - return false; - } - } - if (localization.languageName && typeof localization.languageName !== 'string') { - return false; - } - if (localization.localizedLanguageName && typeof localization.localizedLanguageName !== 'string') { - return false; - } - return true; -} diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 36c01538db4..2d8bde94e63 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -28,8 +28,8 @@ import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IServerEnvironmentService, ServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { getErrorMessage } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { isAbsolute, join } from 'vs/base/common/path'; @@ -104,7 +104,7 @@ class CliMain extends Disposable { services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); - services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); + services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); return new InstantiationService(services); } diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 9885e9a1982..9a4bdee0c1a 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -35,8 +35,8 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILogService, LogLevel, LogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; @@ -159,7 +159,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); const instantiationService: IInstantiationService = new InstantiationService(services); - services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService)); + services.set(ILanguagePackService, instantiationService.createInstance(LanguagePackService)); const extensionManagementCLIService = instantiationService.createInstance(ExtensionManagementCLIService); services.set(IExtensionManagementCLIService, extensionManagementCLIService); diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts similarity index 90% rename from src/vs/workbench/contrib/localizations/browser/localizationsActions.ts rename to src/vs/workbench/contrib/localization/browser/localizationsActions.ts index 016000d58c9..afebaf8f1e1 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -30,8 +30,8 @@ export class ConfigureLocaleAction extends Action2 { }); } - private async getLanguageOptions(localizationService: ILocalizationsService): Promise { - const availableLanguages = await localizationService.getLanguageIds(); + private async getLanguageOptions(localizationService: ILanguagePackService): Promise { + const availableLanguages = await localizationService.getInstalledLanguages(); availableLanguages.sort(); return availableLanguages @@ -41,7 +41,7 @@ export class ConfigureLocaleAction extends Action2 { public override async run(accessor: ServicesAccessor): Promise { const environmentService: IEnvironmentService = accessor.get(IEnvironmentService); - const localizationService: ILocalizationsService = accessor.get(ILocalizationsService); + const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService); const quickInputService: IQuickInputService = accessor.get(IQuickInputService); const jsonEditingService: IJSONEditingService = accessor.get(IJSONEditingService); const hostService: IHostService = accessor.get(IHostService); @@ -50,7 +50,7 @@ export class ConfigureLocaleAction extends Action2 { const dialogService: IDialogService = accessor.get(IDialogService); const productService: IProductService = accessor.get(IProductService); - const languageOptions = await this.getLanguageOptions(localizationService); + const languageOptions = await this.getLanguageOptions(languagePackService); const currentLanguageIndex = languageOptions.findIndex(l => l.label === language); try { diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts similarity index 98% rename from src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts rename to src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index f05063b6b05..30f9660514d 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -6,9 +6,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ConfigureLocaleAction } from 'vs/workbench/contrib/localizations/browser/localizationsActions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -20,11 +18,13 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; -import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/browser/minimalTranslations'; +import { minimumTranslatedStrings } from 'vs/workbench/contrib/localization/electron-sandbox/minimalTranslations'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { ConfigureLocaleAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; // Register action to configure locale and related settings registerAction2(ConfigureLocaleAction); @@ -117,7 +117,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0]; const languageName = loc ? (loc.languageName || locale) : locale; const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale; - const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/localizations/browser/minimalTranslations'] : {}; + const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/localization/electron-sandbox/minimalTranslations'] : {}; const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'; const useEnglish = !translationsFromPack[promptMessageKey]; diff --git a/src/vs/workbench/contrib/localizations/browser/minimalTranslations.ts b/src/vs/workbench/contrib/localization/electron-sandbox/minimalTranslations.ts similarity index 100% rename from src/vs/workbench/contrib/localizations/browser/minimalTranslations.ts rename to src/vs/workbench/contrib/localization/electron-sandbox/minimalTranslations.ts diff --git a/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts b/src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts similarity index 68% rename from src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts rename to src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts index 5b716004793..b71303b3a90 100644 --- a/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts +++ b/src/vs/workbench/services/localization/electron-sandbox/languagePackService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; -registerSharedProcessRemoteService(ILocalizationsService, 'localizations', { supportsDelayedInstantiation: true }); +registerSharedProcessRemoteService(ILanguagePackService, 'languagePacks', { supportsDelayedInstantiation: true }); diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index e0708193c34..dd9ddd715b1 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -58,7 +58,7 @@ import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionMana import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService'; import 'vs/workbench/services/credentials/electron-sandbox/credentialsService'; import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; -import 'vs/workbench/services/localizations/electron-sandbox/localizationsService'; +import 'vs/workbench/services/localization/electron-sandbox/languagePackService'; import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService'; @@ -98,7 +98,7 @@ registerSingleton(IUserDataInitializationService, UserDataInitializationService) import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; // Localizations -import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; +import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; // Explorer import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; From 3a8b1fe03ebbcf57fb9c50b161db91229e2fe04a Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 24 May 2022 19:00:55 -0400 Subject: [PATCH 278/350] More telemetry comments (#150303) * Add owners to all telemetry events * More comments * Add comments to non typescript GDPR annotation * Another comment --- extensions/git/src/commands.ts | 6 ++++++ extensions/git/src/main.ts | 4 +++- extensions/git/src/repository.ts | 1 + .../github-authentication/src/githubServer.ts | 3 +++ .../src/languageFeatures/completions.ts | 2 ++ .../src/languageFeatures/organizeImports.ts | 1 + .../src/languageFeatures/quickFix.ts | 2 ++ .../src/languageFeatures/refactor.ts | 1 + .../src/tsServer/server.ts | 1 + .../src/typescriptServiceClient.ts | 6 ++++++ .../src/utils/largeProjectStatus.ts | 1 + .../diagnostics/node/diagnosticsService.ts | 14 +++++++++----- .../common/abstractExtensionManagementService.ts | 3 +++ .../common/extensionGalleryService.ts | 1 + .../browser/parts/editor/editorGroupView.ts | 2 ++ .../contrib/debug/browser/variablesView.ts | 1 + .../contrib/debug/common/debugTelemetry.ts | 2 ++ .../contrib/extensions/browser/extensionEditor.ts | 1 + .../browser/extensionRecommendationsService.ts | 1 + .../browser/extensionsWorkbenchService.ts | 2 ++ .../electron-sandbox/localization.contribution.ts | 1 + .../workbench/contrib/search/common/searchModel.ts | 3 +++ .../contrib/surveys/browser/ces.contribution.ts | 5 ++++- .../contrib/tags/electron-sandbox/workspaceTags.ts | 3 +++ .../terminal/browser/terminalTypeAheadAddon.ts | 1 + .../contrib/watermark/browser/watermark.ts | 4 +++- .../electron-sandbox/electronExtensionService.ts | 1 + .../services/timer/browser/timerService.ts | 1 + 28 files changed, 66 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index a3199cffc3a..6852639fcda 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -416,6 +416,7 @@ export class CommandCenter { if (!url) { /* __GDPR__ "clone" : { + "owner": "lszomoru", "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -441,6 +442,7 @@ export class CommandCenter { if (!uris || uris.length === 0) { /* __GDPR__ "clone" : { + "owner": "lszomoru", "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -499,6 +501,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { + "owner": "lszomoru", "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } @@ -518,6 +521,7 @@ export class CommandCenter { if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { /* __GDPR__ "clone" : { + "owner": "lszomoru", "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -527,6 +531,7 @@ export class CommandCenter { } else { /* __GDPR__ "clone" : { + "owner": "lszomoru", "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -2920,6 +2925,7 @@ export class CommandCenter { /* __GDPR__ "git.command" : { + "owner": "lszomoru", "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 0327aae4868..ee60d2cb1b1 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -192,7 +192,9 @@ export async function _activate(context: ExtensionContext): Promise { /* __GDPR__ "organizeImports.execute" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}" ] diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 977bc20dc61..9ea0780a446 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -37,6 +37,7 @@ class ApplyCodeActionCommand implements Command { ): Promise { /* __GDPR__ "quickFix.execute" : { + "owner": "mjbvz", "fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" @@ -67,6 +68,7 @@ class ApplyFixAllCodeAction implements Command { public async execute(args: ApplyFixAllCodeAction_args): Promise { /* __GDPR__ "quickFixAll.execute" : { + "owner": "mjbvz", "fixName" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index 392cc5a3e18..d5d1723533c 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -36,6 +36,7 @@ class DidApplyRefactoringCommand implements Command { public async execute(args: DidApplyRefactoringCommand_Args): Promise { /* __GDPR__ "refactor.execute" : { + "owner": "mjbvz", "action" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 12e6f70d91b..a035fbc9fdb 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -230,6 +230,7 @@ export class ProcessBasedTsServer extends Disposable implements ITypeScriptServe if (!executeInfo.token || !executeInfo.token.isCancellationRequested) { /* __GDPR__ "languageServiceErrorResponse" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}", "${TypeScriptRequestErrorProperties}" diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 306dd8570eb..0fdecc5c952 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -388,6 +388,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType /* __GDPR__ "tsserver.spawned" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}" ], @@ -418,6 +419,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType /* __GDPR__ "tsserver.error" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}" ] @@ -443,6 +445,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.error(`TSServer exited with code: ${code}. Signal: ${signal}`); /* __GDPR__ "tsserver.exitWithCode" : { + "owner": "mjbvz", "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "${include}": [ @@ -601,6 +604,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType /* __GDPR__ "serviceExited" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}" ] @@ -846,6 +850,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private fatalError(command: string, error: unknown): void { /* __GDPR__ "fatalError" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}", "${TypeScriptRequestErrorProperties}" @@ -977,6 +982,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType /* __GDPR__ "typingsInstalled" : { + "owner": "mjbvz", "installedPackages" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "installSuccess": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, "typingsInstallerVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, diff --git a/extensions/typescript-language-features/src/utils/largeProjectStatus.ts b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts index 346f95c6979..f6bdbf3721c 100644 --- a/extensions/typescript-language-features/src/utils/largeProjectStatus.ts +++ b/extensions/typescript-language-features/src/utils/largeProjectStatus.ts @@ -49,6 +49,7 @@ class ExcludeHintItem { this._item.show(); /* __GDPR__ "js.hintProjectExcludes" : { + "owner": "mjbvz", "${include}": [ "${TypeScriptCommonProperties}" ] diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 19dd0e65b2a..7a7a3ec76ba 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -536,8 +536,10 @@ export class DiagnosticsService implements IDiagnosticsService { try { const stats = await collectWorkspaceStats(folder, ['node_modules', '.git']); type WorkspaceStatsClassification = { - 'workspace.id': { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - rendererSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + owner: 'lramos15'; + comment: 'Metadata related to the workspace'; + 'workspace.id': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A UUID given to a workspace to identify it.' }; + rendererSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the session' }; }; type WorkspaceStatsEvent = { 'workspace.id': string | undefined; @@ -548,9 +550,11 @@ export class DiagnosticsService implements IDiagnosticsService { rendererSessionId: workspace.rendererSessionId }); type WorkspaceStatsFileClassification = { - rendererSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - type: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + owner: 'lramos15'; + comment: 'Helps us gain insights into what type of files are being used in a workspace'; + rendererSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the session.' }; + type: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The type of file' }; + count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How many types of that file are present' }; }; type WorkspaceStatsFileEvent = { rendererSessionId: string; diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 9279c520625..a2ff49e9bfd 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -616,6 +616,7 @@ export function reportTelemetry(telemetryService: ITelemetryService, eventName: const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ExtensionManagementErrorCode.Internal : undefined; /* __GDPR__ "extensionGallery:install" : { + "owner": "sandy081", "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "durationSinceUpdate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -628,6 +629,7 @@ export function reportTelemetry(telemetryService: ITelemetryService, eventName: */ /* __GDPR__ "extensionGallery:uninstall" : { + "owner": "sandy081", "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, @@ -638,6 +640,7 @@ export function reportTelemetry(telemetryService: ITelemetryService, eventName: */ /* __GDPR__ "extensionGallery:update" : { + "owner": "sandy081", "success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index ad990114c43..443107d84b9 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -994,6 +994,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const startTime = new Date().getTime(); /* __GDPR__ "galleryService:downloadVSIX" : { + "owner": "sandy081", "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "${include}": [ "${GalleryExtensionTelemetryData}" diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 7ea0ca109d4..21b5d732107 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -575,6 +575,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { /* __GDPR__ "editorOpened" : { + "owner": "bpasero", "${include}": [ "${EditorTelemetryDescriptor}" ] @@ -611,6 +612,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { /* __GDPR__ "editorClosed" : { + "owner": "bpasero", "${include}": [ "${EditorTelemetryDescriptor}" ] diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 677b8c86440..01aac2fa0b6 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -541,6 +541,7 @@ CommandsRegistry.registerCommand({ if (ext || await tryInstallHexEditor(notifications, progressService, extensionService, commandService)) { /* __GDPR__ "debug/didViewMemory" : { + "owner": "connor4312", "debugType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ diff --git a/src/vs/workbench/contrib/debug/common/debugTelemetry.ts b/src/vs/workbench/contrib/debug/common/debugTelemetry.ts index a68ce7e798e..a39c0651c55 100644 --- a/src/vs/workbench/contrib/debug/common/debugTelemetry.ts +++ b/src/vs/workbench/contrib/debug/common/debugTelemetry.ts @@ -18,6 +18,7 @@ export class DebugTelemetry { const extension = dbgr.getMainExtensionDescriptor(); /* __GDPR__ "debugSessionStart" : { + "owner": "connor4312", "type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "breakpointCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "exceptionBreakpoints": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -44,6 +45,7 @@ export class DebugTelemetry { /* __GDPR__ "debugSessionStop" : { + "owner": "connor4312", "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "success": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "sessionLengthInSeconds": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index f2da4fedd34..c0c3e368540 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -562,6 +562,7 @@ export class ExtensionEditor extends EditorPane { } /* __GDPR__ "extensionGallery:openExtension" : { + "owner": "sandy081", "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "${include}": [ "${GalleryExtensionTelemetryData}" diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 86ffb382e28..de0d081513a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -234,6 +234,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte if (recommendationReason) { /* __GDPR__ "extensionGallery:install:recommendations" : { + "owner": "sandy081", "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "${include}": [ "${GalleryExtensionTelemetryData}" diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 764a26d89ec..0bc773af868 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1455,6 +1455,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (changed[i]) { /* __GDPR__ "extension:enable" : { + "owner": "sandy081", "${include}": [ "${GalleryExtensionTelemetryData}" ] @@ -1462,6 +1463,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension */ /* __GDPR__ "extension:disable" : { + "owner": "sandy081", "${include}": [ "${GalleryExtensionTelemetryData}" ] diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index 30f9660514d..c725565a55d 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -133,6 +133,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo const logUserReaction = (userReaction: string) => { /* __GDPR__ "languagePackSuggestion:popup" : { + "owner": "TylerLeonhardt", "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "language": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 578221a80ef..b10af56a625 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -1076,6 +1076,7 @@ export class SearchModel extends Disposable { Promise.race([currentRequest, Event.toPromise(progressEmitter.event)]).finally(() => { /* __GDPR__ "searchResultsFirstRender" : { + "owner": "roblourens", "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } */ @@ -1091,6 +1092,7 @@ export class SearchModel extends Disposable { } finally { /* __GDPR__ "searchResultsFinished" : { + "owner": "roblourens", "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } */ @@ -1119,6 +1121,7 @@ export class SearchModel extends Disposable { /* __GDPR__ "searchResultsShown" : { + owner": "roblourens", "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "fileCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "options": { "${inline}": [ "${IPatternInfo}" ] }, diff --git a/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts b/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts index 6c2dab81268..a2c5a2beaf7 100644 --- a/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/ces.contribution.ts @@ -66,6 +66,7 @@ class CESContribution extends Disposable implements IWorkbenchContribution { const sendTelemetry = (userReaction: 'accept' | 'remindLater' | 'neverShowAgain' | 'cancelled') => { /* __GDPR__ "cesSurvey:popup" : { + "owner": "digitarald", "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -141,7 +142,9 @@ class CESContribution extends Disposable implements IWorkbenchContribution { } } /* __GDPR__ - "cesSurvey:schedule" : { } + "cesSurvey:schedule" : { + "owner": "digitarald" + } */ this.telemetryService.publicLog('cesSurvey:schedule'); diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts index c5156ebe74b..ab86a14c069 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts @@ -89,6 +89,7 @@ export class WorkspaceTags implements IWorkbenchContribution { private reportWorkspaceTags(tags: Tags): void { /* __GDPR__ "workspce.tags" : { + "owner": "lramos15", "${include}": [ "${WorkspaceTags}" ] @@ -116,6 +117,7 @@ export class WorkspaceTags implements IWorkbenchContribution { set.forEach(item => list.push(item)); /* __GDPR__ "workspace.remotes" : { + "owner": "lramos15", "domains" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ @@ -192,6 +194,7 @@ export class WorkspaceTags implements IWorkbenchContribution { if (Object.keys(tags).length) { /* __GDPR__ "workspace.azure" : { + "owner": "lramos15", "${include}": [ "${AzureTags}" ] diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts index 168cbe2fbf2..9c53e0d00f6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts @@ -1411,6 +1411,7 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { private _sendLatencyStats(stats: PredictionStats) { /* __GDPR__ "terminalLatencyStats" : { + "owner": "Tyriar", "min" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "max" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "median" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 799c6820bba..cb1fe2362b2 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -181,7 +181,9 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); /* __GDPR__ - "watermark:open" : { } + "watermark:open" : { + "owner": "digitarald" + } */ this.telemetryService.publicLog('watermark:open'); } diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index dabd7358452..ea87ef0642a 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -612,6 +612,7 @@ export abstract class ElectronExtensionService extends AbstractExtensionService const sendTelemetry = (userReaction: 'install' | 'enable' | 'cancel') => { /* __GDPR__ "remoteExtensionRecommendations:popup" : { + "owner": "sandy081", "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index be619fd1188..08723845be3 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -514,6 +514,7 @@ export abstract class AbstractTimerService implements ITimerService { // report IStartupMetrics as telemetry /* __GDPR__ "startupTimeVaried" : { + "owner": "jrieken", "${include}": [ "${IStartupMetrics}" ] From 0226ad3aa7ca779076f6d4e5bfd5bceec13170bf Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 24 May 2022 16:02:04 -0700 Subject: [PATCH 279/350] Fix drop data transfer overwriting everything with `uri-list` mime (#150319) --- .../dropIntoEditor/browser/dropIntoEditorContribution.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index edbc29db56e..f45143e4e0e 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -121,10 +121,8 @@ export class DropIntoEditorController extends Disposable implements IEditorContr .map(input => input.resource!.toString()); if (editorData.length) { - const added = new VSDataTransfer(); const str = distinct(editorData).join('\n'); - added.setString(Mimes.uriList.toLowerCase(), str); - return added; + textEditorDataTransfer.setString(Mimes.uriList.toLowerCase(), str); } } From 7635c51d7fbee5fdc591f091ee23dc6c5b0e424e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 24 May 2022 16:10:38 -0700 Subject: [PATCH 280/350] add telemetry comments (#150317) --- .../platform/terminal/common/xterm/shellIntegrationAddon.ts | 4 ++-- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/terminal/browser/terminalProcessManager.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 0c02dfbf908..2e456109e9e 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -144,7 +144,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _handleVSCodeSequence(data: string): boolean { const didHandle = this._doHandleVSCodeSequence(data); if (!this._hasUpdatedTelemetry && didHandle) { - this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationSucceeded'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration was activated' }>('terminal/shellIntegrationActivationSucceeded'); this._hasUpdatedTelemetry = true; if (this._activationTimeout !== undefined) { clearTimeout(this._activationTimeout); @@ -232,7 +232,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { this._activationTimeout = setTimeout(() => { if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationTimeout'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration activation did not occur within 10 seconds' }>('terminal/shellIntegrationActivationTimeout'); this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); } this._hasUpdatedTelemetry = true; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 67614b1df92..5c841da1021 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1634,7 +1634,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } if (failedShellIntegrationInjection) { - this._telemetryService.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationFailureProcessExit'); + this._telemetryService.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates the process exited when created with shell integration args' }>('terminal/shellIntegrationFailureProcessExit'); } // First onExit to consumers, this can happen after the terminal has already been disposed. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index bf5ad2f3b68..65d72db0196 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -335,7 +335,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._hasChildProcesses = value; break; case ProcessPropertyType.FailedShellIntegrationActivation: - this._telemetryService?.publicLog2<{}, { owner: 'meganrogge' }>('terminal/shellIntegrationActivationFailureCustomArgs'); + this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration was not activated because of custom args' }>('terminal/shellIntegrationActivationFailureCustomArgs'); break; } this._onDidChangeProperty.fire({ type, value }); From 61861d70439cd073fcec9c589cbb18d03400b2a6 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 24 May 2022 17:28:33 -0700 Subject: [PATCH 281/350] skip localization test until localized strings are present (#150326) --- test/smoke/src/areas/workbench/localization.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index be81bb17d4c..a6c86ba20d1 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -9,11 +9,11 @@ import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { describe('Localization', () => { - // Shared before/after handling installAllHandlers(logger); - it('starts with "DE" locale and verifies title and viewlets text is in German', async function () { + // skipped until translations are available https://github.com/microsoft/vscode/issues/150324 + it.skip('starts with "DE" locale and verifies title and viewlets text is in German', async function () { const app = this.app as Application; await app.workbench.extensions.openExtensionsViewlet(); From d891b49fc0f8fc5e00591657ca225975952b09ca Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 24 May 2022 21:38:12 -0700 Subject: [PATCH 282/350] Enable file DataTransfer on tree views (#150328) For #147481 --- .../api/browser/mainThreadTreeViews.ts | 5 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../workbench/api/common/extHostTreeViews.ts | 5 +- .../test/browser/mainThreadTreeViews.test.ts | 2 +- .../workbench/browser/parts/views/treeView.ts | 64 ++++++++----------- src/vs/workbench/common/views.ts | 1 + 6 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index f0923d95701..56133dc4b20 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -37,14 +37,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise { + async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): Promise { this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options); this.extensionService.whenInstalledExtensionsRegistered().then(() => { const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); this._dataProviders.set(treeViewId, dataProvider); const dndController = (options.hasHandleDrag || options.hasHandleDrop) - ? new TreeViewDragAndDropController(treeViewId, options.dropMimeTypes, options.dragMimeTypes, options.hasHandleDrag, this._proxy) : undefined; + ? new TreeViewDragAndDropController(treeViewId, options.dropMimeTypes, options.dragMimeTypes, options.hasHandleDrag, options.supportsFileDataTransfers, this._proxy) : undefined; const viewer = this.getTreeView(treeViewId); if (viewer) { // Order is important here. The internal tree isn't created until the dataProvider is set. @@ -201,6 +201,7 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController { readonly dropMimeTypes: string[], readonly dragMimeTypes: string[], readonly hasWillDrop: boolean, + readonly supportsFileDataTransfers: boolean, private readonly _proxy: ExtHostTreeViewsShape) { } async handleDrop(dataTransfer: VSDataTransfer, targetTreeItem: ITreeItem | undefined, token: CancellationToken, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c7ebcd110f8..8975b45a790 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -258,7 +258,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index fdbb535e422..8bdd508109c 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -24,7 +24,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Command } from 'vs/editor/common/languages'; import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; type TreeItemHandle = string; @@ -92,7 +92,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { const dragMimeTypes = options.dragAndDropController?.dragMimeTypes ?? []; const hasHandleDrag = !!options.dragAndDropController?.handleDrag; const hasHandleDrop = !!options.dragAndDropController?.handleDrop; - const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop }); + const supportsFileDataTransfers = isProposedApiEnabled(extension, 'dataTransferFiles'); + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop, supportsFileDataTransfers }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index 1386a01b234..4afba6e05e6 100644 --- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -75,7 +75,7 @@ suite('MainThreadHostTreeView', function () { } drain(): any { return null; } }, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()); - mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false }); + mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false, supportsFileDataTransfers: false }); await testExtensionService.whenInstalledExtensionsRegistered(); }); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 50da8ebb647..051926ebcb4 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -66,7 +66,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; -import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; +import { CodeDataTransfers, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; export class TreeViewPane extends ViewPane { @@ -1503,40 +1503,21 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } const treeDataTransfer = new VSDataTransfer(); const uris: URI[] = []; - let itemsCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => { - if ((current.kind === 'string') || (current.kind === 'file')) { - return previous + 1; - } - return previous; - }, 0); let treeSourceInfo: TreeDragSourceInfo | undefined; let willDropUuid: string | undefined; if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { willDropUuid = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype)![0].identifier; } - await new Promise(resolve => { - function decrementStringCount() { - itemsCount--; - if (itemsCount === 0) { - // Check if there are uris to add and add them - if (uris.length) { - treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); - } - resolve(); - } - } - if (!originalEvent.dataTransfer) { - return; - } - for (const dataItem of originalEvent.dataTransfer.items) { - const type = dataItem.type; - const kind = dataItem.kind; - const convertedType = this.convertKnownMimes(type, kind).type; - if ((INTERNAL_MIME_TYPES.indexOf(convertedType) < 0) - && (convertedType === this.treeMimeType) || (dndController.dropMimeTypes.indexOf(convertedType) >= 0)) { - if (dataItem.kind === 'string') { + await Promise.all([...originalEvent.dataTransfer.items].map(async dataItem => { + const type = dataItem.type; + const kind = dataItem.kind; + const convertedType = this.convertKnownMimes(type, kind).type; + if ((INTERNAL_MIME_TYPES.indexOf(convertedType) < 0) + && (convertedType === this.treeMimeType) || (dndController.dropMimeTypes.indexOf(convertedType) >= 0)) { + if (dataItem.kind === 'string') { + await new Promise(resolve => dataItem.getAsString(dataValue => { if (convertedType === this.treeMimeType) { treeSourceInfo = JSON.parse(dataValue); @@ -1545,20 +1526,27 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { const converted = this.convertKnownMimes(type, kind, dataValue); treeDataTransfer.setString(converted.type, converted.value + ''); } - decrementStringCount(); - }); - } else if (dataItem.kind === 'file') { - const dataValue = dataItem.getAsFile(); - if (dataValue) { - uris.push(URI.file(dataValue.path)); + resolve(); + })); + } else if (dataItem.kind === 'file') { + const file = dataItem.getAsFile(); + if (file) { + uris.push(URI.file(file.path)); + const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; + if (dndController.supportsFileDataTransfers) { + treeDataTransfer.setFile(type, file.name, uri, async () => { + return new Uint8Array(await file.arrayBuffer()); + }); } - decrementStringCount(); } - } else if (dataItem.kind === 'string' || dataItem.kind === 'file') { - decrementStringCount(); } } - }); + })); + + // Check if there are uris to add and add them + if (uris.length) { + treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); + } const additionalWillDropPromise = this.treeViewsDragAndDropService.removeDragOperationTransfer(willDropUuid); if (!additionalWillDropPromise) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index ac9b39c0c21..a255a45c0e2 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -831,6 +831,7 @@ export interface ITreeViewDataProvider { export interface ITreeViewDragAndDropController { readonly dropMimeTypes: string[]; readonly dragMimeTypes: string[]; + readonly supportsFileDataTransfers: boolean; handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; handleDrop(elements: VSDataTransfer, target: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; } From 9ade6a663662c5e4805038a6e3b3b4ba2f648472 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 25 May 2022 10:01:21 +0200 Subject: [PATCH 283/350] Engineering - Refactor cyclic dependency (#150343) * Refactor to avoid cyclic dependency * Pull request feedback + compilation errors --- .../contrib/debug/browser/debugTaskRunner.ts | 6 +- .../contrib/markers/browser/constants.ts | 33 ---- .../markers/browser/markers.contribution.ts | 143 ++++++++++-------- .../contrib/markers/browser/markers.ts | 29 +--- .../markers/browser/markersTreeViewer.ts | 5 +- .../contrib/markers/browser/markersView.ts | 17 +-- .../markers/browser/markersViewActions.ts | 4 +- .../contrib/markers/common/markers.ts | 39 +++++ .../tasks/browser/abstractTaskService.ts | 4 +- .../tasks/browser/terminalTaskSystem.ts | 8 +- 10 files changed, 142 insertions(+), 146 deletions(-) delete mode 100644 src/vs/workbench/contrib/markers/browser/constants.ts create mode 100644 src/vs/workbench/contrib/markers/common/markers.ts diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 4667fc38a3e..8f4255fb1d2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; import { Event } from 'vs/base/common/event'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { Markers } from 'vs/workbench/contrib/markers/common/markers'; import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; @@ -76,7 +76,7 @@ export class DebugTaskRunner { return TaskRunResult.Success; } if (onTaskErrors === 'showErrors') { - await this.viewsService.openView(Constants.MARKERS_VIEW_ID, true); + await this.viewsService.openView(Markers.MARKERS_VIEW_ID, true); return Promise.resolve(TaskRunResult.Failure); } if (onTaskErrors === 'abort') { @@ -113,7 +113,7 @@ export class DebugTaskRunner { return TaskRunResult.Success; } - await this.viewsService.openView(Constants.MARKERS_VIEW_ID, true); + await this.viewsService.openView(Markers.MARKERS_VIEW_ID, true); return Promise.resolve(TaskRunResult.Failure); } catch (err) { const taskConfigureAction = this.taskService.configureAction(); diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts deleted file mode 100644 index a883b3bf148..00000000000 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; - -export default { - MARKERS_CONTAINER_ID: 'workbench.panel.markers', - MARKERS_VIEW_ID: 'workbench.panel.markers.view', - MARKERS_VIEW_STORAGE_ID: 'workbench.panel.markers', - MARKER_COPY_ACTION_ID: 'problems.action.copy', - MARKER_COPY_MESSAGE_ACTION_ID: 'problems.action.copyMessage', - RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', - FOCUS_PROBLEMS_FROM_FILTER: 'problems.action.focusProblemsFromFilter', - MARKERS_VIEW_FOCUS_FILTER: 'problems.action.focusFilter', - MARKERS_VIEW_CLEAR_FILTER_TEXT: 'problems.action.clearFilterText', - MARKERS_VIEW_SHOW_MULTILINE_MESSAGE: 'problems.action.showMultilineMessage', - MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE: 'problems.action.showSinglelineMessage', - MARKER_OPEN_ACTION_ID: 'problems.action.open', - MARKER_OPEN_SIDE_ACTION_ID: 'problems.action.openToSide', - MARKER_SHOW_PANEL_ID: 'workbench.action.showErrorsWarnings', - MARKER_SHOW_QUICK_FIX: 'problems.action.showQuickFixes', - TOGGLE_MARKERS_VIEW_ACTION_ID: 'workbench.actions.view.toggleProblems', - - MarkersViewModeContextKey: new RawContextKey('problemsViewMode', MarkersViewMode.Tree), - MarkersViewSmallLayoutContextKey: new RawContextKey(`problemsView.smallLayout`, false), - MarkersTreeVisibilityContextKey: new RawContextKey('problemsVisibility', false), - MarkerFocusContextKey: new RawContextKey('problemFocus', false), - MarkerViewFilterFocusContextKey: new RawContextKey('problemsFilterFocus', false), - RelatedInformationFocusContextKey: new RawContextKey('relatedInformationFocus', false) -}; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index ffd15324657..66e8b574bc6 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -11,16 +11,16 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersView, MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; +import { MarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { MarkersViewMode, Markers, MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ActivityUpdater, IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; +import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; @@ -31,53 +31,54 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.MARKER_OPEN_ACTION_ID, + id: Markers.MARKER_OPEN_ACTION_ID, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.MarkerFocusContextKey), + when: ContextKeyExpr.and(MarkersContextKeys.MarkerFocusContextKey), primary: KeyCode.Enter, mac: { primary: KeyCode.Enter, secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow] }, handler: (accessor, args: any) => { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID)!; + const markersView = accessor.get(IViewsService).getActiveViewWithId(Markers.MARKERS_VIEW_ID)!; markersView.openFileAtElement(markersView.getFocusElement(), false, false, true); } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.MARKER_OPEN_SIDE_ACTION_ID, + id: Markers.MARKER_OPEN_SIDE_ACTION_ID, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.MarkerFocusContextKey), + when: ContextKeyExpr.and(MarkersContextKeys.MarkerFocusContextKey), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, handler: (accessor, args: any) => { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID)!; + const markersView = accessor.get(IViewsService).getActiveViewWithId(Markers.MARKERS_VIEW_ID)!; markersView.openFileAtElement(markersView.getFocusElement(), false, true, true); } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.MARKER_SHOW_PANEL_ID, + id: Markers.MARKER_SHOW_PANEL_ID, weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, handler: async (accessor, args: any) => { - await accessor.get(IViewsService).openView(Constants.MARKERS_VIEW_ID); + await accessor.get(IViewsService).openView(Markers.MARKERS_VIEW_ID); } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.MARKER_SHOW_QUICK_FIX, + id: Markers.MARKER_SHOW_QUICK_FIX, weight: KeybindingWeight.WorkbenchContrib, - when: Constants.MarkerFocusContextKey, + when: MarkersContextKeys.MarkerFocusContextKey, primary: KeyMod.CtrlCmd | KeyCode.Period, handler: (accessor, args: any) => { - const markersView = accessor.get(IViewsService).getActiveViewWithId(Constants.MARKERS_VIEW_ID)!; + const markersView = accessor.get(IViewsService).getActiveViewWithId(Markers.MARKERS_VIEW_ID)!; const focusedElement = markersView.getFocusElement(); if (focusedElement instanceof Marker) { markersView.showQuickFixes(focusedElement); @@ -125,17 +126,17 @@ const markersViewIcon = registerIcon('markers-view-icon', Codicon.warning, local // markers view container const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ - id: Constants.MARKERS_CONTAINER_ID, + id: Markers.MARKERS_CONTAINER_ID, title: Messages.MARKERS_PANEL_TITLE_PROBLEMS, icon: markersViewIcon, hideIfEmpty: true, order: 0, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), - storageId: Constants.MARKERS_VIEW_STORAGE_ID, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Markers.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + storageId: Markers.MARKERS_VIEW_STORAGE_ID, }, ViewContainerLocation.Panel, { donotRegisterOpenCommand: true }); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ - id: Constants.MARKERS_VIEW_ID, + id: Markers.MARKERS_VIEW_ID, containerIcon: markersViewIcon, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, canToggleVisibility: false, @@ -151,22 +152,21 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews // workbench const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); // actions registerAction2(class extends ViewAction { constructor() { super({ - id: `workbench.actions.table.${Constants.MARKERS_VIEW_ID}.viewAsTree`, + id: `workbench.actions.table.${Markers.MARKERS_VIEW_ID}.viewAsTree`, title: localize('viewAsTree', "View as Tree"), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Table)), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Table)), group: 'navigation', order: 3 }, icon: Codicon.listTree, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } @@ -178,16 +178,16 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: `workbench.actions.table.${Constants.MARKERS_VIEW_ID}.viewAsTable`, + id: `workbench.actions.table.${Markers.MARKERS_VIEW_ID}.viewAsTable`, title: localize('viewAsTable', "View as Table"), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), group: 'navigation', order: 3 }, icon: Codicon.listFlat, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } @@ -206,15 +206,15 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - accessor.get(IViewsService).openView(Constants.MARKERS_VIEW_ID, true); + accessor.get(IViewsService).openView(Markers.MARKERS_VIEW_ID, true); } }); registerAction2(class extends ViewAction { constructor() { - const when = ContextKeyExpr.and(FocusedViewContext.isEqualTo(Constants.MARKERS_VIEW_ID), Constants.MarkersTreeVisibilityContextKey, Constants.RelatedInformationFocusContextKey.toNegated()); + const when = ContextKeyExpr.and(FocusedViewContext.isEqualTo(Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersTreeVisibilityContextKey, MarkersContextKeys.RelatedInformationFocusContextKey.toNegated()); super({ - id: Constants.MARKER_COPY_ACTION_ID, + id: Markers.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, menu: { id: MenuId.ProblemsPanelContext, @@ -226,7 +226,7 @@ registerAction2(class extends ViewAction { primary: KeyMod.CtrlCmd | KeyCode.KeyC, when }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -254,14 +254,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, + id: Markers.MARKER_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, menu: { id: MenuId.ProblemsPanelContext, - when: Constants.MarkerFocusContextKey, + when: MarkersContextKeys.MarkerFocusContextKey, group: 'navigation' }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -276,14 +276,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, + id: Markers.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, menu: { id: MenuId.ProblemsPanelContext, - when: Constants.RelatedInformationFocusContextKey, + when: MarkersContextKeys.RelatedInformationFocusContextKey, group: 'navigation' }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -298,14 +298,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.FOCUS_PROBLEMS_FROM_FILTER, + id: Markers.FOCUS_PROBLEMS_FROM_FILTER, title: localize('focusProblemsList', "Focus problems view"), keybinding: { - when: Constants.MarkerViewFilterFocusContextKey, + when: MarkersContextKeys.MarkerViewFilterFocusContextKey, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -316,14 +316,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.MARKERS_VIEW_FOCUS_FILTER, + id: Markers.MARKERS_VIEW_FOCUS_FILTER, title: localize('focusProblemsFilter', "Focus problems filter"), keybinding: { - when: FocusedViewContext.isEqualTo(Constants.MARKERS_VIEW_ID), + when: FocusedViewContext.isEqualTo(Markers.MARKERS_VIEW_ID), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyF }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -334,14 +334,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, + id: Markers.MARKERS_VIEW_SHOW_MULTILINE_MESSAGE, title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.has(getVisbileViewContextKey(Constants.MARKERS_VIEW_ID)) + when: ContextKeyExpr.has(getVisbileViewContextKey(Markers.MARKERS_VIEW_ID)) }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -352,14 +352,14 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, + id: Markers.MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE, title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, category: localize('problems', "Problems"), menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.has(getVisbileViewContextKey(Constants.MARKERS_VIEW_ID)) + when: ContextKeyExpr.has(getVisbileViewContextKey(Markers.MARKERS_VIEW_ID)) }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -370,15 +370,15 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: Constants.MARKERS_VIEW_CLEAR_FILTER_TEXT, + id: Markers.MARKERS_VIEW_CLEAR_FILTER_TEXT, title: localize('clearFiltersText', "Clear filters text"), category: localize('problems', "Problems"), keybinding: { - when: Constants.MarkerViewFilterFocusContextKey, + when: MarkersContextKeys.MarkerViewFilterFocusContextKey, weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.Escape }, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, markersView: IMarkersView): Promise { @@ -389,16 +389,16 @@ registerAction2(class extends ViewAction { registerAction2(class extends ViewAction { constructor() { super({ - id: `workbench.actions.treeView.${Constants.MARKERS_VIEW_ID}.collapseAll`, + id: `workbench.actions.treeView.${Markers.MARKERS_VIEW_ID}.collapseAll`, title: localize('collapseAll', "Collapse All"), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersViewModeContextKey.isEqualTo(MarkersViewMode.Tree)), group: 'navigation', order: 2, }, icon: Codicon.collapseAll, - viewId: Constants.MARKERS_VIEW_ID + viewId: Markers.MARKERS_VIEW_ID }); } async runInView(serviceAccessor: ServicesAccessor, view: IMarkersView): Promise { @@ -409,11 +409,11 @@ registerAction2(class extends ViewAction { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.treeView.${Constants.MARKERS_VIEW_ID}.filter`, + id: `workbench.actions.treeView.${Markers.MARKERS_VIEW_ID}.filter`, title: localize('filter', "Filter"), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Constants.MARKERS_VIEW_ID), Constants.MarkersViewSmallLayoutContextKey.negate()), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', Markers.MARKERS_VIEW_ID), MarkersContextKeys.MarkersViewSmallLayoutContextKey.negate()), group: 'navigation', order: 1, }, @@ -425,16 +425,16 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: Constants.TOGGLE_MARKERS_VIEW_ACTION_ID, + id: Markers.TOGGLE_MARKERS_VIEW_ACTION_ID, title: Messages.MARKERS_PANEL_TOGGLE_LABEL, }); } async run(accessor: ServicesAccessor): Promise { const viewsService = accessor.get(IViewsService); - if (viewsService.isViewVisible(Constants.MARKERS_VIEW_ID)) { - viewsService.closeView(Constants.MARKERS_VIEW_ID); + if (viewsService.isViewVisible(Markers.MARKERS_VIEW_ID)) { + viewsService.closeView(Markers.MARKERS_VIEW_ID); } else { - viewsService.openView(Constants.MARKERS_VIEW_ID, true); + viewsService.openView(Markers.MARKERS_VIEW_ID, true); } } }); @@ -514,3 +514,26 @@ class MarkersStatusBarContributions extends Disposable implements IWorkbenchCont } workbenchRegistry.registerWorkbenchContribution(MarkersStatusBarContributions, LifecyclePhase.Restored); + +class ActivityUpdater extends Disposable implements IWorkbenchContribution { + + private readonly activity = this._register(new MutableDisposable()); + + constructor( + @IActivityService private readonly activityService: IActivityService, + @IMarkerService private readonly markerService: IMarkerService + ) { + super(); + this._register(this.markerService.onMarkerChanged(() => this.updateBadge())); + this.updateBadge(); + } + + private updateBadge(): void { + const { errors, warnings, infos } = this.markerService.getStatistics(); + const total = errors + warnings + infos; + const message = localize('totalProblems', 'Total {0} Problems', total); + this.activity.value = this.activityService.showViewActivity(Markers.MARKERS_VIEW_ID, { badge: new NumberBadge(total, () => message) }); + } +} + +workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index c65df45418a..0969177554d 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -3,17 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { localize } from 'vs/nls'; -import Constants from './constants'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { MarkersFilters } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { Event } from 'vs/base/common/event'; import { IView } from 'vs/workbench/common/views'; import { MarkerElement, ResourceMarkers } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; +import { MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers'; export interface IMarkersView extends IView { @@ -33,24 +27,3 @@ export interface IMarkersView extends IView { setMultiline(multiline: boolean): void; setViewMode(viewMode: MarkersViewMode): void; } - -export class ActivityUpdater extends Disposable implements IWorkbenchContribution { - - private readonly activity = this._register(new MutableDisposable()); - - constructor( - @IActivityService private readonly activityService: IActivityService, - @IMarkerService private readonly markerService: IMarkerService - ) { - super(); - this._register(this.markerService.onMarkerChanged(() => this.updateBadge())); - this.updateBadge(); - } - - private updateBadge(): void { - const { errors, warnings, infos } = this.markerService.getStatistics(); - const total = errors + warnings + infos; - const message = localize('totalProblems', 'Total {0} Problems', total); - this.activity.value = this.activityService.showViewActivity(Constants.MARKERS_VIEW_ID, { badge: new NumberBadge(total, () => message) }); - } -} diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index f6e04799b34..92a2abd857b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -48,9 +48,8 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Link } from 'vs/platform/opener/browser/link'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { MarkersViewMode } from 'vs/workbench/contrib/markers/browser/markersView'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers'; interface IResourceMarkersTemplateData { resourceLabel: IResourceLabel; @@ -718,7 +717,7 @@ export class MarkersViewModel extends Disposable { this._multiline = multiline; this._viewMode = viewMode; - this.viewModeContextKey = Constants.MarkersViewModeContextKey.bindTo(this.contextKeyService); + this.viewModeContextKey = MarkersContextKeys.MarkersViewModeContextKey.bindTo(this.contextKeyService); this.viewModeContextKey.set(viewMode); } diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index b20df6c492a..b3cdba4c228 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -10,7 +10,6 @@ import * as dom from 'vs/base/browser/dom'; import { IAction, Action, Separator } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent, MarkersModel, compareMarkersByUri, MarkerElement, MarkerTableItem } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionViewItem, MarkersFilters, IMarkersFiltersChangeEvent } from 'vs/workbench/contrib/markers/browser/markersViewActions'; @@ -59,6 +58,7 @@ import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/m import { ResourceListDnDHandler } from 'vs/workbench/browser/dnd'; import { ITableContextMenuEvent, ITableEvent } from 'vs/base/browser/ui/table/table'; import { MarkersTable } from 'vs/workbench/contrib/markers/browser/markersTable'; +import { Markers, MarkersContextKeys, MarkersViewMode } from 'vs/workbench/contrib/markers/common/markers'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { @@ -69,11 +69,6 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterab }); } -export const enum MarkersViewMode { - Table = 'table', - Tree = 'tree' -} - export interface IProblemsWidget { get contextKeyService(): IContextKeyService; @@ -162,8 +157,8 @@ export class MarkersView extends ViewPane implements IMarkersView { @IThemeService themeService: IThemeService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); - this.smallLayoutContextKey = Constants.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); - this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER); + this.smallLayoutContextKey = MarkersContextKeys.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); + this.panelState = new Memento(Markers.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE, StorageTarget.USER); this.markersModel = this._register(instantiationService.createInstance(MarkersModel)); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'], this.panelState['viewMode'] ?? this.getDefaultViewMode())); @@ -419,8 +414,8 @@ export class MarkersView extends ViewPane implements IMarkersView { this.widget = this.markersViewModel.viewMode === MarkersViewMode.Table ? this.createTable(parent) : this.createTree(parent); this.widgetDisposables.add(this.widget); - const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.widget.contextKeyService); - const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.widget.contextKeyService); + const markerFocusContextKey = MarkersContextKeys.MarkerFocusContextKey.bindTo(this.widget.contextKeyService); + const relatedInformationFocusContextKey = MarkersContextKeys.RelatedInformationFocusContextKey.bindTo(this.widget.contextKeyService); this.widgetDisposables.add(this.widget.onDidChangeFocus(focus => { markerFocusContextKey.set(focus.elements.some(e => e instanceof Marker)); relatedInformationFocusContextKey.set(focus.elements.some(e => e instanceof RelatedInformation)); @@ -915,7 +910,7 @@ class MarkersTree extends WorkbenchObjectTree impleme @IAccessibilityService accessibilityService: IAccessibilityService ) { super(user, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService); - this.visibilityContextKey = Constants.MarkersTreeVisibilityContextKey.bindTo(contextKeyService); + this.visibilityContextKey = MarkersContextKeys.MarkersTreeVisibilityContextKey.bindTo(contextKeyService); } collapseMarkers(): void { diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 63dc3458879..a8857968647 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -11,7 +11,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -31,6 +30,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IMarkersView } from 'vs/workbench/contrib/markers/browser/markers'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; +import { MarkersContextKeys } from 'vs/workbench/contrib/markers/common/markers'; export interface IMarkersFiltersChangeEvent { filterText?: boolean; @@ -260,7 +260,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { ) { super(null, action); this.keybindingService = keybindingService; - this.focusContextKey = Constants.MarkerViewFilterFocusContextKey.bindTo(contextKeyService); + this.focusContextKey = MarkersContextKeys.MarkerViewFilterFocusContextKey.bindTo(contextKeyService); this.delayedFilterUpdate = new Delayer(400); this._register(toDisposable(() => this.delayedFilterUpdate.cancel())); this._register(markersView.onDidFocusFilter(() => this.focus())); diff --git a/src/vs/workbench/contrib/markers/common/markers.ts b/src/vs/workbench/contrib/markers/common/markers.ts new file mode 100644 index 00000000000..6f3efbbeec6 --- /dev/null +++ b/src/vs/workbench/contrib/markers/common/markers.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const enum MarkersViewMode { + Table = 'table', + Tree = 'tree' +} + +export namespace Markers { + export const MARKERS_CONTAINER_ID = 'workbench.panel.markers'; + export const MARKERS_VIEW_ID = 'workbench.panel.markers.view'; + export const MARKERS_VIEW_STORAGE_ID = 'workbench.panel.markers'; + export const MARKER_COPY_ACTION_ID = 'problems.action.copy'; + export const MARKER_COPY_MESSAGE_ACTION_ID = 'problems.action.copyMessage'; + export const RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID = 'problems.action.copyRelatedInformationMessage'; + export const FOCUS_PROBLEMS_FROM_FILTER = 'problems.action.focusProblemsFromFilter'; + export const MARKERS_VIEW_FOCUS_FILTER = 'problems.action.focusFilter'; + export const MARKERS_VIEW_CLEAR_FILTER_TEXT = 'problems.action.clearFilterText'; + export const MARKERS_VIEW_SHOW_MULTILINE_MESSAGE = 'problems.action.showMultilineMessage'; + export const MARKERS_VIEW_SHOW_SINGLELINE_MESSAGE = 'problems.action.showSinglelineMessage'; + export const MARKER_OPEN_ACTION_ID = 'problems.action.open'; + export const MARKER_OPEN_SIDE_ACTION_ID = 'problems.action.openToSide'; + export const MARKER_SHOW_PANEL_ID = 'workbench.action.showErrorsWarnings'; + export const MARKER_SHOW_QUICK_FIX = 'problems.action.showQuickFixes'; + export const TOGGLE_MARKERS_VIEW_ACTION_ID = 'workbench.actions.view.toggleProblems'; +} + +export namespace MarkersContextKeys { + export const MarkersViewModeContextKey = new RawContextKey('problemsViewMode', MarkersViewMode.Tree); + export const MarkersViewSmallLayoutContextKey = new RawContextKey(`problemsView.smallLayout`, false); + export const MarkersTreeVisibilityContextKey = new RawContextKey('problemsVisibility', false); + export const MarkerFocusContextKey = new RawContextKey('problemFocus', false); + export const MarkerViewFilterFocusContextKey = new RawContextKey('problemsFilterFocus', false); + export const RelatedInformationFocusContextKey = new RawContextKey('relatedInformationFocus', false); +} diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index dbd096e791c..42f69229199 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -36,7 +36,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IModelService } from 'vs/editor/common/services/model'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { Markers } from 'vs/workbench/contrib/markers/common/markers'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -430,7 +430,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); - CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => this.commandService.executeCommand(Constants.TOGGLE_MARKERS_VIEW_ACTION_ID)); + CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => this.commandService.executeCommand(Markers.TOGGLE_MARKERS_VIEW_ACTION_ID)); CommandsRegistry.registerCommand('workbench.action.tasks.openUserTasks', async () => { const resource = this.getResourceForKind(TaskSourceKind.User); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 6cb1d3b7f86..2c06dbc8717 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -22,7 +22,7 @@ import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/marke import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/model'; import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { Markers } from 'vs/workbench/contrib/markers/common/markers'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -813,7 +813,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let reveal = task.command.presentation!.reveal; let revealProblems = task.command.presentation!.revealProblems; if (revealProblems === RevealProblemKind.OnProblem) { - this.viewsService.openView(Constants.MARKERS_VIEW_ID, true); + this.viewsService.openView(Markers.MARKERS_VIEW_ID, true); } else if (reveal === RevealKind.Silent) { this.terminalService.setActiveInstance(terminal!); this.terminalGroupService.showPanel(false); @@ -956,7 +956,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let revealProblems = task.command.presentation!.revealProblems; let revealProblemPanel = terminal && (revealProblems === RevealProblemKind.OnProblem) && (startStopProblemMatcher.numberOfMatches > 0); if (revealProblemPanel) { - this.viewsService.openView(Constants.MARKERS_VIEW_ID); + this.viewsService.openView(Markers.MARKERS_VIEW_ID); } else if (terminal && (reveal === RevealKind.Silent) && ((exitCode !== 0) || (startStopProblemMatcher.numberOfMatches > 0) && startStopProblemMatcher.maxMarkerSeverity && (startStopProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error))) { try { @@ -991,7 +991,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { let showProblemPanel = task.command.presentation && (task.command.presentation.revealProblems === RevealProblemKind.Always); if (showProblemPanel) { - this.viewsService.openView(Constants.MARKERS_VIEW_ID); + this.viewsService.openView(Markers.MARKERS_VIEW_ID); } else if (task.command.presentation && (task.command.presentation.reveal === RevealKind.Always)) { this.terminalService.setActiveInstance(terminal); this.terminalGroupService.showPanel(task.command.presentation.focus); From a6724dcc109871736cb025064470ee3fdfdb61aa Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 25 May 2022 02:50:44 -0700 Subject: [PATCH 284/350] Bump default JS target for scripts in html (#150240) * Bump default JS target for scripts in html For JS/TS files, we target ES2020 by default. However in html files we're still stuck targetting es6 This change aligns the two * use es2020.full (includes dom), fix web, avoid searching in node_modules Co-authored-by: Martin Aeschlimann --- .../server/build/javaScriptLibraryLoader.js | 2 +- .../server/src/modes/javascriptLibs.ts | 2 +- .../server/src/modes/javascriptMode.ts | 13 +++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions/html-language-features/server/build/javaScriptLibraryLoader.js b/extensions/html-language-features/server/build/javaScriptLibraryLoader.js index 85792138ba4..51e74618042 100644 --- a/extensions/html-language-features/server/build/javaScriptLibraryLoader.js +++ b/extensions/html-language-features/server/build/javaScriptLibraryLoader.js @@ -31,7 +31,7 @@ module.exports = function () { queue.push(name); }; - enqueue('es6'); + enqueue('es2020.full'); var result = []; while (queue.length > 0) { diff --git a/extensions/html-language-features/server/src/modes/javascriptLibs.ts b/extensions/html-language-features/server/src/modes/javascriptLibs.ts index bdca89be362..7abf94edf22 100644 --- a/extensions/html-language-features/server/src/modes/javascriptLibs.ts +++ b/extensions/html-language-features/server/src/modes/javascriptLibs.ts @@ -24,7 +24,7 @@ export function loadLibrary(name: string) { try { content = readFileSync(libPath).toString(); } catch (e) { - console.log(`Unable to load library ${name} at ${libPath}: ${e.message}`); + console.log(`Unable to load library ${name} at ${libPath}`); content = ''; } contents[name] = content; diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 7f9e3f1d85a..a119a9248ae 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -19,7 +19,7 @@ import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticT const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; function getLanguageServiceHost(scriptKind: ts.ScriptKind) { - const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false }; + const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es2020.full.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false }; let currentTextDocument = TextDocument.create('init', 'javascript', 1, ''); const jsLanguageService = import(/* webpackChunkName: "javascriptLibs" */ './javascriptLibs').then(libs => { @@ -52,7 +52,7 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { }; }, getCurrentDirectory: () => '', - getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6', + getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es2020.full', readFile: (path: string, _encoding?: string | undefined): string | undefined => { if (path === currentTextDocument.uri) { return currentTextDocument.getText(); @@ -66,6 +66,15 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { } else { return !!libs.loadLibrary(path); } + }, + directoryExists: (path: string): boolean => { + // typescript tries to first find libraries in node_modules/@types and node_modules/@typescript + // there's no node_modules in our setup + if (path.startsWith('node_modules')) { + return false; + } + return true; + } }; return ts.createLanguageService(host); From 1ed660b17e3c01e57937bbfb53aadf21407ceb11 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 25 May 2022 11:59:53 +0200 Subject: [PATCH 285/350] editor gutter accept buttons bug-fixing. --- .../contrib/mergeEditor/browser/editorGutter.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts index c4390489209..429457d5a50 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts @@ -86,13 +86,17 @@ export class EditorGutter< } const top = - this._editor.getTopForLineNumber(gutterItem.range.startLineNumber - 1) - - scrollTop + lineHeight; + (gutterItem.range.startLineNumber === 1 + ? -lineHeight + : this._editor.getTopForLineNumber( + gutterItem.range.startLineNumber - 1 + )) - + scrollTop + + lineHeight; - // +1 line and -lineHeight makes the height to cover view zones at the end of the range. const bottom = - this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive + 1) - - scrollTop - lineHeight; + this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive) - + scrollTop; const height = bottom - top; From e4f7f6a9da6cdc06c5d4f425bb03424cfb604536 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 25 May 2022 03:27:58 -0700 Subject: [PATCH 286/350] Add PasteEditProvider (#107283) For #30066 This adds a new `documentPaste` api proposal that lets extensions hook into copy and paste. This can be used to do things such as: - Create link when pasting an image - Bring along imports when copy and pasting code --- .../markdown-language-features/package.json | 9 +- .../package.nls.json | 3 +- .../src/extension.ts | 4 +- .../src/languageFeatures/copyPaste.ts | 26 +++ .../src/languageFeatures/dropIntoEditor.ts | 78 +++---- .../markdown-language-features/tsconfig.json | 3 +- src/vs/base/common/dataTransfer.ts | 4 + src/vs/editor/browser/dnd.ts | 28 +++ src/vs/editor/common/languages.ts | 9 + .../common/services/languageFeatures.ts | 4 +- .../services/languageFeaturesService.ts | 3 +- .../browser/copyPasteContribution.ts | 25 +++ .../copyPaste/browser/copyPasteController.ts | 195 ++++++++++++++++++ .../browser/dropIntoEditorContribution.ts | 1 + src/vs/editor/editor.all.ts | 1 + .../api/browser/mainThreadLanguageFeatures.ts | 47 ++++- .../workbench/api/common/extHost.api.impl.ts | 4 + .../workbench/api/common/extHost.protocol.ts | 3 + .../api/common/extHostLanguageFeatures.ts | 69 ++++++- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.documentPaste.d.ts | 46 +++++ 21 files changed, 512 insertions(+), 51 deletions(-) create mode 100644 extensions/markdown-language-features/src/languageFeatures/copyPaste.ts create mode 100644 src/vs/editor/browser/dnd.ts create mode 100644 src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts create mode 100644 src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts create mode 100644 src/vscode-dts/vscode.proposed.documentPaste.d.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 4ef07a0dd65..f2ad883243f 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -16,7 +16,8 @@ "Programming Languages" ], "enabledApiProposals": [ - "textEditorDrop" + "textEditorDrop", + "documentPaste" ], "activationEvents": [ "onLanguage:markdown", @@ -414,6 +415,12 @@ "markdownDescription": "%configuration.markdown.editor.drop.enabled%", "scope": "resource" }, + "markdown.experimental.editor.pasteLinks.enabled": { + "type": "boolean", + "default": false, + "markdownDescription": "%configuration.markdown.editor.pasteLinks.enabled%", + "scope": "resource" + }, "markdown.experimental.validate.enabled": { "type": "boolean", "scope": "resource", diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 2c8977cf2e0..1f3598e3fd8 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -28,7 +28,8 @@ "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index f088f91391a..0f2399692e9 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -6,8 +6,9 @@ import * as vscode from 'vscode'; import { CommandManager } from './commandManager'; import * as commands from './commands/index'; -import { register as registerDiagnostics } from './languageFeatures/diagnostics'; +import { registerPasteProvider } from './languageFeatures/copyPaste'; import { MdDefinitionProvider } from './languageFeatures/definitionProvider'; +import { register as registerDiagnostics } from './languageFeatures/diagnostics'; import { MdLinkProvider } from './languageFeatures/documentLinkProvider'; import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider'; import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor'; @@ -78,6 +79,7 @@ function registerMarkdownLanguageFeatures( MdPathCompletionProvider.register(selector, engine, linkProvider), registerDiagnostics(selector, engine, workspaceContents, linkProvider, commandManager), registerDropIntoEditor(selector), + registerPasteProvider(selector), registerFindFileReferences(commandManager, referencesProvider), ); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts new file mode 100644 index 00000000000..d9e939b463c --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { tryInsertUriList } from './dropIntoEditor'; + +export function registerPasteProvider(selector: vscode.DocumentSelector) { + return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider { + + async provideDocumentPasteEdits( + document: vscode.TextDocument, + range: vscode.Range, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', false); + if (!enabled) { + return; + } + + return tryInsertUriList(document, range, dataTransfer, token); + } + }); +} diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index d5d7c4d9d29..2ad71ec0516 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -32,45 +32,45 @@ export function registerDropIntoEditor(selector: vscode.DocumentSelector) { } const replacementRange = new vscode.Range(position, position); - return this.tryInsertUriList(document, replacementRange, dataTransfer, token); - } - - private async tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { - const urlList = await dataTransfer.get('text/uri-list')?.asString(); - if (!urlList || token.isCancellationRequested) { - return undefined; - } - - const uris: vscode.Uri[] = []; - for (const resource of urlList.split('\n')) { - try { - uris.push(vscode.Uri.parse(resource)); - } catch { - // noop - } - } - - if (!uris.length) { - return; - } - - const snippet = new vscode.SnippetString(); - uris.forEach((uri, i) => { - const mdPath = document.uri.scheme === uri.scheme - ? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/')) - : uri.toString(false); - - const ext = URI.Utils.extname(uri).toLowerCase(); - snippet.appendText(imageFileExtensions.has(ext) ? '![' : '['); - snippet.appendTabstop(); - snippet.appendText(`](${mdPath})`); - - if (i <= uris.length - 1 && uris.length > 1) { - snippet.appendText(' '); - } - }); - - return new vscode.SnippetTextEdit(replacementRange, snippet); + return tryInsertUriList(document, replacementRange, dataTransfer, token); } }); } + +export async function tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { + const urlList = await dataTransfer.get('text/uri-list')?.asString(); + if (!urlList || token.isCancellationRequested) { + return undefined; + } + + const uris: vscode.Uri[] = []; + for (const resource of urlList.split('\n')) { + try { + uris.push(vscode.Uri.parse(resource)); + } catch { + // noop + } + } + + if (!uris.length) { + return; + } + + const snippet = new vscode.SnippetString(); + uris.forEach((uri, i) => { + const mdPath = document.uri.scheme === uri.scheme + ? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/')) + : uri.toString(false); + + const ext = URI.Utils.extname(uri).toLowerCase(); + snippet.appendText(imageFileExtensions.has(ext) ? '![' : '['); + snippet.appendTabstop(); + snippet.appendText(`](${mdPath})`); + + if (i <= uris.length - 1 && uris.length > 1) { + snippet.appendText(' '); + } + }); + + return new vscode.SnippetTextEdit(replacementRange, snippet); +} diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index 775eaa7c0a8..7c1d4a7fca8 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -7,6 +7,7 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.textEditorDrop.d.ts", - "../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts" + "../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts", + "../../src/vscode-dts/vscode.proposed.documentPaste.d.ts" ] } diff --git a/src/vs/base/common/dataTransfer.ts b/src/vs/base/common/dataTransfer.ts index 1f56e79f751..d5d99bd283b 100644 --- a/src/vs/base/common/dataTransfer.ts +++ b/src/vs/base/common/dataTransfer.ts @@ -37,6 +37,10 @@ export class VSDataTransfer { this._data.set(mimeType, value); } + public delete(mimeType: string) { + this._data.delete(mimeType); + } + public setString(mimeType: string, stringOrPromise: string | Promise) { this.set(mimeType, { asString: async () => stringOrPromise, diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts new file mode 100644 index 00000000000..08a3f558786 --- /dev/null +++ b/src/vs/editor/browser/dnd.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { URI } from 'vs/base/common/uri'; + + +export function toVSDataTransfer(dataTransfer: DataTransfer) { + const vsDataTransfer = new VSDataTransfer(); + for (const item of dataTransfer.items) { + const type = item.type; + if (item.kind === 'string') { + const asStringValue = new Promise(resolve => item.getAsString(resolve)); + vsDataTransfer.setString(type, asStringValue); + } else if (item.kind === 'file') { + const file = item.getAsFile() as null | (File & { path?: string }); + if (file) { + const uri = file.path ? URI.parse(file.path) : undefined; + vsDataTransfer.setFile(type, file.name, uri, async () => { + return new Uint8Array(await file.arrayBuffer()); + }); + } + } + } + return vsDataTransfer; +} diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 647dc1fa131..6ad570fcd54 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -721,6 +721,15 @@ export interface CodeActionProvider { _getAdditionalMenuItems?(context: CodeActionContext, actions: readonly CodeAction[]): Command[]; } +/** + * @internal + */ +export interface DocumentPasteEditProvider { + prepareDocumentPaste?(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise; + + provideDocumentPasteEdits(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise; +} + /** * Represents a parameter of a callable-signature. A parameter can * have a label and a doc-comment. diff --git a/src/vs/editor/common/services/languageFeatures.ts b/src/vs/editor/common/services/languageFeatures.ts index e0ba2416582..2c80887df1a 100644 --- a/src/vs/editor/common/services/languageFeatures.ts +++ b/src/vs/editor/common/services/languageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentPasteEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguageFeaturesService = createDecorator('ILanguageFeaturesService'); @@ -25,6 +25,8 @@ export interface ILanguageFeaturesService { readonly codeActionProvider: LanguageFeatureRegistry; + readonly documentPasteEditProvider: LanguageFeatureRegistry; + readonly renameProvider: LanguageFeatureRegistry; readonly documentFormattingEditProvider: LanguageFeatureRegistry; diff --git a/src/vs/editor/common/services/languageFeaturesService.ts b/src/vs/editor/common/services/languageFeaturesService.ts index 2ed7cebcf62..4059a90b370 100644 --- a/src/vs/editor/common/services/languageFeaturesService.ts +++ b/src/vs/editor/common/services/languageFeaturesService.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry'; -import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; +import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DocumentPasteEditProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -41,6 +41,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService { readonly documentRangeSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentSemanticTokensProvider = new LanguageFeatureRegistry(this._score.bind(this)); readonly documentOnDropEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); + readonly documentPasteEditProvider = new LanguageFeatureRegistry(this._score.bind(this)); private _notebookTypeResolver?: NotebookInfoResolver; diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts new file mode 100644 index 00000000000..55c120d060f --- /dev/null +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteContribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; +import { CopyPasteController } from 'vs/editor/contrib/copyPaste/browser/copyPasteController'; +import * as nls from 'vs/nls'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; + +registerEditorContribution(CopyPasteController.ID, CopyPasteController); + +Registry.as(Extensions.Configuration).registerConfiguration({ + ...editorConfigurationBaseNode, + properties: { + 'editor.experimental.pasteActions.enabled': { + type: 'boolean', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + description: nls.localize('pasteActions', "Enable/disable running edits from extensions on paste."), + default: false, + }, + } +}); diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts new file mode 100644 index 00000000000..6c203af56cd --- /dev/null +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener } from 'vs/base/browser/dom'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Mimes } from 'vs/base/common/mime'; +import { generateUuid } from 'vs/base/common/uuid'; +import { toVSDataTransfer } from 'vs/editor/browser/dnd'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { Selection } from 'vs/editor/common/core/selection'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { DocumentPasteEditProvider, SnippetTextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; +import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +const vscodeClipboardMime = 'application/vnd.code.copyId'; + +class DefaultPasteEditProvider implements DocumentPasteEditProvider { + + async provideDocumentPasteEdits(model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise { + const textDataTransfer = dataTransfer.get(Mimes.text) ?? dataTransfer.get('text'); + if (textDataTransfer) { + const text = await textDataTransfer.asString(); + return { + edits: [new ResourceTextEdit(model.uri, { range: selection, text })] + }; + } + + return undefined; + } +} + +export class CopyPasteController extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.copyPasteActionController'; + + public static get(editor: ICodeEditor): CopyPasteController { + return editor.getContribution(CopyPasteController.ID)!; + } + + private readonly _editor: ICodeEditor; + + private _currentClipboardItem: undefined | { + readonly handle: string; + readonly dataTransferPromise: CancelablePromise; + }; + + constructor( + editor: ICodeEditor, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @IClipboardService private readonly _clipboardService: IClipboardService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + ) { + super(); + + this._editor = editor; + + this._languageFeaturesService.documentPasteEditProvider.register('*', new DefaultPasteEditProvider()); + + const container = editor.getContainerDomNode(); + + this._register(addDisposableListener(container, 'copy', (e: ClipboardEvent) => { + if (!e.clipboardData) { + return; + } + + const model = editor.getModel(); + const selection = this._editor.getSelection(); + if (!model || !selection) { + return; + } + + if (!this.arePasteActionsEnabled(model)) { + return; + } + + const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model).filter(x => !!x.prepareDocumentPaste); + if (!providers.length) { + return; + } + + const dataTransfer = toVSDataTransfer(e.clipboardData); + + // Save off a handle pointing to data that VS Code maintains. + const handle = generateUuid(); + e.clipboardData.setData(vscodeClipboardMime, handle); + + const promise = createCancelablePromise(async token => { + const results = await Promise.all(providers.map(provider => { + return provider.prepareDocumentPaste!(model, selection, dataTransfer, token); + })); + + for (const result of results) { + result?.forEach((value, key) => { + dataTransfer.set(key, value); + }); + } + + return dataTransfer; + }); + + this._currentClipboardItem?.dataTransferPromise.cancel(); + this._currentClipboardItem = { handle: handle, dataTransferPromise: promise }; + })); + + this._register(addDisposableListener(container, 'paste', async (e: ClipboardEvent) => { + const selection = this._editor.getSelection(); + if (!e.clipboardData || !selection || !editor.hasModel()) { + return; + } + + const model = editor.getModel(); + if (!this.arePasteActionsEnabled(model)) { + return; + } + + const originalDocVersion = model.getVersionId(); + + const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model); + if (!providers.length) { + return; + } + + const handle = e.clipboardData?.getData(vscodeClipboardMime); + if (typeof handle !== 'string') { + return; + } + + e.preventDefault(); + e.stopImmediatePropagation(); + + const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection); + + try { + const dataTransfer = toVSDataTransfer(e.clipboardData); + + if (handle && this._currentClipboardItem?.handle === handle) { + const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise; + toMergeDataTransfer.forEach((value, key) => { + dataTransfer.set(key, value); + }); + } + + if (!dataTransfer.has(Mimes.uriList)) { + const resources = await this._clipboardService.readResources(); + if (resources.length) { + const value = resources.join('\n'); + dataTransfer.set(Mimes.uriList, { + value, + asString: async () => value, + asFile: () => undefined, + }); + } + } + + dataTransfer.delete(vscodeClipboardMime); + + for (const provider of providers) { + const edit = await provider.provideDocumentPasteEdits(model, selection, dataTransfer, tokenSource.token); + if (originalDocVersion !== model.getVersionId()) { + return; + } + + if (edit) { + if ((edit as WorkspaceEdit).edits) { + await this._bulkEditService.apply(ResourceEdit.convert(edit as WorkspaceEdit), { editor }); + } else { + performSnippetEdit(editor, edit as SnippetTextEdit); + } + return; + } + } + } finally { + tokenSource.dispose(); + } + }, true)); + } + + public arePasteActionsEnabled(model: ITextModel): boolean { + return this._configurationService.getValue('editor.experimental.pasteActions.enabled', { + resource: model.uri + }); + } +} diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index f45143e4e0e..b713b71a7dd 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -186,3 +186,4 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController); + diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index aba1e89aa1d..1168ef7bb16 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -15,6 +15,7 @@ import 'vs/editor/contrib/clipboard/browser/clipboard'; import 'vs/editor/contrib/codeAction/browser/codeActionContributions'; import 'vs/editor/contrib/codelens/browser/codelensController'; import 'vs/editor/contrib/colorPicker/browser/colorContributions'; +import 'vs/editor/contrib/copyPaste/browser/copyPasteContribution'; import 'vs/editor/contrib/comment/browser/comment'; import 'vs/editor/contrib/contextmenu/browser/contextmenu'; import 'vs/editor/contrib/cursorUndo/browser/cursorUndo'; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index ff3bd15f82a..f89e0698b9d 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -5,6 +5,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -23,14 +24,13 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceEditDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -366,6 +366,47 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread this._registrations.set(handle, this._languageFeaturesService.codeActionProvider.register(selector, provider)); } + // --- copy paste action provider + + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void { + const provider: languages.DocumentPasteEditProvider = { + prepareDocumentPaste: supportsCopy + ? async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise => { + const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); + if (token.isCancellationRequested) { + return undefined; + } + + const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selection, dataTransferDto, token); + if (!result) { + return undefined; + } + + + const dataTransferOut = new VSDataTransfer(); + result.items.forEach(([type, item]) => { + dataTransferOut.setString(type, item.asString); + }); + return dataTransferOut; + } + : undefined, + + provideDocumentPasteEdits: async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken) => { + const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); + const result = await this._proxy.$providePasteEdits(handle, model.uri, selection, d, token); + if (!result) { + return; + } else if ((result as IWorkspaceEditDto).edits) { + return reviveWorkspaceEditDto(result as IWorkspaceEditDto); + } else { + return result as languages.SnippetTextEdit; + } + } + }; + + this._registrations.set(handle, this._languageFeaturesService.documentPasteEditProvider.register(selector, provider)); + } + // --- formatting $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 364272a3ecd..9c1a1e66eed 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -457,6 +457,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); }, + registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + checkProposedApiEnabled(extension, 'documentPaste'); + return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider); + }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8975b45a790..3b29ae91432 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -372,6 +372,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; @@ -1730,6 +1731,8 @@ export interface ExtHostLanguageFeaturesShape { $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCodeActions(handle: number, cacheId: number): void; + $prepareDocumentPaste(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise; + $providePasteEdits(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise | undefined>; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 53f5ad08081..4f06e60d82c 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 { 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, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind, WorkspaceEdit } 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'; @@ -31,7 +31,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Cache } from './cache'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { isCancellationError } from 'vs/base/common/errors'; +import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -485,6 +485,52 @@ class CodeActionAdapter { } } +class DocumentPasteEditProvider { + + constructor( + private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape, + private readonly _documents: ExtHostDocuments, + private readonly _provider: vscode.DocumentPasteEditProvider, + private readonly _handle: number, + ) { } + + async prepareDocumentPaste(resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + if (!this._provider.prepareDocumentPaste) { + return undefined; + } + + const doc = this._documents.getDocument(resource); + const vscodeRange = typeConvert.Range.to(range); + + const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, () => { + throw new NotImplementedError(); + }); + await this._provider.prepareDocumentPaste(doc, vscodeRange, dataTransfer, token); + + return typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); + } + + async providePasteEdits(requestId: number, resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise> { + const doc = this._documents.getDocument(resource); + const vscodeRange = typeConvert.Range.to(range); + + const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { + return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer; + }); + + const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRange, dataTransfer, token); + if (!edit) { + return; + } + + if (edit instanceof WorkspaceEdit) { + return typeConvert.WorkspaceEdit.from(edit); + } else { + return typeConvert.SnippetTextEdit.from(edit as vscode.SnippetTextEdit); + } + } +} + class DocumentFormattingAdapter { constructor( @@ -1769,7 +1815,7 @@ class DocumentOnDropEditAdapter { } type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter - | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter + | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentPasteEditProvider | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | CompletionsAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter @@ -2413,6 +2459,23 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } + // --- copy/paste actions + + registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + const handle = this._nextHandle(); + this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle), extension)); + this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste); + return this._createDisposable(handle); + } + + $prepareDocumentPaste(handle: number, resource: UriComponents, range: IRange, dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), range, dataTransfer, token), undefined, token); + } + + $providePasteEdits(handle: number, resource: UriComponents, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise | undefined> { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), range, dataTransferDto, token), undefined, token); + } + // --- configuration private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 3c4bf725a84..e2f86dc13a9 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -18,6 +18,7 @@ export const allApiProposals = Object.freeze({ dataTransferFiles: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', + documentPaste: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentPaste.d.ts', editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', extensionRuntime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', extensionsAny: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts', diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts new file mode 100644 index 00000000000..1777c8d4145 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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/30066/ + + /** + * Provider invoked when the user copies and pastes code. + */ + interface DocumentPasteEditProvider { + + /** + * Optional method invoked after the user copies text in a file. + * + * During {@link prepareDocumentPaste}, an extension can compute metadata that is attached to + * a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}. + * + * @param document Document where the copy took place. + * @param range Range being copied in the `document`. + * @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}. + * @param token A cancellation token. + */ + prepareDocumentPaste?(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): void | Thenable; + + /** + * Invoked before the user pastes into a document. + * + * In this method, extensions can return a workspace edit that replaces the standard pasting behavior. + * + * @param document Document being pasted into + * @param range Currently selected range in the document. + * @param dataTransfer The data transfer associated with the paste. + * @param token A cancellation token. + * + * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. + */ + provideDocumentPasteEdits(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + } + + namespace languages { + export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider): Disposable; + } +} From 52c5c31139826f8c3448c22e6c9472376ea40ba8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 25 May 2022 12:31:56 +0200 Subject: [PATCH 287/350] use fake timers (#150356) --- .../test/common/userDataSyncStoreService.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 4790464ce4a..96031d04737 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -484,14 +484,14 @@ suite('UserDataSyncRequestsSession', () => { assert.fail('Should fail with limit exceeded'); }); - test('requests are handled after session is expired', async () => { + test('requests are handled after session is expired', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const testObject = new RequestsSession(1, 100, requestService, new NullLogService()); await testObject.request('url', {}, CancellationToken.None); await timeout(125); await testObject.request('url', {}, CancellationToken.None); - }); + })); - test('too many requests are thrown after session is expired', async () => { + test('too many requests are thrown after session is expired', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const testObject = new RequestsSession(1, 100, requestService, new NullLogService()); await testObject.request('url', {}, CancellationToken.None); await timeout(125); @@ -505,6 +505,6 @@ suite('UserDataSyncRequestsSession', () => { return; } assert.fail('Should fail with limit exceeded'); - }); + })); }); From 73dda0c06add139307f6be753cea50028bcde05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 25 May 2022 14:42:17 +0200 Subject: [PATCH 288/350] remove UpdateMode policy (#150357) --- build/azure-pipelines/win32/product-build-win32.yml | 7 ------- build/gulpfile.vscode.js | 3 --- .../platform/update/common/update.config.contribution.ts | 6 +----- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 4b746afc2a1..7501869b57d 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -136,13 +136,6 @@ steps: displayName: Download Electron condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build\lib\policies } - displayName: Generate Group Policy definitions - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 94b22026a07..7dff1645210 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -330,9 +330,6 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); - result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) - .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); - } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@PRODNAME@@', product.nameLong)) diff --git a/src/vs/platform/update/common/update.config.contribution.ts b/src/vs/platform/update/common/update.config.contribution.ts index 4134233def6..5ceb95727b5 100644 --- a/src/vs/platform/update/common/update.config.contribution.ts +++ b/src/vs/platform/update/common/update.config.contribution.ts @@ -27,11 +27,7 @@ configurationRegistry.registerConfiguration({ localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), localize('start', "Check for updates only on startup. Disable automatic background update checks."), localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically.") - ], - policy: { - name: 'UpdateMode', - minimumVersion: '1.67', - } + ] }, 'update.channel': { type: 'string', From 986ef1c76d5c6a031c272c4172d87e6220011834 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 25 May 2022 15:43:11 +0200 Subject: [PATCH 289/350] Disable Terrapin for OSS builds (#150374) --- build/azure-pipelines/product-build-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 5b09f8ee77e..3c45858413e 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -21,7 +21,7 @@ variables: - name: skipComponentGovernanceDetection value: true - name: ENABLE_TERRAPIN - value: true + value: false - name: VSCODE_PUBLISH value: false - name: VSCODE_QUALITY From 07655f3a23b9b168e7ed351a754fc77bd7faf3ff Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 25 May 2022 16:21:16 +0200 Subject: [PATCH 290/350] use remote cli when in remote terminal (#150372) --- build/gulpfile.vscode.js | 3 ++- resources/darwin/bin/code.sh | 9 +++++++++ resources/linux/bin/code.sh | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 7dff1645210..268650959cd 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -288,6 +288,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' })); } else if (platform === 'darwin') { const shortcut = gulp.src('resources/darwin/bin/code.sh') + .pipe(replace('@@APPNAME@@', product.applicationName)) .pipe(rename('bin/code')); all = es.merge(all, shortcut); @@ -333,7 +334,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@PRODNAME@@', product.nameLong)) - .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@APPNAME@@', product.applicationName)) .pipe(rename('bin/' + product.applicationName))); } diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index eecdf9c68b5..8c058727071 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -3,6 +3,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. +# when run in remote terminal, use the remote cli +if [ -n "$VSCODE_IPC_HOOK_CLI" ]; then + REMOTE_CLI="$(which -a '@@APPNAME@@' | grep /remote-cli/)" + if [ -n "$REMOTE_CLI" ]; then + "$REMOTE_CLI" "$@" + exit $? + fi +fi + function app_realpath() { SOURCE=$1 while [ -h "$SOURCE" ]; do diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index bfebec1aa8e..5fe68cb4f3e 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -3,9 +3,18 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. +# when run in remote terminal, use the remote cli +if [ -n "$VSCODE_IPC_HOOK_CLI" ]; then + REMOTE_CLI="$(which -a '@@APPNAME@@' | grep /remote-cli/)" + if [ -n "$REMOTE_CLI" ]; then + "$REMOTE_CLI" "$@" + exit $? + fi +fi + # test that VSCode wasn't installed inside WSL if grep -qi Microsoft /proc/version && [ -z "$DONT_PROMPT_WSL_INSTALL" ]; then - echo "To use @@PRODNAME@@ with the Windows Subsystem for Linux, please install @@PRODNAME@@ in Windows and uninstall the Linux version in WSL. You can then use the \`@@NAME@@\` command in a WSL terminal just as you would in a normal command prompt." 1>&2 + echo "To use @@PRODNAME@@ with the Windows Subsystem for Linux, please install @@PRODNAME@@ in Windows and uninstall the Linux version in WSL. You can then use the \`@@APPNAME@@\` command in a WSL terminal just as you would in a normal command prompt." 1>&2 printf "Do you want to continue anyway? [y/N] " 1>&2 read -r YN YN=$(printf '%s' "$YN" | tr '[:upper:]' '[:lower:]') @@ -44,11 +53,11 @@ else VSCODE_PATH="$(dirname "$(readlink -f "$0")")/.." else # else use the standard install location - VSCODE_PATH="/usr/share/@@NAME@@" + VSCODE_PATH="/usr/share/@@APPNAME@@" fi fi -ELECTRON="$VSCODE_PATH/@@NAME@@" +ELECTRON="$VSCODE_PATH/@@APPNAME@@" CLI="$VSCODE_PATH/resources/app/out/cli.js" ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" exit $? From 38931b6a3db467bf1c5ba51f053ddedbeeed6acd Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 25 May 2022 17:08:15 +0200 Subject: [PATCH 291/350] add `git.experimental.mergeEditor` setting to enable/disable merge editor for conflicting files --- extensions/git/package.json | 6 ++++++ extensions/git/package.nls.json | 1 + extensions/git/src/repository.ts | 37 +++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index bb077b6f23d..b5ad659a010 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2445,6 +2445,12 @@ ], "markdownDescription": "%config.logLevel%", "scope": "window" + }, + "git.experimental.mergeEditor": { + "type": "boolean", + "default": false, + "markdownDescription": "%config.experimental.mergeEditor%", + "scope": "window" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index e43df92114f..e1a8c6a9faf 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -236,6 +236,7 @@ "config.logLevel.error": "Log only error, and critical information", "config.logLevel.critical": "Log only critical information", "config.logLevel.off": "Log nothing", + "config.experimental.mergeEditor": "Open the _experimental_ merge editor for files that are currently under conflict.", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ebe1105ab94..61548892e7c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -296,6 +296,10 @@ export class Resource implements SourceControlResourceState { const command = this._commandResolver.resolveChangeCommand(this); await commands.executeCommand(command.command, ...(command.arguments || [])); } + + clone() { + return new Resource(this._commandResolver, this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri); + } } export const enum Operation { @@ -602,18 +606,21 @@ class ResourceCommandResolver { resolveChangeCommand(resource: Resource): Command { const title = this.getTitle(resource); - if (!resource.leftUri && resource.rightUri && resource.type === Status.BOTH_MODIFIED) { - return { - command: '_git.openMergeEditor', - title: localize('open.merge', "Open Merge"), - arguments: [resource.rightUri] - }; - } else if (!resource.leftUri) { - return { - command: 'vscode.open', - title: localize('open', "Open"), - arguments: [resource.rightUri, { override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title] - }; + if (!resource.leftUri) { + const bothModified = resource.type === Status.BOTH_MODIFIED; + if (resource.rightUri && bothModified && workspace.getConfiguration('git').get('experimental.mergeEditor', false)) { + return { + command: '_git.openMergeEditor', + title: localize('open.merge', "Open Merge"), + arguments: [resource.rightUri] + }; + } else { + return { + command: 'vscode.open', + title: localize('open', "Open"), + arguments: [resource.rightUri, { override: bothModified ? false : undefined }, title] + }; + } } else { return { command: 'vscode.diff', @@ -918,6 +925,12 @@ export class Repository implements Disposable { onConfigListener(updateIndexGroupVisibility, this, this.disposables); updateIndexGroupVisibility(); + workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('git.experimental.mergeEditor')) { + this.mergeGroup.resourceStates = this.mergeGroup.resourceStates.map(r => r.clone()); + } + }, undefined, this.disposables); + filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root) || e.affectsConfiguration('git.untrackedChanges', root) From 97f8e66d74b7b2d9cefbcb8db8589aa55c0e9cb9 Mon Sep 17 00:00:00 2001 From: Jonas Dellinger Date: Wed, 25 May 2022 17:16:10 +0200 Subject: [PATCH 292/350] A full editor can be used as git commit message editor (#95266) Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- extensions/git/extension.webpack.config.js | 3 +- extensions/git/package.json | 62 +++++++++++++----- extensions/git/package.nls.json | 2 + extensions/git/src/api/git.d.ts | 3 + extensions/git/src/askpass.ts | 14 +--- extensions/git/src/commands.ts | 39 +++++++++-- extensions/git/src/git.ts | 34 ++++++++-- extensions/git/src/gitEditor/gitEditor.ts | 64 +++++++++++++++++++ extensions/git/src/gitEditor/main.ts | 21 ++++++ .../gitEditor/scripts/git-editor-empty.bat | 1 + .../src/gitEditor/scripts/git-editor-empty.sh | 1 + .../git/src/gitEditor/scripts/git-editor.bat | 4 ++ .../git/src/gitEditor/scripts/git-editor.sh | 4 ++ extensions/git/src/main.ts | 17 ++++- extensions/git/src/repository.ts | 7 ++ extensions/git/tsconfig.json | 2 + src/vs/workbench/contrib/scm/browser/util.ts | 2 +- 17 files changed, 235 insertions(+), 45 deletions(-) create mode 100644 extensions/git/src/gitEditor/gitEditor.ts create mode 100644 extensions/git/src/gitEditor/main.ts create mode 100644 extensions/git/src/gitEditor/scripts/git-editor-empty.bat create mode 100755 extensions/git/src/gitEditor/scripts/git-editor-empty.sh create mode 100644 extensions/git/src/gitEditor/scripts/git-editor.bat create mode 100755 extensions/git/src/gitEditor/scripts/git-editor.sh diff --git a/extensions/git/extension.webpack.config.js b/extensions/git/extension.webpack.config.js index 5efa2052e88..ee6203af47e 100644 --- a/extensions/git/extension.webpack.config.js +++ b/extensions/git/extension.webpack.config.js @@ -13,6 +13,7 @@ module.exports = withDefaults({ context: __dirname, entry: { main: './src/main.ts', - ['askpass-main']: './src/askpass-main.ts' + ['askpass-main']: './src/askpass-main.ts', + ['git-editor-main']: './src/gitEditor/main.ts' } }); diff --git a/extensions/git/package.json b/extensions/git/package.json index de341a095ee..1c6ca2f4aff 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -12,7 +12,9 @@ "enabledApiProposals": [ "diffCommand", "contribViewsWelcome", + "resolvers", "scmActionButton", + "scmInput", "scmSelectedProvider", "scmValidation", "timeline" @@ -211,83 +213,99 @@ "command": "git.commit", "title": "%command.commit%", "category": "Git", - "icon": "$(check)" + "icon": "$(check)", + "enablement": "!commitInProgress" }, { "command": "git.commitStaged", "title": "%command.commitStaged%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitEmpty", "title": "%command.commitEmpty%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedSigned", "title": "%command.commitStagedSigned%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedAmend", "title": "%command.commitStagedAmend%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAll", "title": "%command.commitAll%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllSigned", "title": "%command.commitAllSigned%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllAmend", "title": "%command.commitAllAmend%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitNoVerify", "title": "%command.commitNoVerify%", "category": "Git", - "icon": "$(check)" + "icon": "$(check)", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedNoVerify", "title": "%command.commitStagedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitEmptyNoVerify", "title": "%command.commitEmptyNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedSignedNoVerify", "title": "%command.commitStagedSignedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedAmendNoVerify", "title": "%command.commitStagedAmendNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllNoVerify", "title": "%command.commitAllNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllSignedNoVerify", "title": "%command.commitAllSignedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllAmendNoVerify", "title": "%command.commitAllAmendNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.restoreCommitTemplate", @@ -1986,6 +2004,18 @@ "scope": "machine", "description": "%config.defaultCloneDirectory%" }, + "git.useEditorAsCommitInput": { + "type": "boolean", + "scope": "resource", + "description": "%config.useEditorAsCommitInput%", + "default": false + }, + "git.verboseCommit": { + "type": "boolean", + "scope": "resource", + "markdownDescription": "%config.verboseCommit%", + "default": false + }, "git.enableSmartCommit": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index ec691130508..d70493951b6 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -135,6 +135,8 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.ignoreRebaseWarning": "Ignores the warning when it looks like the branch might have been rebased when pulling.", "config.defaultCloneDirectory": "The default location to clone a git repository.", + "config.useEditorAsCommitInput": "Use an editor to author the commit message.", + "config.verboseCommit": "Enable verbose output when `#git.useEditorAsCommitInput#` is enabled.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", "config.smartCommitChanges.all": "Automatically stage all changes.", diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 7ba58a0e607..e552f03f25d 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -137,6 +137,8 @@ export interface CommitOptions { empty?: boolean; noVerify?: boolean; requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; } export interface FetchOptions { @@ -336,4 +338,5 @@ export const enum GitErrorCodes { PatchDoesNotApply = 'PatchDoesNotApply', NoPathFound = 'NoPathFound', UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage' } diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 81895a0e0d6..ffbd7e48a0e 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -6,9 +6,8 @@ import { window, InputBoxOptions, Uri, Disposable, workspace } from 'vscode'; import { IDisposable, EmptyDisposable, toDisposable } from './util'; import * as path from 'path'; -import { IIPCHandler, IIPCServer, createIPCServer } from './ipc/ipcServer'; +import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; import { CredentialsProvider, Credentials } from './api/git'; -import { OutputChannelLogger } from './log'; export class Askpass implements IIPCHandler { @@ -16,16 +15,7 @@ export class Askpass implements IIPCHandler { private cache = new Map(); private credentialsProviders = new Set(); - static async create(outputChannelLogger: OutputChannelLogger, context?: string): Promise { - try { - return new Askpass(await createIPCServer(context)); - } catch (err) { - outputChannelLogger.logError(`Failed to create git askpass IPC: ${err}`); - return new Askpass(); - } - } - - private constructor(private ipc?: IIPCServer) { + constructor(private ipc?: IIPCServer) { if (ipc) { this.disposable = ipc.registerHandler('askpass', this); } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6852639fcda..3597d62a52b 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1443,6 +1443,14 @@ export class CommandCenter { opts.signoff = true; } + if (config.get('useEditorAsCommitInput')) { + opts.useEditor = true; + + if (config.get('verboseCommit')) { + opts.verbose = true; + } + } + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges'); if ( @@ -1490,7 +1498,7 @@ export class CommandCenter { let message = await getCommitMessage(); - if (!message && !opts.amend) { + if (!message && !opts.amend && !opts.useEditor) { return false; } @@ -1550,10 +1558,13 @@ export class CommandCenter { private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise { const message = repository.inputBox.value; + const root = Uri.file(repository.root); + const config = workspace.getConfiguration('git', root); + const getCommitMessage = async () => { let _message: string | undefined = message; - if (!_message) { + if (!_message && !config.get('useEditorAsCommitInput')) { let value: string | undefined = undefined; if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { @@ -2937,7 +2948,7 @@ export class CommandCenter { }; let message: string; - let type: 'error' | 'warning' = 'error'; + let type: 'error' | 'warning' | 'information' = 'error'; const choices = new Map void>(); const openOutputChannelChoice = localize('open git log', "Open Git Log"); @@ -3000,6 +3011,12 @@ export class CommandCenter { message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git."); choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-setup-git'))); break; + case GitErrorCodes.EmptyCommitMessage: + message = localize('empty commit', "Commit operation was cancelled due to empty commit message."); + choices.clear(); + type = 'information'; + options.modal = false; + break; default: { const hint = (err.stderr || err.message || String(err)) .replace(/^error: /mi, '') @@ -3021,10 +3038,20 @@ export class CommandCenter { return; } + let result: string | undefined; const allChoices = Array.from(choices.keys()); - const result = type === 'error' - ? await window.showErrorMessage(message, options, ...allChoices) - : await window.showWarningMessage(message, options, ...allChoices); + + switch (type) { + case 'error': + result = await window.showErrorMessage(message, options, ...allChoices); + break; + case 'warning': + result = await window.showWarningMessage(message, options, ...allChoices); + break; + case 'information': + result = await window.showInformationMessage(message, options, ...allChoices); + break; + } if (result) { const resultFn = choices.get(result); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 8fdab7f659b..21fa0f0bf8e 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1397,20 +1397,37 @@ export class Repository { } async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { - const args = ['commit', '--quiet', '--allow-empty-message']; + const args = ['commit', '--quiet']; + const options: SpawnOptions = {}; + + if (message) { + options.input = message; + args.push('--file', '-'); + } + + if (opts.verbose) { + args.push('--verbose'); + } if (opts.all) { args.push('--all'); } - if (opts.amend && message) { + if (opts.amend) { args.push('--amend'); } - if (opts.amend && !message) { - args.push('--amend', '--no-edit'); - } else { - args.push('--file', '-'); + if (!opts.useEditor) { + if (!message) { + if (opts.amend) { + args.push('--no-edit'); + } else { + options.input = ''; + args.push('--file', '-'); + } + } + + args.push('--allow-empty-message'); } if (opts.signoff) { @@ -1435,7 +1452,7 @@ export class Repository { } try { - await this.exec(args, !opts.amend || message ? { input: message || '' } : {}); + await this.exec(args, options); } catch (commitErr) { await this.handleCommitError(commitErr); } @@ -1459,6 +1476,9 @@ export class Repository { if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; throw commitErr; + } else if (/Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { + commitErr.gitErrorCode = GitErrorCodes.EmptyCommitMessage; + throw commitErr; } try { diff --git a/extensions/git/src/gitEditor/gitEditor.ts b/extensions/git/src/gitEditor/gitEditor.ts new file mode 100644 index 00000000000..34f8ee9f20f --- /dev/null +++ b/extensions/git/src/gitEditor/gitEditor.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; +import { TabInputText, Uri, window, workspace } from 'vscode'; +import { IIPCHandler, IIPCServer } from '../ipc/ipcServer'; +import { EmptyDisposable, IDisposable } from '../util'; + +interface GitEditorRequest { + commitMessagePath?: string; +} + +export class GitEditor implements IIPCHandler { + + private disposable: IDisposable = EmptyDisposable; + + constructor(private ipc?: IIPCServer) { + if (ipc) { + this.disposable = ipc.registerHandler('git-editor', this); + } + } + + async handle({ commitMessagePath }: GitEditorRequest): Promise { + if (commitMessagePath) { + const uri = Uri.file(commitMessagePath); + const doc = await workspace.openTextDocument(uri); + await window.showTextDocument(doc, { preview: false }); + + return new Promise((c) => { + const onDidClose = window.tabGroups.onDidChangeTabs(async (tabs) => { + if (tabs.closed.some(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString())) { + onDidClose.dispose(); + return c(true); + } + }); + }); + } + } + + getEnv(): { [key: string]: string } { + if (!this.ipc) { + const fileType = process.platform === 'win32' ? 'bat' : 'sh'; + const gitEditor = path.join(__dirname, `scripts/git-editor-empty.${fileType}`); + + return { + GIT_EDITOR: `'${gitEditor}'` + }; + } + + const fileType = process.platform === 'win32' ? 'bat' : 'sh'; + const gitEditor = path.join(__dirname, `scripts/git-editor.${fileType}`); + + return { + GIT_EDITOR: `'${gitEditor}'`, + VSCODE_GIT_EDITOR_NODE: process.execPath, + VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'main.js') + }; + } + + dispose(): void { + this.disposable.dispose(); + } +} diff --git a/extensions/git/src/gitEditor/main.ts b/extensions/git/src/gitEditor/main.ts new file mode 100644 index 00000000000..661540e0030 --- /dev/null +++ b/extensions/git/src/gitEditor/main.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IPCClient } from '../ipc/ipcClient'; + +function fatal(err: any): void { + console.error(err); + process.exit(1); +} + +function main(argv: string[]): void { + const ipcClient = new IPCClient('git-editor'); + const commitMessagePath = argv[2]; + + ipcClient.call({ commitMessagePath }).then(() => { + setTimeout(() => process.exit(0), 0); + }).catch(err => fatal(err)); +} + +main(process.argv); diff --git a/extensions/git/src/gitEditor/scripts/git-editor-empty.bat b/extensions/git/src/gitEditor/scripts/git-editor-empty.bat new file mode 100644 index 00000000000..56eeb8a5801 --- /dev/null +++ b/extensions/git/src/gitEditor/scripts/git-editor-empty.bat @@ -0,0 +1 @@ +@ECHO off diff --git a/extensions/git/src/gitEditor/scripts/git-editor-empty.sh b/extensions/git/src/gitEditor/scripts/git-editor-empty.sh new file mode 100755 index 00000000000..1a2485251c3 --- /dev/null +++ b/extensions/git/src/gitEditor/scripts/git-editor-empty.sh @@ -0,0 +1 @@ +#!/bin/sh diff --git a/extensions/git/src/gitEditor/scripts/git-editor.bat b/extensions/git/src/gitEditor/scripts/git-editor.bat new file mode 100644 index 00000000000..ce28807cae1 --- /dev/null +++ b/extensions/git/src/gitEditor/scripts/git-editor.bat @@ -0,0 +1,4 @@ +@ECHO off + +set ELECTRON_RUN_AS_NODE=1 +"%VSCODE_GIT_EDITOR_NODE%" "%VSCODE_GIT_EDITOR_MAIN%" %* diff --git a/extensions/git/src/gitEditor/scripts/git-editor.sh b/extensions/git/src/gitEditor/scripts/git-editor.sh new file mode 100755 index 00000000000..f4ba2e4fd73 --- /dev/null +++ b/extensions/git/src/gitEditor/scripts/git-editor.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +ELECTRON_RUN_AS_NODE="1" \ +"$VSCODE_GIT_EDITOR_NODE" "$VSCODE_GIT_EDITOR_MAIN" $@ diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index ee60d2cb1b1..e5d99158a4e 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,6 +25,8 @@ import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; import { OutputChannelLogger } from './log'; +import { createIPCServer, IIPCServer } from './ipc/ipcServer'; +import { GitEditor } from './gitEditor/gitEditor'; const deactivateTasks: { (): Promise }[] = []; @@ -60,10 +62,21 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu return !skip; }); - const askpass = await Askpass.create(outputChannelLogger, context.storagePath); + let ipc: IIPCServer | undefined = undefined; + + try { + ipc = await createIPCServer(context.storagePath); + } catch (err) { + outputChannelLogger.logError(`Failed to create git IPC: ${err}`); + } + + const askpass = new Askpass(ipc); disposables.push(askpass); - const environment = askpass.getEnv(); + const gitEditor = new GitEditor(ipc); + disposables.push(gitEditor); + + const environment = { ...askpass.getEnv(), ...gitEditor.getEnv() }; const terminalEnvironmentManager = new TerminalEnvironmentManager(context, environment); disposables.push(terminalEnvironmentManager); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c337f91a7f5..b5ed89ac94b 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -450,6 +450,13 @@ class ProgressManager { const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root))); onDidChange(_ => this.updateEnablement()); this.updateEnablement(); + + this.repository.onDidChangeOperations(() => { + const commitInProgress = this.repository.operations.isRunning(Operation.Commit); + + this.repository.sourceControl.inputBox.enabled = !commitInProgress; + commands.executeCommand('setContext', 'commitInProgress', commitInProgress); + }); } private updateEnablement(): void { diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 13997275056..899f8c9c33d 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -11,7 +11,9 @@ "src/**/*", "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", + "../../src/vscode-dts/vscode.proposed.resolvers.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmInput.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", "../../src/vscode-dts/vscode.proposed.tabs.d.ts", diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 80d5d19302d..c8f4524d017 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -37,7 +37,7 @@ export function isSCMResource(element: any): element is ISCMResource { return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup); } -const compareActions = (a: IAction, b: IAction) => a.id === b.id; +const compareActions = (a: IAction, b: IAction) => a.id === b.id && a.enabled === b.enabled; export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, primaryGroup?: string): IDisposable { let cachedDisposable: IDisposable = Disposable.None; From ce01702bbec4419cfb76ab10d2b04ce09d3f6600 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 25 May 2022 17:43:02 +0200 Subject: [PATCH 293/350] make git "accept from merge editor" command save the document and close the merge editor (uses heuristic to identify merge editor tab) --- extensions/git/src/commands.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index da619d4feb5..2805dcab768 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as picomatch from 'picomatch'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import * as nls from 'vscode-nls'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; @@ -1082,15 +1082,30 @@ export class CommandCenter { @command('git.acceptMerge') async acceptMerge(uri: Uri | unknown): Promise { - // TODO@jrieken make this proper, needs help from SCM folks if (!(uri instanceof Uri)) { return; } const repository = this.model.getRepository(uri); if (!repository) { + console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't belong to any repository`); return; } + + const doc = workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString()); + if (!doc) { + console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't match a document`); + return; + } + + await doc.save(); await repository.add([uri]); + + // TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor + // uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab + const { activeTab } = window.tabGroups.activeTabGroup; + if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) { + await window.tabGroups.close(activeTab, true); + } } private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise { From cdeaf55221397893f30ea52473d6f057476f9586 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 25 May 2022 17:55:57 +0200 Subject: [PATCH 294/350] Implements synchronized scrolling. --- .../contrib/audioCues/browser/observable.ts | 10 ++ .../mergeEditor/browser/mergeEditor.ts | 97 +++++++++++++++++-- .../mergeEditor/browser/mergeEditorModel.ts | 6 +- .../contrib/mergeEditor/browser/model.ts | 83 ++++++++++++++++ 4 files changed, 187 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts index 937f4b2f9cf..c3974606057 100644 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ b/src/vs/workbench/contrib/audioCues/browser/observable.ts @@ -612,3 +612,13 @@ export function wasEventTriggeredRecently(event: Event, timeoutMs: number, return observable; } + +/** + * This ensures the observable is kept up-to-date. + * This is useful when the observables `get` method is used. +*/ +export function keepAlive(observable: IObservable): IDisposable { + return autorun(reader => { + observable.read(reader); + }, 'keep-alive'); +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 0f0ef7c4cba..184d0812770 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -9,6 +9,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IAction } from 'vs/base/common/actions'; +import { findLast } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; @@ -40,9 +41,10 @@ import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorControl, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { autorun, derivedObservable, IObservable, ITransaction, keepAlive, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorModel'; +import { LineRange, ModifiedBaseRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; import { applyObservableDecorations, n, ReentrancyBarrier, setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { EditorGutter, IGutterItemInfo, IGutterItemView } from './editorGutter'; @@ -82,27 +84,67 @@ export class MergeEditor extends EditorPane { const reentrancyBarrier = new ReentrancyBarrier(); + const input1ResultMapping = derivedObservable('input1ResultMapping', reader => { + const model = this.input1View.model.read(reader); + if (!model) { + return undefined; + } + const resultDiffs = model.resultDiffs.read(reader); + const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input1, model.input1LinesDiffs, model.result, resultDiffs); + return modifiedBaseRanges; + }); + const input2ResultMapping = derivedObservable('input2ResultMapping', reader => { + const model = this.input2View.model.read(reader); + if (!model) { + return undefined; + } + const resultDiffs = model.resultDiffs.read(reader); + const modifiedBaseRanges = ModifiedBaseRange.fromDiffs(model.base, model.input2, model.input2LinesDiffs, model.result, resultDiffs); + return modifiedBaseRanges; + }); + + this._register(keepAlive(input1ResultMapping)); + this._register(keepAlive(input2ResultMapping)); + this._store.add(this.input1View.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { + const mapping = input1ResultMapping.get(); + if (!mapping) { + return; + } + synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mapping, 1); this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); - this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); }); } })); + this._store.add(this.input2View.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { + const mapping = input2ResultMapping.get(); + if (!mapping) { + return; + } + synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mapping, 1); this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); - this.inputResultView.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); }); } })); this._store.add(this.inputResultView.editor.onDidScrollChange(c => { if (c.scrollTopChanged) { reentrancyBarrier.runExclusively(() => { - this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); - this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); + const mapping = input1ResultMapping.get(); + if (!mapping) { + return; + } + synchronizeScrolling(this.inputResultView.editor, this.input1View.editor, mapping, 2); + + const mapping2 = input2ResultMapping.get(); + if (!mapping2) { + return; + } + synchronizeScrolling(this.inputResultView.editor, this.input2View.editor, mapping2, 2); }); } })); @@ -265,6 +307,49 @@ export class MergeEditor extends EditorPane { } } +function flip(value: 1 | 2): 1 | 2 { + return value === 1 ? 2 : 1; +} + +function synchronizeScrolling(scrollingEditor: CodeEditorWidget, targetEditor: CodeEditorWidget, mapping: ModifiedBaseRange[], sourceNumber: 1 | 2) { + const visibleRanges = scrollingEditor.getVisibleRanges(); + if (visibleRanges.length === 0) { + return; + } + const topLineNumber = visibleRanges[0].startLineNumber - 1; + + const firstBefore = findLast(mapping, r => r.getInputRange(sourceNumber).startLineNumber <= topLineNumber); + let sourceRange: LineRange; + let targetRange: LineRange; + + const targetNumber = flip(sourceNumber); + + if (firstBefore && firstBefore.getInputRange(sourceNumber).contains(topLineNumber)) { + sourceRange = firstBefore.getInputRange(sourceNumber); + targetRange = firstBefore.getInputRange(targetNumber); + } else if (firstBefore && firstBefore.getInputRange(sourceNumber).isEmpty && firstBefore.getInputRange(sourceNumber).startLineNumber === topLineNumber) { + sourceRange = firstBefore.getInputRange(sourceNumber).deltaEnd(1); + targetRange = firstBefore.getInputRange(targetNumber).deltaEnd(1); + } else { + const delta = firstBefore ? firstBefore.getInputRange(targetNumber).endLineNumberExclusive - firstBefore.getInputRange(sourceNumber).endLineNumberExclusive : 0; + sourceRange = new LineRange(topLineNumber, 1); + targetRange = new LineRange(topLineNumber + delta, 1); + } + + // sourceRange is not empty! + + const resultStartTopPx = targetEditor.getTopForLineNumber(targetRange.startLineNumber); + const resultEndPx = targetEditor.getTopForLineNumber(targetRange.endLineNumberExclusive); + + const sourceStartTopPx = scrollingEditor.getTopForLineNumber(sourceRange.startLineNumber); + const sourceEndPx = scrollingEditor.getTopForLineNumber(sourceRange.endLineNumberExclusive); + + const factor = (scrollingEditor.getScrollTop() - sourceStartTopPx) / (sourceEndPx - sourceStartTopPx); + const resultScrollPosition = resultStartTopPx + (resultEndPx - resultStartTopPx) * factor; + + targetEditor.setScrollTop(resultScrollPosition, ScrollType.Immediate); +} + interface ICodeEditorViewOptions { readonly: boolean; } @@ -272,7 +357,7 @@ interface ICodeEditorViewOptions { abstract class CodeEditorView extends Disposable { private readonly _model = new ObservableValue(undefined, 'model'); - protected readonly model: IObservable = this._model; + readonly model: IObservable = this._model; protected readonly htmlElements = n('div.code-view', [ n('div.title', { $: 'title' }), diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts index 024aa737d42..2c23f66b502 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorModel.ts @@ -101,8 +101,8 @@ export class MergeEditorModel extends EditorModel { readonly input2Detail: string | undefined, readonly input2Description: string | undefined, readonly result: ITextModel, - private readonly input1LinesDiffs: readonly LineDiff[], - private readonly input2LinesDiffs: readonly LineDiff[], + public readonly input1LinesDiffs: readonly LineDiff[], + public readonly input2LinesDiffs: readonly LineDiff[], resultDiffs: LineDiff[], private readonly editorWorkerService: IEditorWorkerService ) { @@ -164,7 +164,7 @@ export class MergeEditorModel extends EditorModel { this.input2LinesDiffs ); - private readonly modifiedBaseRangeStateStores = new Map>( + private readonly modifiedBaseRangeStateStores: ReadonlyMap> = new Map( this.modifiedBaseRanges.map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')])) ); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model.ts b/src/vs/workbench/contrib/mergeEditor/browser/model.ts index 9a045854182..9ee3176fc92 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model.ts @@ -102,6 +102,14 @@ export class LineRange { public equals(originalRange: LineRange) { return this.startLineNumber === originalRange.startLineNumber && this.lineCount === originalRange.lineCount; } + + public contains(lineNumber: number): boolean { + return this.startLineNumber <= lineNumber && lineNumber < this.endLineNumberExclusive; + } + + public deltaEnd(delta: number): LineRange { + return new LineRange(this.startLineNumber, this.lineCount + delta); + } } export class LineDiff { @@ -263,6 +271,9 @@ export class ModifiedBaseRange { const result = new Array(); function pushAndReset() { + if (currentDiffs[0].length === 0 && currentDiffs[1].length === 0) { + return; + } result.push(new ModifiedBaseRange( baseTextModel, input1TextModel, @@ -416,3 +427,75 @@ export class ModifiedBaseRangeState { return arr.join(','); } } + +/* +export class LineMappings { + public static fromDiffs( + diffs1: readonly LineDiff[], + diffs2: readonly LineDiff[], + inputLineCount: number, + ): LineMappings { + const compareByStartLineNumber = compareBy( + (d) => d.originalRange.startLineNumber, + numberComparator + ); + + const diffs = diffs1 + .map((diff) => ({ source: 0 as 0 | 1, diff })) + .concat(diffs2.map((diff) => ({ source: 1 as const, diff }))); + + diffs.sort(compareBy(d => d.diff, compareByStartLineNumber)); + + const currentDiffs = [ + new Array(), + new Array(), + ]; + let deltaFromBaseToInput = [0, 0]; + + const result = new Array(); + + function pushAndReset() { + result.push(LineMapping.create( + baseTextModel, + input1TextModel, + currentDiffs[0], + deltaFromBaseToInput[0], + input2TextModel, + currentDiffs[1], + deltaFromBaseToInput[1], + )); + currentDiffs[0] = []; + currentDiffs[1] = []; + } + + let currentRange: LineRange | undefined; + + for (const diff of diffs) { + const range = diff.diff.originalRange; + if (currentRange && !currentRange.touches(range)) { + pushAndReset(); + } + deltaFromBaseToInput[diff.source] = diff.diff.resultingDeltaFromOriginalToModified; + currentRange = currentRange ? currentRange.join(range) : range; + currentDiffs[diff.source].push(diff.diff); + } + pushAndReset(); + + return result; + } + + constructor(private readonly lineMappings: LineMapping[]) {} +} + +// A lightweight ModifiedBaseRange. Maybe they can be united? +export class LineMapping { + public static create(input: LineDiff, ): LineMapping { + + } + + constructor( + public readonly inputRange: LineRange, + public readonly resultRange: LineRange + ) { } +} +*/ From 63121f8039cb8dd6485655488a10252cdcaa64e5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 25 May 2022 18:02:31 +0200 Subject: [PATCH 295/350] Code polishing --- .../workbench/contrib/mergeEditor/browser/icons.ts | 10 ---------- .../mergeEditor/browser/media/mergeEditor.css | 2 +- .../contrib/mergeEditor/browser/mergeEditor.ts | 12 +++++++----- 3 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 src/vs/workbench/contrib/mergeEditor/browser/icons.ts diff --git a/src/vs/workbench/contrib/mergeEditor/browser/icons.ts b/src/vs/workbench/contrib/mergeEditor/browser/icons.ts deleted file mode 100644 index a37f0e66faf..00000000000 --- a/src/vs/workbench/contrib/mergeEditor/browser/icons.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Codicon } from 'vs/base/common/codicons'; -import { localize } from 'vs/nls'; -import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; - -export const acceptConflictIcon = registerIcon('merge-accept-conflict-icon', Codicon.check, localize('acceptConflictIcon', 'TODO.')); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css index abae99ac16c..363dffaaf6a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/media/mergeEditor.css @@ -33,7 +33,7 @@ cursor: pointer; } -.merge-accept-foo { +.merge-base-range-projection { background-color: rgb(170 175 170 / 15%); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts index 184d0812770..8c95edcc1fa 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.ts @@ -443,8 +443,8 @@ class InputCodeEditorView extends CodeEditorView { range: new Range(range.startLineNumber, 1, range.endLineNumberExclusive - 1, 1), options: { isWholeLine: true, - className: 'merge-accept-foo', - description: 'foo2' + className: 'merge-base-range-projection', + description: 'Base Range Projection' } }); } @@ -509,7 +509,8 @@ class MergeConflictGutterItemView extends Disposable implements IGutterItemView< target.classList.add('merge-accept-gutter-marker'); target.classList.add(item.range.lineCount > 1 ? 'multi-line' : 'single-line'); - const checkBox = new Toggle({ isChecked: false, title: 'TODO', icon: Codicon.check, actionClassName: 'monaco-checkbox' }); + // TODO: Tri-State-Toggle, localized title + const checkBox = new Toggle({ isChecked: false, title: 'Accept Merge', icon: Codicon.check, actionClassName: 'monaco-checkbox' }); checkBox.domNode.classList.add('accept-conflict-group'); this._register( @@ -550,8 +551,9 @@ class ResultCodeEditorView extends CodeEditorView { range: new Range(range.startLineNumber, 1, range.endLineNumberExclusive - 1, 1), options: { isWholeLine: true, - className: 'merge-accept-foo', - description: 'foo2' + // TODO + className: 'merge-base-range-projection', + description: 'Result Diff' } }); } From 4cf4438fc001e0c925905af51577cfc1db72f42c Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 25 May 2022 09:33:13 -0700 Subject: [PATCH 296/350] Ensure execution and kernel status visible when there are source commands contributed. --- .../browser/contrib/editorStatusBar/editorStatusBar.ts | 6 +++--- .../contrib/notebook/browser/controller/coreActions.ts | 4 ++-- .../notebook/browser/controller/executeActions.ts | 3 ++- .../browser/viewParts/notebookEditorWidgetContextKeys.ts | 9 ++++++++- .../contrib/notebook/common/notebookContextKeys.ts | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 20c8c594f12..4de014d82ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -22,7 +22,7 @@ import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow'; import { NOTEBOOK_ACTIONS_CATEGORY, SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { getNotebookEditorFromEditorPane, INotebookEditor, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; @@ -48,7 +48,7 @@ registerAction2(class extends Action2 { id: MenuId.EditorTitle, when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, - ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), + ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_KERNEL_SOURCE_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) ), group: 'navigation', @@ -56,7 +56,7 @@ registerAction2(class extends Action2 { }, { id: MenuId.NotebookToolbar, when: ContextKeyExpr.and( - ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), + ContextKeyExpr.or(NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), NOTEBOOK_KERNEL_SOURCE_COUNT.notEqualsTo(0), NOTEBOOK_MISSING_KERNEL_EXTENSION), ContextKeyExpr.equals('config.notebook.globalToolbar', true) ), group: 'status', diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index 1c601fbf36f..f7b01782aa0 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -10,7 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, cellRangeToViewCells } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { ICellRange, isICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; @@ -276,7 +276,7 @@ export abstract class NotebookCellAction extends abstract override runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; } -export const executeNotebookCondition = ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0); +export const executeNotebookCondition = ContextKeyExpr.or(ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0), ContextKeyExpr.greater(NOTEBOOK_KERNEL_SOURCE_COUNT.key, 0)); interface IMultiCellArgs { ranges: ICellRange[]; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 351b57c8bef..58227467a66 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -15,7 +15,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -41,6 +41,7 @@ export const executeCondition = ContextKeyExpr.and( NOTEBOOK_CELL_TYPE.isEqualTo('code'), ContextKeyExpr.or( ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0), + ContextKeyExpr.greater(NOTEBOOK_KERNEL_SOURCE_COUNT.key, 0), NOTEBOOK_MISSING_KERNEL_EXTENSION )); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index a90477db542..3702ac26261 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -6,7 +6,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -15,6 +15,7 @@ export class NotebookEditorContextKeys { private readonly _notebookKernel: IContextKey; private readonly _notebookKernelCount: IContextKey; + private readonly _notebookKernelSourceCount: IContextKey; private readonly _notebookKernelSelected: IContextKey; private readonly _interruptibleKernel: IContextKey; private readonly _someCellRunning: IContextKey; @@ -44,6 +45,7 @@ export class NotebookEditorContextKeys { this._hasOutputs = NOTEBOOK_HAS_OUTPUTS.bindTo(contextKeyService); this._viewType = NOTEBOOK_VIEW_TYPE.bindTo(contextKeyService); this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService); + this._notebookKernelSourceCount = NOTEBOOK_KERNEL_SOURCE_COUNT.bindTo(contextKeyService); this._cellToolbarLocation = NOTEBOOK_CELL_TOOLBAR_LOCATION.bindTo(contextKeyService); this._handleDidChangeModel(); @@ -52,6 +54,7 @@ export class NotebookEditorContextKeys { this._disposables.add(_editor.onDidChangeModel(this._handleDidChangeModel, this)); this._disposables.add(_notebookKernelService.onDidAddKernel(this._updateKernelContext, this)); this._disposables.add(_notebookKernelService.onDidChangeSelectedNotebooks(this._updateKernelContext, this)); + this._disposables.add(_notebookKernelService.onDidChangeSourceActions(this._updateKernelContext, this)); this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this)); this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this)); this._disposables.add(_notebookExecutionStateService.onDidChangeCellExecution(this._updateForCellExecution, this)); @@ -61,6 +64,7 @@ export class NotebookEditorContextKeys { this._disposables.dispose(); this._viewModelDisposables.dispose(); this._notebookKernelCount.reset(); + this._notebookKernelSourceCount.reset(); this._interruptibleKernel.reset(); this._someCellRunning.reset(); this._viewType.reset(); @@ -142,12 +146,15 @@ export class NotebookEditorContextKeys { private _updateKernelContext(): void { if (!this._editor.hasModel()) { this._notebookKernelCount.reset(); + this._notebookKernelSourceCount.reset(); this._interruptibleKernel.reset(); return; } const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.textModel); + const sourceActions = this._notebookKernelService.getSourceActions(); this._notebookKernelCount.set(all.length); + this._notebookKernelSourceCount.set(sourceActions.length); this._interruptibleKernel.set(selected?.implementsInterrupt ?? false); this._notebookKernelSelected.set(Boolean(selected)); this._notebookKernel.set(selected?.id ?? ''); diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index 689f9ca995e..882a76d6092 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -43,6 +43,7 @@ export const NOTEBOOK_CELL_RESOURCE = new RawContextKey('notebookCellRes // Kernels export const NOTEBOOK_KERNEL = new RawContextKey('notebookKernel', undefined); export const NOTEBOOK_KERNEL_COUNT = new RawContextKey('notebookKernelCount', 0); +export const NOTEBOOK_KERNEL_SOURCE_COUNT = new RawContextKey('notebookKernelSourceCount', 0); export const NOTEBOOK_KERNEL_SELECTED = new RawContextKey('notebookKernelSelected', false); export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey('notebookInterruptibleKernel', false); export const NOTEBOOK_MISSING_KERNEL_EXTENSION = new RawContextKey('notebookMissingKernelExtension', false); From 3837e55e31f1e44c514516873a4457c292607d58 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 25 May 2022 09:38:54 -0700 Subject: [PATCH 297/350] update bash status correctly (#150322) --- .../contrib/terminal/browser/media/shellIntegration-bash.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index ec96e7d3fd3..3c0cb9cfb87 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -96,6 +96,7 @@ if [[ -n "$__vsc_original_trap" ]]; then fi __vsc_preexec() { + __vsc_status="$?" eval ${__vsc_original_trap} PS1="$__vsc_prior_prompt" if [ -z "${__vsc_in_command_execution-}" ]; then @@ -110,7 +111,6 @@ __vsc_prompt_cmd_original() { if [[ ${IFS+set} ]]; then __vsc_original_ifs="$IFS" fi - __vsc_status="$?" if [[ "$__vsc_original_prompt_command" =~ .+\;.+ ]]; then IFS=';' else @@ -124,7 +124,7 @@ __vsc_prompt_cmd_original() { unset IFS fi for ((i = 0; i < ${#ADDR[@]}; i++)); do - # unset IFS + (exit ${__vsc_status}) builtin eval ${ADDR[i]} done __vsc_precmd From 3e8d674698b814e2d65bc206b9c19926655e4519 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 25 May 2022 11:09:59 -0700 Subject: [PATCH 298/350] Clicking outline in notebook should reveal cell near the top of the editor (#150336) Fixes #123828 --- .../contrib/outline/notebookOutline.ts | 8 ++++-- .../notebook/browser/notebookBrowser.ts | 7 +++++ .../notebook/browser/notebookEditorWidget.ts | 8 +++++- .../notebook/browser/view/notebookCellList.ts | 26 +++++++++++++++---- .../browser/view/notebookRenderingCommon.ts | 1 + 5 files changed, 42 insertions(+), 8 deletions(-) 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 473ff42494f..da58d351ae4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -8,7 +8,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, IDisposable, Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IActiveNotebookEditor, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellRevealType, IActiveNotebookEditor, ICellViewModel, INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; @@ -583,7 +583,11 @@ export class NotebookCellOutline extends Disposable implements IOutline { await this._editorService.openEditor({ resource: entry.cell.uri, - options: { ...options, override: this._editor.input?.editorId }, + options: { + ...options, + override: this._editor.input?.editorId, + cellRevealType: CellRevealType.NearTopIfOutsideViewport + } as INotebookEditorOptions, }, sideBySide ? SIDE_GROUP : undefined); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 75452af0bde..780ff8fc21a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -277,8 +277,15 @@ export interface INotebookDeltaCellStatusBarItems { items: INotebookCellStatusBarItem[]; } + +export enum CellRevealType { + NearTopIfOutsideViewport, + CenterIfOutsideViewport +} + export interface INotebookEditorOptions extends ITextEditorOptions { readonly cellOptions?: ITextResourceEditorInput; + readonly cellRevealType?: CellRevealType; readonly cellSelections?: ICellRange[]; readonly isReadOnly?: boolean; readonly viewState?: INotebookEditorViewState; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 2f1bf44324c..51c50c5d6a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -49,7 +49,7 @@ import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeg import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, 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, 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 { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { notebookDebug } from 'vs/workbench/contrib/notebook/browser/notebookLogger'; @@ -1244,6 +1244,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const selection = cellOptions.options?.selection; if (selection) { await this.revealLineInCenterIfOutsideViewportAsync(cell, selection.startLineNumber); + } else if (options?.cellRevealType === CellRevealType.NearTopIfOutsideViewport) { + await this.revealNearTopIfOutsideViewportAync(cell); } else { await this.revealInCenterIfOutsideViewportAsync(cell); } @@ -2028,6 +2030,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._listViewInfoAccessor.revealInCenter(cell); } + revealNearTopIfOutsideViewportAync(cell: ICellViewModel) { + return this._listViewInfoAccessor.revealNearTopIfOutsideViewportAync(cell); + } + async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { return this._listViewInfoAccessor.revealLineInViewAsync(cell, line); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 0318aa6a288..c68b97e7df5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -39,7 +39,8 @@ const enum CellRevealType { const enum CellRevealPosition { Top, Center, - Bottom + Bottom, + NearTop } function getVisibleCells(cells: CellViewModel[], hiddenRanges: ICellRange[]) { @@ -861,7 +862,15 @@ export class NotebookCellList extends WorkbenchList implements ID const index = this._getViewIndexUpperBound(cell); if (index >= 0) { - return this._revealInCenterIfOutsideViewportAsync(index); + return this._revealIfOutsideViewportAsync(index, CellRevealPosition.Center); + } + } + + async revealNearTopIfOutsideViewportAync(cell: ICellViewModel): Promise { + const index = this._getViewIndexUpperBound(cell); + + if (index >= 0) { + return this._revealIfOutsideViewportAsync(index, CellRevealPosition.NearTop); } } @@ -1200,8 +1209,8 @@ export class NotebookCellList extends WorkbenchList implements ID } } - private async _revealInCenterIfOutsideViewportAsync(viewIndex: number): Promise { - this._revealInternal(viewIndex, true, CellRevealPosition.Center); + private async _revealIfOutsideViewportAsync(viewIndex: number, revealPosition: CellRevealPosition): Promise { + this._revealInternal(viewIndex, true, revealPosition); const element = this.view.element(viewIndex); // wait for the editor to be created only if the cell is in editing mode (meaning it has an editor and will focus the editor) @@ -1249,6 +1258,7 @@ export class NotebookCellList extends WorkbenchList implements ID this.view.setScrollTop(this.view.elementTop(viewIndex)); break; case CellRevealPosition.Center: + case CellRevealPosition.NearTop: { // reveal the cell top in the viewport center initially this.view.setScrollTop(elementTop - this.view.renderHeight / 2); @@ -1259,8 +1269,10 @@ export class NotebookCellList extends WorkbenchList implements ID if (newElementHeight >= renderHeight) { // cell is larger than viewport, reveal top this.view.setScrollTop(newElementTop); - } else { + } else if (revealPosition === CellRevealPosition.Center) { this.view.setScrollTop(newElementTop + (newElementHeight / 2) - (renderHeight / 2)); + } else if (revealPosition === CellRevealPosition.NearTop) { + this.view.setScrollTop(newElementTop - (renderHeight / 5)); } } break; @@ -1497,6 +1509,10 @@ export class ListViewInfoAccessor extends Disposable { this.list.revealElementInCenter(cell); } + async revealNearTopIfOutsideViewportAync(cell: ICellViewModel) { + return this.list.revealNearTopIfOutsideViewportAync(cell); + } + async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { return this.list.revealElementLineInViewAsync(cell, line); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 0bdb4ff330f..ceabb8d6de9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -66,6 +66,7 @@ export interface INotebookCellList { revealElementInCenterIfOutsideViewport(element: ICellViewModel): void; revealElementInCenter(element: ICellViewModel): void; revealElementInCenterIfOutsideViewportAsync(element: ICellViewModel): Promise; + revealNearTopIfOutsideViewportAync(element: ICellViewModel): Promise; revealElementLineInViewAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterIfOutsideViewportAsync(element: ICellViewModel, line: number): Promise; From 8eeed3dd6c140da47b326d99e5a0579465c4f62e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 25 May 2022 11:27:58 -0700 Subject: [PATCH 299/350] Fix bad layout of search input (#150405) Fix #150381 --- src/vs/workbench/contrib/search/browser/media/searchview.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index f9e5c40fa0d..f59811de9a2 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -47,8 +47,7 @@ .search-view .search-widget .monaco-inputbox > .ibwrapper > .mirror, .search-view .search-widget .monaco-inputbox > .ibwrapper > textarea.input { - padding: 3px; - padding-left: 4px; + padding: 3px 0px 3px 4px; } /* NOTE: height is also used in searchWidget.ts as a constant*/ From 149e2b7674ba0888cbb7cf8bbd70f45db7393fa5 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 25 May 2022 20:51:08 +0200 Subject: [PATCH 300/350] Fix #149831 (#150276) --- extensions/git/src/repository.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e6bd193aa0c..c6fa51b5497 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1408,15 +1408,15 @@ export class Repository implements Disposable { } @throttle - async fetchAll(): Promise { - await this._fetch({ all: true }); + async fetchAll(cancellationToken?: CancellationToken): Promise { + await this._fetch({ all: true, cancellationToken }); } async fetch(options: FetchOptions): Promise { await this._fetch(options); } - private async _fetch(options: { remote?: string; ref?: string; all?: boolean; prune?: boolean; depth?: number; silent?: boolean } = {}): Promise { + private async _fetch(options: { remote?: string; ref?: string; all?: boolean; prune?: boolean; depth?: number; silent?: boolean; cancellationToken?: CancellationToken } = {}): Promise { if (!options.prune) { const config = workspace.getConfiguration('git', Uri.file(this.root)); const prune = config.get('pruneOnFetch'); @@ -1461,7 +1461,7 @@ export class Repository implements Disposable { // When fetchOnPull is enabled, fetch all branches when pulling if (fetchOnPull) { - await this.repository.fetch({ all: true }); + await this.fetchAll(); } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { @@ -1532,7 +1532,7 @@ export class Repository implements Disposable { const fn = async (cancellationToken?: CancellationToken) => { // When fetchOnPull is enabled, fetch all branches when pulling if (fetchOnPull) { - await this.repository.fetch({ all: true, cancellationToken }); + await this.fetchAll(cancellationToken); } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { From b118105bf28d773fbbce683f7230d058be2f89a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 25 May 2022 12:05:19 -0700 Subject: [PATCH 301/350] Prevent work for really long links - We no longer get all xterm.js content for a full wrapped line but limit it to a certain context, this context depends on the type of link detector. - There is also a sanity check when calling the low level helper function just in case. Fixes #150401 --- .../contrib/terminal/browser/links/links.ts | 6 ++++++ .../browser/links/terminalExternalLinkDetector.ts | 11 +++-------- .../browser/links/terminalLinkDetectorAdapter.ts | 11 +++++++++-- .../terminal/browser/links/terminalLinkHelpers.ts | 4 ++++ .../browser/links/terminalLocalLinkDetector.ts | 6 ++++++ .../browser/links/terminalUriLinkDetector.ts | 13 +++++-------- .../browser/links/terminalWordLinkDetector.ts | 4 ++++ .../browser/links/terminalUriLinkDetector.test.ts | 14 +++++++------- 8 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/links.ts b/src/vs/workbench/contrib/terminal/browser/links/links.ts index 012b95f3214..a93a42c4b49 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/links.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/links.ts @@ -17,6 +17,12 @@ export interface ITerminalLinkDetector { */ readonly xterm: Terminal; + /** + * The maximum link length possible for this detector, this puts a cap on how much of a wrapped + * line to consider to prevent performance problems. + */ + readonly maxLinkLength: number; + /** * Detects links within the _wrapped_ line range provided and returns them as an array. * diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkDetector.ts index 9a633e8956a..521a7b2ee44 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkDetector.ts @@ -8,14 +8,9 @@ import { convertLinkRangeToBuffer, getXtermLineContent } from 'vs/workbench/cont import { ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IBufferLine, Terminal } from 'xterm'; -const enum Constants { - /** - * The max line length to try extract word links from. - */ - MaxLineLength = 2000 -} - export class TerminalExternalLinkDetector implements ITerminalLinkDetector { + readonly maxLinkLength = 2000; + constructor( readonly id: string, readonly xterm: Terminal, @@ -26,7 +21,7 @@ export class TerminalExternalLinkDetector implements ITerminalLinkDetector { async detect(lines: IBufferLine[], startLine: number, endLine: number): Promise { // Get the text representation of the wrapped line const text = getXtermLineContent(this.xterm.buffer.active, startLine, endLine, this.xterm.cols); - if (text === '' || text.length > Constants.MaxLineLength) { + if (text === '' || text.length > this.maxLinkLength) { return []; } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkDetectorAdapter.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkDetectorAdapter.ts index 868bf9600e8..43d8923d291 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkDetectorAdapter.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkDetectorAdapter.ts @@ -59,12 +59,19 @@ export class TerminalLinkDetectorAdapter extends Disposable implements ILinkProv this._detector.xterm.buffer.active.getLine(startLine)! ]; - while (startLine >= 0 && this._detector.xterm.buffer.active.getLine(startLine)?.isWrapped) { + // Cap the maximum context on either side of the line being provided, by taking the context + // around the line being provided for this ensures the line the pointer is on will have + // links provided. + const maxLineContext = Math.max(this._detector.maxLinkLength / this._detector.xterm.cols); + const minStartLine = Math.max(startLine - maxLineContext, 0); + const maxEndLine = Math.min(endLine + maxLineContext, this._detector.xterm.buffer.active.length); + + while (startLine >= minStartLine && this._detector.xterm.buffer.active.getLine(startLine)?.isWrapped) { lines.unshift(this._detector.xterm.buffer.active.getLine(startLine - 1)!); startLine--; } - while (endLine < this._detector.xterm.buffer.active.length && this._detector.xterm.buffer.active.getLine(endLine + 1)?.isWrapped) { + while (endLine < maxEndLine && this._detector.xterm.buffer.active.getLine(endLine + 1)?.isWrapped) { lines.push(this._detector.xterm.buffer.active.getLine(endLine + 1)!); endLine++; } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers.ts index 202ba173cc0..bb6042ce80a 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkHelpers.ts @@ -122,6 +122,10 @@ export function convertBufferRangeToViewport(bufferRange: IBufferRange, viewport } export function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number, cols: number): string { + // Cap the maximum number of lines generated to prevent potential performance problems. This is + // more of a sanity check as the wrapped line should already be trimmed down at this point. + const maxLineLength = Math.max(2048 / cols * 2); + lineEnd = Math.min(lineEnd, lineStart + maxLineLength); let content = ''; for (let i = lineStart; i <= lineEnd; i++) { // Make sure only 0 to cols are considered as resizing when windows mode is enabled will diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index 936af397072..48e5c0659bc 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -67,6 +67,12 @@ export const lineAndColumnClauseGroupCount = 6; export class TerminalLocalLinkDetector implements ITerminalLinkDetector { static id = 'local'; + // This was chosen as a reasonable maximum line length given the tradeoff between performance + // and how likely it is to encounter such a large line length. Some useful reference points: + // - Window old max length: 260 ($MAX_PATH) + // - Linux max length: 4096 ($PATH_MAX) + readonly maxLinkLength = 500; + constructor( readonly xterm: Terminal, private readonly _capabilities: ITerminalCapabilityStore, diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts index 948b257d8ab..446a7305405 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts @@ -17,18 +17,15 @@ const enum Constants { * The maximum number of links in a line to resolve against the file system. This limit is put * in place to avoid sending excessive data when remote connections are in place. */ - MaxResolvedLinksInLine = 10, - - /** - * The maximum length of a link to resolve against the file system. This limit is put in place - * to avoid sending excessive data when remote connections are in place. - */ - MaxResolvedLinkLength = 1024, + MaxResolvedLinksInLine = 10 } export class TerminalUriLinkDetector implements ITerminalLinkDetector { static id = 'uri'; + // 2048 is the maximum URL length + readonly maxLinkLength = 2048; + constructor( readonly xterm: Terminal, private readonly _resolvePath: (link: string, uri?: URI) => Promise, @@ -70,7 +67,7 @@ export class TerminalUriLinkDetector implements ITerminalLinkDetector { } // Don't try resolve any links of excessive length - if (text.length > Constants.MaxResolvedLinkLength) { + if (text.length > this.maxLinkLength) { continue; } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkDetector.ts index 6bfa63c5038..637ff8dad9c 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkDetector.ts @@ -25,6 +25,10 @@ interface Word { export class TerminalWordLinkDetector implements ITerminalLinkDetector { static id = 'word'; + // Word links typically search the workspace so it makes sense that their maximum link length is + // quite small. + readonly maxLinkLength = 100; + constructor( readonly xterm: Terminal, @IConfigurationService private readonly _configurationService: IConfigurationService, diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts index 448f145fda2..bc867788d4b 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts @@ -91,13 +91,13 @@ suite('Workbench - TerminalUriLinkDetector', () => { text: `https://${'foobarbaz/'.repeat(102)}` }]); }); - test('should filter out file:// links that exceed 1024 characters', async () => { - // 8 + 101 * 10 = 1018 characters - await assertLink(TerminalBuiltinLinkType.LocalFile, `file:///${'foobarbaz/'.repeat(101)}`, [{ - text: `file:///${'foobarbaz/'.repeat(101)}`, - range: [[1, 1], [58, 13]] + test('should filter out file:// links that exceed 4096 characters', async () => { + // 8 + 200 * 10 = 2008 characters + await assertLink(TerminalBuiltinLinkType.LocalFile, `file:///${'foobarbaz/'.repeat(200)}`, [{ + text: `file:///${'foobarbaz/'.repeat(200)}`, + range: [[1, 1], [8, 26]] }]); - // 8 + 102 * 10 = 1028 characters - await assertLink(TerminalBuiltinLinkType.LocalFile, `file:///${'foobarbaz/'.repeat(102)}`, []); + // 8 + 450 * 10 = 4508 characters + await assertLink(TerminalBuiltinLinkType.LocalFile, `file:///${'foobarbaz/'.repeat(450)}`, []); }); }); From 9d18042706a81afd0a92e86a8cbbdd0abb74c922 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 25 May 2022 12:06:51 -0700 Subject: [PATCH 302/350] fix #150390 (#150395) fix #150392 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 5c841da1021..b7edef134d5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1369,6 +1369,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Send it to the process await this._processManager.write(text); this._onDidInputData.fire(this); + this.xterm?.scrollToBottom(); } async sendPath(originalPath: string, addNewLine: boolean): Promise { From 4d166fc71a5c8f5f1c33aa2a10609f5ec87754d6 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 25 May 2022 12:07:16 -0700 Subject: [PATCH 303/350] don't add telemetry for feature terminals etc (#150224) --- .../common/xterm/shellIntegrationAddon.ts | 24 +++++++++++-------- src/vs/platform/terminal/node/ptyService.ts | 2 +- .../terminal/browser/terminalInstance.ts | 3 ++- .../terminal/browser/xterm/xtermTerminal.ts | 3 ++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 2e456109e9e..8119de5aa52 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -128,6 +128,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati private _activationTimeout: any; constructor( + private readonly _disableTelemetry: boolean | undefined, private readonly _telemetryService: ITelemetryService | undefined, @ILogService private readonly _logService: ILogService ) { @@ -154,6 +155,19 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return didHandle; } + private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { + if (!this._telemetryService || this._disableTelemetry) { + return; + } + this._activationTimeout = setTimeout(() => { + if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { + this._telemetryService?.publicLog2<{ classification: 'SystemMetaData'; purpose: 'FeatureInsight' }>('terminal/shellIntegrationActivationTimeout'); + this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); + } + this._hasUpdatedTelemetry = true; + }, 10000); + } + private _doHandleVSCodeSequence(data: string): boolean { if (!this._terminal) { return false; @@ -229,16 +243,6 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return false; } - private async _ensureCapabilitiesOrAddFailureTelemetry(): Promise { - this._activationTimeout = setTimeout(() => { - if (!this.capabilities.get(TerminalCapability.CommandDetection) && !this.capabilities.get(TerminalCapability.CwdDetection)) { - this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration activation did not occur within 10 seconds' }>('terminal/shellIntegrationActivationTimeout'); - this._logService.warn('Shell integration failed to add capabilities within 10 seconds'); - } - this._hasUpdatedTelemetry = true; - }, 10000); - } - serialize(): ISerializedCommandDetectionCapability { if (!this._terminal || !this.capabilities.has(TerminalCapability.CommandDetection)) { return { diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index d9f8ae2b092..397fee9a493 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -742,7 +742,7 @@ class XtermSerializer implements ITerminalSerializer { this._xterm.writeln(reviveBuffer); } this.setUnicodeVersion(unicodeVersion); - this._shellIntegrationAddon = new ShellIntegrationAddon(undefined, logService); + this._shellIntegrationAddon = new ShellIntegrationAddon(true, undefined, logService); this._xterm.loadAddon(this._shellIntegrationAddon); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b7edef134d5..82088ea5347 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -657,7 +657,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { throw new ErrorNoTelemetry('Terminal disposed of during xterm.js creation'); } - const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel, this.capabilities); + const disableShellIntegrationTelemetry = this.shellLaunchConfig.isFeatureTerminal || this.shellLaunchConfig.hideFromUser || this.shellLaunchConfig.executable === undefined; + const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows, this.target || TerminalLocation.Panel, this.capabilities, disableShellIntegrationTelemetry); this.xterm = xterm; const lineDataEventAddon = new LineDataEventAddon(); this.xterm.raw.loadAddon(lineDataEventAddon); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 3285ae2c363..56fc30dfdaa 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -99,6 +99,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { rows: number, location: TerminalLocation, private readonly _capabilities: ITerminalCapabilityStore, + disableShellIntegrationTelemetry: boolean, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @@ -175,7 +176,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._updateUnicodeVersion(); this._commandNavigationAddon = this._instantiationService.createInstance(CommandNavigationAddon, _capabilities); this.raw.loadAddon(this._commandNavigationAddon); - this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, this._telemetryService); + this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, disableShellIntegrationTelemetry, this._telemetryService); this.raw.loadAddon(this._shellIntegrationAddon); this._updateShellIntegrationAddons(); } From 280f1be7ba9a60d32d0a1949b38d212b99870906 Mon Sep 17 00:00:00 2001 From: Ian Huff Date: Wed, 25 May 2022 12:19:28 -0700 Subject: [PATCH 304/350] quick draft PR for not throwing when looking for a chunked password (#150402) * quick draft PR for not throwing when looking for a chunked password * update comment with more info Co-authored-by: Ian Huff --- .../common/credentialsMainService.ts | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/credentials/common/credentialsMainService.ts b/src/vs/platform/credentials/common/credentialsMainService.ts index 12a591de763..c9615fe27d8 100644 --- a/src/vs/platform/credentials/common/credentialsMainService.ts +++ b/src/vs/platform/credentials/common/credentialsMainService.ts @@ -150,19 +150,28 @@ export abstract class BaseCredentialsMainService extends Disposable implements I return false; } const didDelete = await keytar.deletePassword(service, account); - let { content, hasNextChunk }: ChunkedPassword = JSON.parse(password); - if (content && hasNextChunk) { - // need to delete additional chunks - let index = 1; - while (hasNextChunk) { - const accountWithIndex = `${account}-${index}`; - const nextChunk = await keytar.getPassword(service, accountWithIndex); - await keytar.deletePassword(service, accountWithIndex); + try { + let { content, hasNextChunk }: ChunkedPassword = JSON.parse(password); + if (content && hasNextChunk) { + // need to delete additional chunks + let index = 1; + while (hasNextChunk) { + const accountWithIndex = `${account}-${index}`; + const nextChunk = await keytar.getPassword(service, accountWithIndex); + await keytar.deletePassword(service, accountWithIndex); - const result: ChunkedPassword = JSON.parse(nextChunk!); - hasNextChunk = result.hasNextChunk; - index++; + const result: ChunkedPassword = JSON.parse(nextChunk!); + hasNextChunk = result.hasNextChunk; + index++; + } } + } catch { + // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON + // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83 + // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here + // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128 + // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse + // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76 } if (didDelete) { From 5f7983c8a56149771132cb1a6c60ba5e374d93af Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 25 May 2022 12:30:19 -0700 Subject: [PATCH 305/350] Fix tests to work with new link filtering --- .../browser/links/terminalUriLinkDetector.ts | 10 +++++----- .../links/terminalUriLinkDetector.test.ts | 17 +++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts index 446a7305405..921164f5de3 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalUriLinkDetector.ts @@ -55,6 +55,11 @@ export class TerminalUriLinkDetector implements ITerminalLinkDetector { const text = computedLink.url?.toString() || ''; + // Don't try resolve any links of excessive length + if (text.length > this.maxLinkLength) { + continue; + } + // Handle non-file scheme links if (uri.scheme !== Schemas.file) { links.push({ @@ -66,11 +71,6 @@ export class TerminalUriLinkDetector implements ITerminalLinkDetector { continue; } - // Don't try resolve any links of excessive length - if (text.length > this.maxLinkLength) { - continue; - } - // Filter out URI with unrecognized authorities if (uri.authority.length !== 2 && uri.authority.endsWith(':')) { continue; diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts index bc867788d4b..2b6becbb4c5 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalUriLinkDetector.test.ts @@ -79,17 +79,14 @@ suite('Workbench - TerminalUriLinkDetector', () => { { range: [[16, 1], [29, 1]], text: 'http://bar.foo' } ]); }); - test('should not filtrer out https:// link that exceed 1024 characters', async () => { - // 8 + 101 * 10 = 1018 characters - await assertLink(TerminalBuiltinLinkType.Url, `https://${'foobarbaz/'.repeat(101)}`, [{ - range: [[1, 1], [58, 13]], - text: `https://${'foobarbaz/'.repeat(101)}` - }]); - // 8 + 102 * 10 = 1028 characters - await assertLink(TerminalBuiltinLinkType.Url, `https://${'foobarbaz/'.repeat(102)}`, [{ - range: [[1, 1], [68, 13]], - text: `https://${'foobarbaz/'.repeat(102)}` + test('should filter out https:// link that exceed 4096 characters', async () => { + // 8 + 200 * 10 = 2008 characters + await assertLink(TerminalBuiltinLinkType.Url, `https://${'foobarbaz/'.repeat(200)}`, [{ + range: [[1, 1], [8, 26]], + text: `https://${'foobarbaz/'.repeat(200)}` }]); + // 8 + 450 * 10 = 4508 characters + await assertLink(TerminalBuiltinLinkType.Url, `https://${'foobarbaz/'.repeat(450)}`, []); }); test('should filter out file:// links that exceed 4096 characters', async () => { // 8 + 200 * 10 = 2008 characters From 9c1926d72676a90733232fee94f949acb466aa33 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 25 May 2022 12:51:23 -0700 Subject: [PATCH 306/350] xterm@4.19.0-beta.52 Fixes #148061 --- package.json | 4 ++-- remote/package.json | 4 ++-- remote/web/package.json | 2 +- remote/web/yarn.lock | 8 ++++---- remote/yarn.lock | 16 ++++++++-------- yarn.lock | 16 ++++++++-------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 3768321d2bb..2728502f1fb 100644 --- a/package.json +++ b/package.json @@ -85,12 +85,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.49", + "xterm": "4.19.0-beta.52", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.49", + "xterm-headless": "4.19.0-beta.52", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 3e833d3c5f6..fe99f034f83 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.49", + "xterm": "4.19.0-beta.52", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.49", + "xterm-headless": "4.19.0-beta.52", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 4ec42b01f12..470499091e3 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,7 +10,7 @@ "tas-client-umd": "0.1.5", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.49", + "xterm": "4.19.0-beta.52", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index d8eac016d98..0b6e2d9535a 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -128,7 +128,7 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm@4.19.0-beta.49: - version "4.19.0-beta.49" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" - integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== +xterm@4.19.0-beta.52: + version "4.19.0-beta.52" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.52.tgz#21c7ad0bd905f3fddaa0583951514c8271dcd822" + integrity sha512-2itY6dyiimOEkgVwc31TngYQ6npHt7or1wxU0Flbv4PjH4ke8K26NDB7KAi0YYkn2g0/qzIjQc7ykdrrTaEgMg== diff --git a/remote/yarn.lock b/remote/yarn.lock index 52a1cc813ed..8f228e501bf 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -934,15 +934,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.49: - version "4.19.0-beta.49" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" - integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== +xterm-headless@4.19.0-beta.52: + version "4.19.0-beta.52" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.52.tgz#87298cb205694cfdd701a9a96dcd06434a9773fe" + integrity sha512-9s8VtGa9JUdEWxrNDJpEng2mEFn0KjWM3R9nxezA8mXHsJ/0cMqoK00s/fzhAEXZqHycWASSECMWgFOaT0MrnQ== -xterm@4.19.0-beta.49: - version "4.19.0-beta.49" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" - integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== +xterm@4.19.0-beta.52: + version "4.19.0-beta.52" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.52.tgz#21c7ad0bd905f3fddaa0583951514c8271dcd822" + integrity sha512-2itY6dyiimOEkgVwc31TngYQ6npHt7or1wxU0Flbv4PjH4ke8K26NDB7KAi0YYkn2g0/qzIjQc7ykdrrTaEgMg== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index e483a32e500..5f155fdde19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12218,15 +12218,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.49: - version "4.19.0-beta.49" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" - integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== +xterm-headless@4.19.0-beta.52: + version "4.19.0-beta.52" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.52.tgz#87298cb205694cfdd701a9a96dcd06434a9773fe" + integrity sha512-9s8VtGa9JUdEWxrNDJpEng2mEFn0KjWM3R9nxezA8mXHsJ/0cMqoK00s/fzhAEXZqHycWASSECMWgFOaT0MrnQ== -xterm@4.19.0-beta.49: - version "4.19.0-beta.49" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" - integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== +xterm@4.19.0-beta.52: + version "4.19.0-beta.52" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.52.tgz#21c7ad0bd905f3fddaa0583951514c8271dcd822" + integrity sha512-2itY6dyiimOEkgVwc31TngYQ6npHt7or1wxU0Flbv4PjH4ke8K26NDB7KAi0YYkn2g0/qzIjQc7ykdrrTaEgMg== y18n@^3.2.1: version "3.2.2" From 22806eaa8e961ca5dff67410e8bbe7ff7cc5e77b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 25 May 2022 12:54:31 -0700 Subject: [PATCH 307/350] re-enable terminal tabs test (#148966) --- test/smoke/src/areas/terminal/terminal-tabs.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/smoke/src/areas/terminal/terminal-tabs.test.ts b/test/smoke/src/areas/terminal/terminal-tabs.test.ts index 1f962540d75..cc4573978e8 100644 --- a/test/smoke/src/areas/terminal/terminal-tabs.test.ts +++ b/test/smoke/src/areas/terminal/terminal-tabs.test.ts @@ -67,7 +67,7 @@ export function setup() { await terminal.assertSingleTab({ name }); }); - it.skip('should reset the tab name to the default value when no name is provided', async () => { // https://github.com/microsoft/vscode/issues/146796 + it('should reset the tab name to the default value when no name is provided', async () => { await terminal.createTerminal(); const defaultName = await terminal.getSingleTabName(); const name = 'my terminal name'; From 3ed15398de076e1ef26cce6cc3532a1c21183078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 25 May 2022 22:22:30 +0200 Subject: [PATCH 308/350] update endgame notebook (#150407) --- .vscode/notebooks/endgame.github-issues | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 90cc4971f43..48195c79000 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"April 2022\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\r\n\r\n$MILESTONE=milestone:\"May 2022\"" }, { "kind": 1, From e7833fe75c5ca9e10e89c67dc201362da623cda0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 25 May 2022 13:38:11 -0700 Subject: [PATCH 309/350] show terminal find history placeholder on focus (#150409) --- .../contrib/codeEditor/browser/find/simpleFindWidget.css | 4 +++- .../contrib/codeEditor/browser/find/simpleFindWidget.ts | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css index 46b11d7eedf..b280e584779 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css @@ -9,7 +9,7 @@ position: absolute; top: 0; right: 18px; - width: 220px; + width: 360px; max-width: calc(100% - 28px - 28px - 8px); pointer-events: none; padding: 0 10px 10px; @@ -29,6 +29,7 @@ align-items: center; pointer-events: all; transition: top 200ms linear; + min-width: 360px; } .monaco-workbench.reduce-motion .monaco-editor .find-widget { @@ -45,6 +46,7 @@ .monaco-workbench .simple-find-part .monaco-findInput { flex: 1; + min-width: 220px; } .monaco-workbench .simple-find-part .button { diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 5fa3bb659ab..d31078de067 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -22,6 +22,7 @@ import { widgetClose } from 'vs/platform/theme/common/iconRegistry'; import * as strings from 'vs/base/common/strings'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { showHistoryKeybindingHint } from 'vs/platform/history/browser/historyWidgetKeybindingHint'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find (\u21C5 for history)"); @@ -80,9 +81,9 @@ export abstract class SimpleFindWidget extends Widget { }, appendCaseSensitiveLabel: options.appendCaseSensitiveLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindCaseSensitive) : undefined, appendRegexLabel: options.appendRegexLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindRegex) : undefined, - appendWholeWordsLabel: options.appendWholeWordsLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindWholeWord) : undefined + appendWholeWordsLabel: options.appendWholeWordsLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindWholeWord) : undefined, + showHistoryHint: () => showHistoryKeybindingHint(_keybindingService) }, contextKeyService, options.showOptionButtons)); - // Find History with update delayer this._updateHistoryDelayer = new Delayer(500); From 6844f0b7d15e881ded03e93b828d85a4aa7417e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 25 May 2022 16:01:25 -0700 Subject: [PATCH 310/350] Clear activation timeout if terminal is disposed Fixes #150419 --- .../common/xterm/shellIntegrationAddon.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 8119de5aa52..3e48db34a63 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IShellIntegration } from 'vs/platform/terminal/common/terminal'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; import { CwdDetectionCapability } from 'vs/platform/terminal/common/capabilities/cwdDetectionCapability'; @@ -133,6 +133,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati @ILogService private readonly _logService: ILogService ) { super(); + this._register(toDisposable(() => this._clearActivationTimeout())); } activate(xterm: Terminal) { @@ -147,10 +148,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati if (!this._hasUpdatedTelemetry && didHandle) { this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration was activated' }>('terminal/shellIntegrationActivationSucceeded'); this._hasUpdatedTelemetry = true; - if (this._activationTimeout !== undefined) { - clearTimeout(this._activationTimeout); - this._activationTimeout = undefined; - } + this._clearActivationTimeout(); } return didHandle; } @@ -168,6 +166,13 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati }, 10000); } + private _clearActivationTimeout(): void { + if (this._activationTimeout !== undefined) { + clearTimeout(this._activationTimeout); + this._activationTimeout = undefined; + } + } + private _doHandleVSCodeSequence(data: string): boolean { if (!this._terminal) { return false; From 12cb9932382bcde504d05477c5049f22e5f07d26 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 25 May 2022 17:30:39 -0700 Subject: [PATCH 311/350] Create cell execution beforehand (#150410) --- .../api/browser/mainThreadNotebookKernels.ts | 13 ++++++- .../browser/notebookExecutionServiceImpl.ts | 37 ++++++++++++------- .../notebookExecutionStateServiceImpl.ts | 9 +---- .../common/notebookExecutionStateService.ts | 2 +- .../notebookExecutionStateService.test.ts | 8 ++-- .../test/browser/testNotebookEditor.ts | 2 +- 6 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts index 6b734982787..c02518f694c 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts @@ -18,6 +18,7 @@ import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workb import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; abstract class MainThreadKernel implements INotebookKernel { private readonly _onDidChange = new Emitter(); @@ -112,6 +113,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape @ILanguageService private readonly _languageService: ILanguageService, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorService notebookEditorService: INotebookEditorService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookKernels); @@ -246,7 +248,16 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape $createExecution(handle: number, controllerId: string, rawUri: UriComponents, cellHandle: number): void { const uri = URI.revive(rawUri); - const execution = this._notebookExecutionStateService.createCellExecution(controllerId, uri, cellHandle); + const notebook = this._notebookService.getNotebookTextModel(uri); + if (!notebook) { + throw new Error(`Notebook not found: ${uri.toString()}`); + } + + const kernel = this._notebookKernelService.getMatchingKernel(notebook); + if (!kernel.selected || kernel.selected.id !== controllerId) { + throw new Error(`Kernel is not selected: ${kernel.selected?.id} !== ${controllerId}`); + } + const execution = this._notebookExecutionStateService.createCellExecution(uri, cellHandle); execution.confirm(); this._executions.set(handle, execution); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 0be1459d36a..de0c18f7638 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -13,7 +13,7 @@ import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controll import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class NotebookExecutionService implements INotebookExecutionService, IDisposable { @@ -38,6 +38,16 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis return; } + // create cell executions + const cellExecutions: [NotebookCellTextModel, INotebookCellExecution][] = []; + for (const cell of cellsArr) { + const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri); + if (cell.cellKind !== CellKind.Code || !!cellExe) { + continue; + } + cellExecutions.push([cell, this._notebookExecutionStateService.createCellExecution(notebook.uri, cell.handle)]); + } + let kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (!kernel) { kernel = await this.resolveSourceActions(notebook); @@ -49,28 +59,27 @@ export class NotebookExecutionService implements INotebookExecutionService, IDis } if (!kernel) { + // clear all pending cell executions + cellExecutions.forEach(cellExe => cellExe[1].complete({})); return; } - const executeCells: NotebookCellTextModel[] = []; - for (const cell of cellsArr) { - const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri); - if (cell.cellKind !== CellKind.Code || !!cellExe) { - continue; - } + // filter cell executions based on selected kernel + const validCellExecutions: INotebookCellExecution[] = []; + for (const [cell, cellExecution] of cellExecutions) { if (!kernel.supportedLanguages.includes(cell.language)) { - continue; + cellExecution.complete({}); + } else { + validCellExecutions.push(cellExecution); } - executeCells.push(cell); } - if (executeCells.length > 0) { + // request execution + if (validCellExecutions.length > 0) { this._notebookKernelService.selectKernelForNotebook(kernel, notebook); - - const exes = executeCells.map(c => this._notebookExecutionStateService.createCellExecution(kernel!.id, notebook.uri, c.handle)); - await kernel.executeNotebookCellsRequest(notebook.uri, executeCells.map(c => c.handle)); + await kernel.executeNotebookCellsRequest(notebook.uri, validCellExecutions.map(c => c.cellHandle)); // the connecting state can change before the kernel resolves executeNotebookCellsRequest - const unconfirmed = exes.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed); + const unconfirmed = validCellExecutions.filter(exe => exe.state === NotebookCellExecutionState.Unconfirmed); if (unconfirmed.length) { this._logService.debug(`NotebookExecutionService#executeNotebookCells completing unconfirmed executions ${JSON.stringify(unconfirmed.map(exe => exe.cellHandle))}`); unconfirmed.forEach(exe => exe.complete({})); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts index be0522ab323..3b487ec7684 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts @@ -14,7 +14,6 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellEditType, CellUri, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService { @@ -28,7 +27,6 @@ export class NotebookExecutionStateService extends Disposable implements INotebo onDidChangeCellExecution = this._onDidChangeCellExecution.event; constructor( - @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @INotebookService private readonly _notebookService: INotebookService, @@ -91,17 +89,12 @@ export class NotebookExecutionStateService extends Disposable implements INotebo this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle)); } - createCellExecution(controllerId: string, notebookUri: URI, cellHandle: number): INotebookCellExecution { + createCellExecution(notebookUri: URI, cellHandle: number): INotebookCellExecution { const notebook = this._notebookService.getNotebookTextModel(notebookUri); if (!notebook) { throw new Error(`Notebook not found: ${notebookUri.toString()}`); } - const kernel = this._notebookKernelService.getMatchingKernel(notebook); - if (!kernel.selected || kernel.selected.id !== controllerId) { - throw new Error(`Kernel is not selected: ${kernel.selected?.id} !== ${controllerId}`); - } - let notebookExecutionMap = this._executions.get(notebookUri); if (!notebookExecutionMap) { const listeners = this._instantiationService.createInstance(NotebookExecutionListeners, notebookUri); diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts index 0a9902a5ff4..f6317245bb1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts @@ -50,7 +50,7 @@ export interface INotebookExecutionStateService { forceCancelNotebookExecutions(notebookUri: URI): void; getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[]; getCellExecution(cellUri: URI): INotebookCellExecution | undefined; - createCellExecution(controllerId: string, notebook: URI, cellHandle: number): INotebookCellExecution; + createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution; } export interface INotebookCellExecution { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 0b05037e51e..e925ef9e011 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -94,7 +94,7 @@ suite('NotebookExecutionStateService', () => { const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); - executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle); + executionStateService.createCellExecution(viewModel.uri, cell.handle); assert.strictEqual(didCancel, false); viewModel.notebookDocument.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 1, cells: [] @@ -113,7 +113,7 @@ suite('NotebookExecutionStateService', () => { const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); - const exe = executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle); + const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle); let didFire = false; disposables.add(executionStateService.onDidChangeCellExecution(e => { @@ -154,7 +154,7 @@ suite('NotebookExecutionStateService', () => { deferred.complete(); })); - executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle); + executionStateService.createCellExecution(viewModel.uri, cell.handle); return deferred.p; }); @@ -170,7 +170,7 @@ suite('NotebookExecutionStateService', () => { const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService); const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); - executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle); + executionStateService.createCellExecution(viewModel.uri, cell.handle); const exe = executionStateService.getCellExecution(cell.uri); assert.ok(exe); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 514c5231443..8570bf9146a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -422,7 +422,7 @@ class TestNotebookExecutionStateService implements INotebookExecutionStateServic return this._executions.get(cellUri); } - createCellExecution(controllerId: string, notebook: URI, cellHandle: number): INotebookCellExecution { + createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution { const onComplete = () => this._executions.delete(CellUri.generate(notebook, cellHandle)); const exe = new TestCellExecution(notebook, cellHandle, onComplete); this._executions.set(CellUri.generate(notebook, cellHandle), exe); From e262c88fb16e1d4ec5c7d5cf82ac78dad7961091 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 25 May 2022 18:28:46 -0700 Subject: [PATCH 312/350] Fix process.platform for picomatch (#150430) The `picomatch` library currently checks `process.platform`. This check fails on web, which causes the markdown extension to not load To fix this, I'm replacing `process.platform` with the string`'web'` --- extensions/shared.webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 84aa4259a3b..3ebb9c62b52 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -151,6 +151,7 @@ const browserPlugins = [ ] }), new DefinePlugin({ + 'process.platform': JSON.stringify('web'), 'process.env': JSON.stringify({}), 'process.env.BROWSER_ENV': JSON.stringify('true') }) From 528ee1ae3daabe30c1307cf9dcd6e77eb96094bc Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 25 May 2022 18:29:28 -0700 Subject: [PATCH 313/350] Allow multiple entries with the same mimetype in dataTransfer (#150425) Currently our data transfer implementation only allows a single entry of each mimeType. There can only be a single `image/gif` file for example. However this doesn't match how the DOM apis work. If you drop multiple gifs into VS Code for example, the DataTransfer you get contains entries for each of the gifs. This change allows us to also support DataTransfers that have multiple entries with the same mime type. Just like with the DOM, we support constructing these duplicate mime data transfers internally, but do not allow extensions to create them As part of this change, I've also made a few clean ups: - Add helpers for creating dataTransfer items - Clarify when adding a data transfer item should `append` or `replace` - Adopt some helper functions in a few more places --- src/vs/base/common/dataTransfer.ts | 75 ++++++++++++------- src/vs/editor/browser/dnd.ts | 19 +++-- .../copyPaste/browser/copyPasteController.ts | 12 +-- .../browser/dropIntoEditorContribution.ts | 27 ++----- .../api/browser/mainThreadLanguageFeatures.ts | 4 +- .../api/browser/mainThreadTreeViews.ts | 4 +- .../api/common/extHostTypeConverters.ts | 10 +-- src/vs/workbench/api/common/extHostTypes.ts | 23 +++++- .../workbench/browser/parts/views/treeView.ts | 16 ++-- 9 files changed, 104 insertions(+), 86 deletions(-) diff --git a/src/vs/base/common/dataTransfer.ts b/src/vs/base/common/dataTransfer.ts index d5d99bd283b..5074f30c75e 100644 --- a/src/vs/base/common/dataTransfer.ts +++ b/src/vs/base/common/dataTransfer.ts @@ -17,55 +17,74 @@ export interface IDataTransferItem { value: any; } +export function createStringDataTransferItem(stringOrPromise: string | Promise): IDataTransferItem { + return { + asString: async () => stringOrPromise, + asFile: () => undefined, + value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined, + }; +} + +export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise): IDataTransferItem { + return { + asString: async () => '', + asFile: () => ({ name: fileName, uri, data }), + value: undefined, + }; +} + export class VSDataTransfer { - private readonly _data = new Map(); + private readonly _entries = new Map(); public get size(): number { - return this._data.size; + return this._entries.size; } public has(mimeType: string): boolean { - return this._data.has(mimeType); + return this._entries.has(this.toKey(mimeType)); } public get(mimeType: string): IDataTransferItem | undefined { - return this._data.get(mimeType); + return this._entries.get(this.toKey(mimeType))?.[0]; } - public set(mimeType: string, value: IDataTransferItem): void { - this._data.set(mimeType, value); + public append(mimeType: string, value: IDataTransferItem): void { + const existing = this._entries.get(mimeType); + if (existing) { + existing.push(value); + } else { + this._entries.set(this.toKey(mimeType), [value]); + } + } + + public replace(mimeType: string, value: IDataTransferItem): void { + this._entries.set(this.toKey(mimeType), [value]); } public delete(mimeType: string) { - this._data.delete(mimeType); + this._entries.delete(this.toKey(mimeType)); } - public setString(mimeType: string, stringOrPromise: string | Promise) { - this.set(mimeType, { - asString: async () => stringOrPromise, - asFile: () => undefined, - value: typeof stringOrPromise === 'string' ? stringOrPromise : undefined, - }); + public *entries(): Iterable<[string, IDataTransferItem]> { + for (const [mine, items] of this._entries.entries()) { + for (const item of items) { + yield [mine, item]; + } + } } - public setFile(mimeType: string, fileName: string, uri: URI | undefined, data: () => Promise) { - this.set(mimeType, { - asString: async () => '', - asFile: () => ({ name: fileName, uri, data }), - value: undefined, - }); - } - - public entries(): IterableIterator<[string, IDataTransferItem]> { - return this._data.entries(); - } - - public values(): IterableIterator { - return this._data.values(); + public values(): Iterable { + return Array.from(this._entries.values()).flat(); } public forEach(f: (value: IDataTransferItem, key: string) => void) { - this._data.forEach(f); + for (const [mime, item] of this.entries()) { + f(item, mime); + } + } + + private toKey(mimeType: string): string { + return mimeType.toLowerCase(); } } diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts index 08a3f558786..80a5958504d 100644 --- a/src/vs/editor/browser/dnd.ts +++ b/src/vs/editor/browser/dnd.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createFileDataTransferItem, createStringDataTransferItem, IDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { URI } from 'vs/base/common/uri'; +import { FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; export function toVSDataTransfer(dataTransfer: DataTransfer) { @@ -13,16 +14,20 @@ export function toVSDataTransfer(dataTransfer: DataTransfer) { const type = item.type; if (item.kind === 'string') { const asStringValue = new Promise(resolve => item.getAsString(resolve)); - vsDataTransfer.setString(type, asStringValue); + vsDataTransfer.append(type, createStringDataTransferItem(asStringValue)); } else if (item.kind === 'file') { - const file = item.getAsFile() as null | (File & { path?: string }); + const file = item.getAsFile(); if (file) { - const uri = file.path ? URI.parse(file.path) : undefined; - vsDataTransfer.setFile(type, file.name, uri, async () => { - return new Uint8Array(await file.arrayBuffer()); - }); + vsDataTransfer.append(type, createFileDataTransferItemFromFile(file)); } } } return vsDataTransfer; } + +export function createFileDataTransferItemFromFile(file: File): IDataTransferItem { + const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; + return createFileDataTransferItem(file.name, uri, async () => { + return new Uint8Array(await file.arrayBuffer()); + }); +} diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts index 6c203af56cd..74ddf65d392 100644 --- a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts @@ -6,7 +6,7 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { generateUuid } from 'vs/base/common/uuid'; @@ -103,7 +103,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi for (const result of results) { result?.forEach((value, key) => { - dataTransfer.set(key, value); + dataTransfer.replace(key, value); }); } @@ -148,7 +148,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (handle && this._currentClipboardItem?.handle === handle) { const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise; toMergeDataTransfer.forEach((value, key) => { - dataTransfer.set(key, value); + dataTransfer.append(key, value); }); } @@ -156,11 +156,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const resources = await this._clipboardService.readResources(); if (resources.length) { const value = resources.join('\n'); - dataTransfer.set(Mimes.uriList, { - value, - asString: async () => value, - asFile: () => undefined, - }); + dataTransfer.append(Mimes.uriList, createStringDataTransferItem(value)); } } diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index b713b71a7dd..4abda27de6c 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -5,11 +5,12 @@ import { distinct } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { toVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IPosition } from 'vs/editor/common/core/position'; @@ -20,7 +21,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { extractEditorsDropData, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; +import { extractEditorsDropData } from 'vs/platform/dnd/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -94,27 +95,11 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } public async extractDataTransferData(dragEvent: DragEvent): Promise { - const textEditorDataTransfer = new VSDataTransfer(); if (!dragEvent.dataTransfer) { - return textEditorDataTransfer; - } - - for (const item of dragEvent.dataTransfer.items) { - const type = item.type; - if (item.kind === 'string') { - const asStringValue = new Promise(resolve => item.getAsString(resolve)); - textEditorDataTransfer.setString(type, asStringValue); - } else if (item.kind === 'file') { - const file = item.getAsFile(); - if (file) { - const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; - textEditorDataTransfer.setFile(type, file.name, uri, async () => { - return new Uint8Array(await file.arrayBuffer()); - }); - } - } + return new VSDataTransfer(); } + const textEditorDataTransfer = toVSDataTransfer(dragEvent.dataTransfer); if (!textEditorDataTransfer.has(Mimes.uriList)) { const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent)) .filter(input => input.resource) @@ -122,7 +107,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr if (editorData.length) { const str = distinct(editorData).join('\n'); - textEditorDataTransfer.setString(Mimes.uriList.toLowerCase(), str); + textEditorDataTransfer.replace(Mimes.uriList, createStringDataTransferItem(str)); } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index f89e0698b9d..ff55fc5844f 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -5,7 +5,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -385,7 +385,7 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread const dataTransferOut = new VSDataTransfer(); result.items.forEach(([type, item]) => { - dataTransferOut.setString(type, item.asString); + dataTransferOut.replace(type, createStringDataTransferItem(item.asString)); }); return dataTransferOut; } diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 56133dc4b20..7c8aa9d2323 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -14,7 +14,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { VSBuffer } from 'vs/base/common/buffer'; import { DataTransferCache } from 'vs/workbench/api/common/shared/dataTransferCache'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; @@ -225,7 +225,7 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController { const additionalDataTransfer = new VSDataTransfer(); additionalDataTransferDTO.items.forEach(([type, item]) => { - additionalDataTransfer.setString(type, item.asString); + additionalDataTransfer.replace(type, createStringDataTransferItem(item.asString)); }); return additionalDataTransfer; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 95e24e8692a..18d71be8a26 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -40,6 +40,7 @@ import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/ed import type * as vscode from 'vscode'; import * as types from './extHostTypes'; import { once } from 'vs/base/common/functional'; +import { VSDataTransfer } from 'vs/base/common/dataTransfer'; export namespace Command { @@ -1980,14 +1981,13 @@ export namespace DataTransferItem { export namespace DataTransfer { export function toDataTransfer(value: extHostProtocol.DataTransferDTO, resolveFileData: (dataItemIndex: number) => Promise): types.DataTransfer { - const newDataTransfer = new types.DataTransfer(); - value.items.forEach(([type, item], index) => { - newDataTransfer.set(type, DataTransferItem.toDataTransferItem(item, () => resolveFileData(index))); + const init = value.items.map(([type, item], index) => { + return [type, DataTransferItem.toDataTransferItem(item, () => resolveFileData(index))] as const; }); - return newDataTransfer; + return new types.DataTransfer(init); } - export async function toDataTransferDTO(value: vscode.DataTransfer): Promise { + export async function toDataTransferDTO(value: vscode.DataTransfer | VSDataTransfer): Promise { const newDTO: extHostProtocol.DataTransferDTO = { items: [] }; const promises: Promise[] = []; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 76ae9232db9..a1c2a3ea107 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2451,18 +2451,33 @@ export class DataTransferItem { @es5ClassCompat export class DataTransfer { - #items = new Map(); + #items = new Map(); + + constructor(init?: Iterable) { + for (const [mime, item] of init ?? []) { + const existing = this.#items.get(mime); + if (existing) { + existing.push(item); + } else { + this.#items.set(mime, [item]); + } + } + } get(mimeType: string): DataTransferItem | undefined { - return this.#items.get(mimeType); + return this.#items.get(mimeType)?.[0]; } set(mimeType: string, value: DataTransferItem): void { - this.#items.set(mimeType, value); + // This intentionally overwrites all entries for a given mimetype. + // This is similar to how the DOM DataTransfer type works + this.#items.set(mimeType, [value]); } forEach(callbackfn: (value: DataTransferItem, key: string) => void): void { - this.#items.forEach(callbackfn); + for (const [mime, items] of this.#items) { + items.forEach(item => callbackfn(item, mime)); + } } } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 051926ebcb4..524979149fe 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -31,7 +31,7 @@ import { isString } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/views'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Command } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -66,7 +66,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; -import { CodeDataTransfers, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; +import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; +import { createFileDataTransferItemFromFile } from 'vs/editor/browser/dnd'; export class TreeViewPane extends ViewPane { @@ -1524,7 +1525,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } if (dataValue) { const converted = this.convertKnownMimes(type, kind, dataValue); - treeDataTransfer.setString(converted.type, converted.value + ''); + treeDataTransfer.append(converted.type, createStringDataTransferItem(converted.value + '')); } resolve(); })); @@ -1532,11 +1533,8 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { const file = dataItem.getAsFile(); if (file) { uris.push(URI.file(file.path)); - const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; if (dndController.supportsFileDataTransfers) { - treeDataTransfer.setFile(type, file.name, uri, async () => { - return new Uint8Array(await file.arrayBuffer()); - }); + treeDataTransfer.append(type, createFileDataTransferItemFromFile(file)); } } } @@ -1545,7 +1543,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { // Check if there are uris to add and add them if (uris.length) { - treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); + treeDataTransfer.replace(Mimes.uriList, createStringDataTransferItem(uris.map(uri => uri.toString()).join('\n'))); } const additionalWillDropPromise = this.treeViewsDragAndDropService.removeDragOperationTransfer(willDropUuid); @@ -1555,7 +1553,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { return additionalWillDropPromise.then(additionalDataTransfer => { if (additionalDataTransfer) { for (const item of additionalDataTransfer.entries()) { - treeDataTransfer.set(item[0], item[1]); + treeDataTransfer.append(item[0], item[1]); } } return dndController.handleDrop(treeDataTransfer, targetNode, new CancellationTokenSource().token, willDropUuid, treeSourceInfo?.id, treeSourceInfo?.itemHandles); From f86bbaa2a510b411ce4fb29da5766d8870880924 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 26 May 2022 07:36:10 -0700 Subject: [PATCH 314/350] Fix terminal find styles Fixes #150453 --- .../contrib/codeEditor/browser/find/simpleFindWidget.css | 3 --- .../workbench/contrib/terminal/browser/media/terminal.css | 7 +++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css index b280e584779..cc120227164 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css @@ -9,7 +9,6 @@ position: absolute; top: 0; right: 18px; - width: 360px; max-width: calc(100% - 28px - 28px - 8px); pointer-events: none; padding: 0 10px 10px; @@ -29,7 +28,6 @@ align-items: center; pointer-events: all; transition: top 200ms linear; - min-width: 360px; } .monaco-workbench.reduce-motion .monaco-editor .find-widget { @@ -46,7 +44,6 @@ .monaco-workbench .simple-find-part .monaco-findInput { flex: 1; - min-width: 220px; } .monaco-workbench .simple-find-part .button { diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 945161cad7f..e3ec3613456 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -87,13 +87,12 @@ .monaco-workbench .simple-find-part-wrapper.result-count { z-index: 33 !important; - padding-right: 80px; } .result-count .simple-find-part { - width: 280px; - max-width: 280px; - min-width: 280px; + width: 330px; + max-width: 330px; + min-width: 330px; } .result-count .matchesCount { From 27e10113dc53dace848912a4975f15087e0c584e Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 26 May 2022 07:42:03 -0700 Subject: [PATCH 315/350] improve configure display language & add clear display language preference (#150433) --- .../contrib/localizationsUpdater.ts | 4 +- .../sharedProcess/sharedProcessMain.ts | 4 +- src/vs/code/node/cliProcessMain.ts | 4 +- .../languagePacks/common/languagePacks.ts | 76 +++++++- .../languagePacks/node/languagePacks.ts | 39 ++-- .../node/remoteExtensionHostAgentCli.ts | 4 +- src/vs/server/node/serverServices.ts | 4 +- .../browser/localizationsActions.ts | 166 ++++++++++++------ .../localization.contribution.ts | 20 +-- .../services/localization/common/locale.ts | 13 ++ .../electron-sandbox/localeService.ts | 36 ++++ src/vs/workbench/workbench.sandbox.main.ts | 1 + 12 files changed, 289 insertions(+), 82 deletions(-) create mode 100644 src/vs/workbench/services/localization/common/locale.ts create mode 100644 src/vs/workbench/services/localization/electron-sandbox/localeService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts index 68085dcefcc..f035c7d8e4f 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts @@ -5,12 +5,12 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; +import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; export class LocalizationsUpdater extends Disposable { constructor( - @ILanguagePackService private readonly localizationsService: LanguagePackService + @ILanguagePackService private readonly localizationsService: NativeLanguagePackService ) { super(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index fb6cac109a9..3d5d20b8c71 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -47,7 +47,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; +import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { ConsoleLogger, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { FollowerLogService, LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; @@ -311,7 +311,7 @@ class SharedProcessMain extends Disposable { services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); // Localizations - services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); + services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); // Diagnostics services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 88f9dc88a7a..32d5fb9c6a3 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -37,7 +37,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; +import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; @@ -161,7 +161,7 @@ class CliMain extends Disposable { services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); // Localizations - services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); + services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); // Telemetry const appenders: AppInsightsAppender[] = []; diff --git a/src/vs/platform/languagePacks/common/languagePacks.ts b/src/vs/platform/languagePacks/common/languagePacks.ts index 473e358148a..61c6c04553a 100644 --- a/src/vs/platform/languagePacks/common/languagePacks.ts +++ b/src/vs/platform/languagePacks/common/languagePacks.ts @@ -3,10 +3,84 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { language } from 'vs/base/common/platform'; +import { IQuickPickItem } from 'vs/base/parts/quickinput/common/quickInput'; +import { localize } from 'vs/nls'; +import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export const ILanguagePackService = createDecorator('languagePackService'); + +export interface ILanguagePackItem extends IQuickPickItem { + readonly extensionId: string; + readonly galleryExtension?: IGalleryExtension; +} + export interface ILanguagePackService { readonly _serviceBrand: undefined; - getInstalledLanguages(): Promise; + getAvailableLanguages(): Promise>; + getInstalledLanguages(): Promise>; +} + +export abstract class LanguagePackBaseService extends Disposable implements ILanguagePackService { + _serviceBrand: undefined; + + constructor(@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService) { + super(); + } + + abstract getInstalledLanguages(): Promise>; + + async getAvailableLanguages(): Promise { + const timeout = new CancellationTokenSource(); + setTimeout(() => timeout.cancel(), 1000); + + let result; + try { + result = await this.extensionGalleryService.query({ + text: 'category:"language packs"', + pageSize: 20 + }, timeout.token); + } catch (_) { + // This method is best effort. So, we ignore any errors. + return []; + } + + const languagePackExtensions = result.firstPage.filter(e => e.properties.localizedLanguages?.length && e.tags.some(t => t.startsWith('lp-'))); + const allFromMarketplace: ILanguagePackItem[] = languagePackExtensions.map(lp => { + const languageName = lp.properties.localizedLanguages?.[0]; + const locale = lp.tags.find(t => t.startsWith('lp-'))!.split('lp-')[1]; + const baseQuickPick = this.createQuickPickItem({ locale, label: languageName }); + return { + ...baseQuickPick, + extensionId: lp.identifier.id, + galleryExtension: lp + }; + }); + + allFromMarketplace.push({ + ...this.createQuickPickItem({ locale: 'en', label: 'English' }), + extensionId: 'default', + }); + + return allFromMarketplace; + } + + protected createQuickPickItem(languageItem: { locale: string; label?: string | undefined }): IQuickPickItem { + const label = languageItem.label ?? languageItem.locale; + let description: string | undefined = languageItem.locale !== languageItem.label ? languageItem.locale : undefined; + if (languageItem.locale.toLowerCase() === language.toLowerCase()) { + if (!description) { + description = ''; + } + description += localize('currentDisplayLanguage', " (Current)"); + } + return { + id: languageItem.locale, + label, + description + }; + } } diff --git a/src/vs/platform/languagePacks/node/languagePacks.ts b/src/vs/platform/languagePacks/node/languagePacks.ts index 009c936bdb0..369f4bb32f6 100644 --- a/src/vs/platform/languagePacks/node/languagePacks.ts +++ b/src/vs/platform/languagePacks/node/languagePacks.ts @@ -4,21 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { createHash } from 'crypto'; -import { distinct, equals } from 'vs/base/common/arrays'; +import { equals } from 'vs/base/common/arrays'; import { Queue } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { join } from 'vs/base/common/path'; import { Promises } from 'vs/base/node/pfs'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ILocalizationContribution } from 'vs/platform/extensions/common/extensions'; -import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { ILogService } from 'vs/platform/log/common/log'; +import { ILocalizationContribution } from 'vs/platform/extensions/common/extensions'; +import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; interface ILanguagePack { hash: string; + label: string | undefined; extensions: { extensionIdentifier: IExtensionIdentifier; version: string; @@ -26,7 +27,7 @@ interface ILanguagePack { translations: { [id: string]: string }; } -export class LanguagePackService extends Disposable implements ILanguagePackService { +export class NativeLanguagePackService extends LanguagePackBaseService { declare readonly _serviceBrand: undefined; @@ -35,9 +36,10 @@ export class LanguagePackService extends Disposable implements ILanguagePackServ constructor( @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @INativeEnvironmentService environmentService: INativeEnvironmentService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @ILogService private readonly logService: ILogService ) { - super(); + super(extensionGalleryService); this.cache = this._register(new LanguagePacksCache(environmentService, logService)); this.extensionManagementService.registerParticipant({ postInstall: async (extension: ILocalExtension): Promise => { @@ -49,11 +51,21 @@ export class LanguagePackService extends Disposable implements ILanguagePackServ }); } - async getInstalledLanguages(): Promise { + async getInstalledLanguages(): Promise> { const languagePacks = await this.cache.getLanguagePacks(); - // Contributed languages are those installed via extension packs, so does not include English - const languages = ['en', ...Object.keys(languagePacks)]; - return distinct(languages); + const languages = Object.keys(languagePacks).map(locale => { + const languagePack = languagePacks[locale]; + const baseQuickPick = this.createQuickPickItem({ locale, label: languagePack.label }); + return { + ...baseQuickPick, + extensionId: languagePack.extensions[0].extensionIdentifier.id, + }; + }); + languages.push({ + ...this.createQuickPickItem({ locale: 'en', label: 'English' }), + extensionId: 'default', + }); + return languages; } private async postInstallExtension(extension: ILocalExtension): Promise { @@ -126,7 +138,12 @@ class LanguagePacksCache extends Disposable { if (extension.location.scheme === Schemas.file && isValidLocalization(localizationContribution)) { let languagePack = languagePacks[localizationContribution.languageId]; if (!languagePack) { - languagePack = { hash: '', extensions: [], translations: {} }; + languagePack = { + hash: '', + extensions: [], + translations: {}, + label: localizationContribution.localizedLanguageName ?? localizationContribution.languageName + }; languagePacks[localizationContribution.languageId] = languagePack; } let extensionInLanguagePack = languagePack.extensions.filter(e => areSameExtensions(e.extensionIdentifier, extensionIdentifier))[0]; diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 2d8bde94e63..898ff7d56b9 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -29,7 +29,7 @@ import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/ import { IServerEnvironmentService, ServerEnvironmentService, ServerParsedArgs } from 'vs/server/node/serverEnvironmentService'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; +import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { getErrorMessage } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { isAbsolute, join } from 'vs/base/common/path'; @@ -104,7 +104,7 @@ class CliMain extends Disposable { services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); - services.set(ILanguagePackService, new SyncDescriptor(LanguagePackService)); + services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); return new InstantiationService(services); } diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 9a4bdee0c1a..a1f9cd11405 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -36,7 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { LanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; +import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; import { AbstractLogger, DEFAULT_LOG_LEVEL, getLogLevel, ILogService, LogLevel, LogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; @@ -159,7 +159,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); const instantiationService: IInstantiationService = new InstantiationService(services); - services.set(ILanguagePackService, instantiationService.createInstance(LanguagePackService)); + services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService)); const extensionManagementCLIService = instantiationService.createInstance(ExtensionManagementCLIService); services.set(IExtensionManagementCLIService, extensionManagementCLIService); diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts index afebaf8f1e1..45df8ab135e 100644 --- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -4,88 +4,154 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { language } from 'vs/base/common/platform'; -import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { isWeb } from 'vs/base/common/platform'; + +const restart = localize('restart', "&&Restart"); + +export class ConfigureDisplayLanguageAction extends Action2 { + public static readonly ID = 'workbench.action.configureLocale'; + public static readonly LABEL = localize('configureLocale', "Configure Display Language"); -export class ConfigureLocaleAction extends Action2 { constructor() { super({ - id: 'workbench.action.configureLocale', - title: { original: 'Configure Display Language', value: localize('configureLocale', "Configure Display Language") }, + id: ConfigureDisplayLanguageAction.ID, + title: { original: 'Configure Display Language', value: ConfigureDisplayLanguageAction.LABEL }, menu: { id: MenuId.CommandPalette } }); } - private async getLanguageOptions(localizationService: ILanguagePackService): Promise { - const availableLanguages = await localizationService.getInstalledLanguages(); - availableLanguages.sort(); - - return availableLanguages - .map(language => { return { label: language }; }) - .concat({ label: localize('installAdditionalLanguages', "Install Additional Languages...") }); - } - - public override async run(accessor: ServicesAccessor): Promise { - const environmentService: IEnvironmentService = accessor.get(IEnvironmentService); + public async run(accessor: ServicesAccessor): Promise { const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService); const quickInputService: IQuickInputService = accessor.get(IQuickInputService); - const jsonEditingService: IJSONEditingService = accessor.get(IJSONEditingService); const hostService: IHostService = accessor.get(IHostService); - const notificationService: INotificationService = accessor.get(INotificationService); - const paneCompositeService: IPaneCompositePartService = accessor.get(IPaneCompositePartService); const dialogService: IDialogService = accessor.get(IDialogService); const productService: IProductService = accessor.get(IProductService); + const localeService: ILocaleService = accessor.get(ILocaleService); + const extensionManagementService: IExtensionManagementService = accessor.get(IExtensionManagementService); + const paneCompositePartService: IPaneCompositePartService = accessor.get(IPaneCompositePartService); + const notificationService: INotificationService = accessor.get(INotificationService); - const languageOptions = await this.getLanguageOptions(languagePackService); - const currentLanguageIndex = languageOptions.findIndex(l => l.label === language); + const installedLanguages = await languagePackService.getInstalledLanguages(); - try { - const selectedLanguage = await quickInputService.pick(languageOptions, - { - canPickMany: false, - placeHolder: localize('chooseDisplayLanguage', "Select Display Language"), - activeItem: languageOptions[currentLanguageIndex] - }); + const qp = quickInputService.createQuickPick(); + qp.placeholder = localize('chooseLocale', "Select Display Language"); - if (selectedLanguage === languageOptions[languageOptions.length - 1]) { - return paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true) - .then(viewlet => viewlet?.getViewPaneContainer()) - .then(viewlet => { - const extensionsViewlet = viewlet as IExtensionsViewPaneContainer; - extensionsViewlet.search('@category:"language packs"'); - extensionsViewlet.focus(); - }); + if (installedLanguages?.length) { + const items: Array = [{ type: 'separator', label: localize('installed', "Installed languages") }]; + qp.items = items.concat(installedLanguages); + } + + const disposables = new DisposableStore(); + const source = new CancellationTokenSource(); + disposables.add(qp.onDispose(() => { + source.cancel(); + disposables.dispose(); + })); + + const installedSet = new Set(installedLanguages?.map(language => language.id!) ?? []); + languagePackService.getAvailableLanguages().then(availableLanguages => { + const newLanguages = availableLanguages.filter(l => l.id && !installedSet.has(l.id)); + if (newLanguages.length) { + qp.items = [ + ...qp.items, + { type: 'separator', label: localize('available', "Available languages") }, + ...newLanguages + ]; + } + qp.busy = false; + }); + + disposables.add(qp.onDidAccept(async () => { + const selectedLanguage = qp.activeItems[0]; + qp.hide(); + + // Only Desktop has the concept of installing language packs so we only do this for Desktop + // and only if the language pack is not installed + if (!isWeb && !installedSet.has(selectedLanguage.id!)) { + try { + // Show the view so the user can see the language pack to be installed + let viewlet = await paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar); + (viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${selectedLanguage.extensionId}`); + + // Only actually install a language pack from Microsoft + if (selectedLanguage.galleryExtension?.publisher.toLowerCase() !== 'ms-ceintl') { + return; + } + + await extensionManagementService.installFromGallery(selectedLanguage.galleryExtension); + } catch (err) { + notificationService.error(err); + return; + } } - if (selectedLanguage) { - await jsonEditingService.write(environmentService.argvResource, [{ path: ['locale'], value: selectedLanguage.label }], true); - const restart = await dialogService.confirm({ + if (await localeService.setLocale(selectedLanguage.id!)) { + const restartDialog = await dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), - primaryButton: localize('restart', "&&Restart") + primaryButton: restart }); - if (restart.confirmed) { + if (restartDialog.confirmed) { hostService.restart(); } } - } catch (e) { - notificationService.error(e); + })); + + qp.show(); + qp.busy = true; + } +} + +export class ClearDisplayLanguageAction extends Action2 { + public static readonly ID = 'workbench.action.clearLocalePreference'; + public static readonly LABEL = localize('clearDisplayLanguage', "Clear Display Language Preference"); + + constructor() { + super({ + id: ClearDisplayLanguageAction.ID, + title: { original: 'Clear Display Language Preference', value: ClearDisplayLanguageAction.LABEL }, + menu: { + id: MenuId.CommandPalette + } + }); + } + + public async run(accessor: ServicesAccessor): Promise { + const localeService: ILocaleService = accessor.get(ILocaleService); + const dialogService: IDialogService = accessor.get(IDialogService); + const productService: IProductService = accessor.get(IProductService); + const hostService: IHostService = accessor.get(IHostService); + + if (await localeService.setLocale(undefined)) { + const restartDialog = await dialogService.confirm({ + type: 'info', + message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), + detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), + primaryButton: restart + }); + + if (restartDialog.confirmed) { + hostService.restart(); + } } } } diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index c725565a55d..b04105ab3c9 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -24,10 +24,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { ConfigureLocaleAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; +import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; // Register action to configure locale and related settings -registerAction2(ConfigureLocaleAction); +registerAction2(ConfigureDisplayLanguageAction); +registerAction2(ClearDisplayLanguageAction); const LANGUAGEPACK_SUGGESTION_IGNORE_STORAGE_KEY = 'extensionsAssistant/languagePackSuggestionIgnore'; @@ -195,14 +196,13 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo } - private isLanguageInstalled(language: string | undefined): Promise { - return this.extensionManagementService.getInstalled() - .then(installed => installed.some(i => - !!(i.manifest - && i.manifest.contributes - && i.manifest.contributes.localizations - && i.manifest.contributes.localizations.length - && i.manifest.contributes.localizations.some(l => l.languageId.toLowerCase() === language)))); + private async isLanguageInstalled(language: string | undefined): Promise { + const installed = await this.extensionManagementService.getInstalled(); + return installed.some(i => !!(i.manifest + && i.manifest.contributes + && i.manifest.contributes.localizations + && i.manifest.contributes.localizations.length + && i.manifest.contributes.localizations.some(l => l.languageId.toLowerCase() === language))); } private installExtension(extension: IGalleryExtension): Promise { diff --git a/src/vs/workbench/services/localization/common/locale.ts b/src/vs/workbench/services/localization/common/locale.ts new file mode 100644 index 00000000000..1b67af98c04 --- /dev/null +++ b/src/vs/workbench/services/localization/common/locale.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ILocaleService = createDecorator('localizationService'); + +export interface ILocaleService { + readonly _serviceBrand: undefined; + setLocale(languagePackItem: string | undefined): Promise; +} diff --git a/src/vs/workbench/services/localization/electron-sandbox/localeService.ts b/src/vs/workbench/services/localization/electron-sandbox/localeService.ts new file mode 100644 index 00000000000..d435b3725d5 --- /dev/null +++ b/src/vs/workbench/services/localization/electron-sandbox/localeService.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 { language } from 'vs/base/common/platform'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; + +export class NativeLocaleService implements ILocaleService { + _serviceBrand: undefined; + + constructor( + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @INotificationService private readonly notificationService: INotificationService, + ) { } + + async setLocale(locale: string | undefined): Promise { + try { + if (locale === language || (!locale && language === 'en')) { + return false; + } + await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); + return true; + } catch (err) { + this.notificationService.error(err); + return false; + } + } +} + +registerSingleton(ILocaleService, NativeLocaleService, true); diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index dd9ddd715b1..41435143c48 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -83,6 +83,7 @@ import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; import 'vs/workbench/services/search/electron-sandbox/searchService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; +import 'vs/workbench/services/localization/electron-sandbox/localeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; From cc86b15a44fe28300180108f4d95cacee98717dc Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 26 May 2022 08:19:15 -0700 Subject: [PATCH 316/350] Enable grabbing translations from an alternate location for server distro/serverful scenarios (#150436) * nls web story * better handling of urls * clean up code and don't do nls in dev * use version instead of quality * revert changes in workbench-dev.html * update nls from changes in vscode-loader * sanitize url a bit * revert loader change --- src/vs/base/common/product.ts | 1 + src/vs/code/browser/workbench/workbench.html | 16 +++++++++++++++- src/vs/server/node/webClientServer.ts | 7 ++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index c66cf41aa6c..731c77b8893 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -74,6 +74,7 @@ export interface IProductConfiguration { readonly resourceUrlTemplate: string; readonly controlUrl: string; readonly recommendationsUrl: string; + readonly nlsBaseUrl: string; }; readonly extensionTips?: { [id: string]: string }; diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 2e4eddb19b4..d8914408e4a 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -40,6 +40,19 @@ Object.keys(self.webPackagePaths).map(function (key, index) { self.webPackagePaths[key] = `${baseUrl}/node_modules/${key}/${self.webPackagePaths[key]}`; }); + + // Set up nls if the user is not using the default language (English) + const nlsConfig = {}; + const locale = navigator.language; + if (!locale.startsWith('en')) { + nlsConfig['vs/nls'] = { + availableLanguages: { + '*': locale + }, + baseUrl: '{{WORKBENCH_NLS_BASE_URL}}' + }; + } + require.config({ baseUrl: `${baseUrl}/out`, recordStats: true, @@ -48,7 +61,8 @@ return value; } }), - paths: self.webPackagePaths + paths: self.webPackagePaths, + ...nlsConfig });